Ticket #35816: 35816.2.diff
File 35816.2.diff, 11.4 KB (added by , 9 years ago) |
---|
-
new file src/wp-includes/class-wp-metadata-lazyloader.php
diff --git src/wp-includes/class-wp-metadata-lazyloader.php src/wp-includes/class-wp-metadata-lazyloader.php new file mode 100644 index 0000000..653a2cb
- + 1 <?php 2 3 /** 4 * Lazyloader for object metadata. 5 * 6 * @since 4.5.0 7 */ 8 class WP_Metadata_Lazyloader { 9 protected $pending_posts; 10 11 /** 12 * Add posts to the metadata lazyload queue. 13 * 14 * @since 4.5.0 15 * 16 * @param array $posts Array of WP_Post objects. 17 * @param string $meta_type Type of meta to be lazyloaded. Accepts 'term' or 'comment'. 18 */ 19 public function add_posts( $posts, $meta_type ) { 20 if ( empty( $posts ) ) { 21 return; 22 } 23 24 foreach ( $posts as $post ) { 25 if ( ! ( $post instanceof WP_Post ) || isset( $this->pending_posts[ $meta_type ][ $post->ID ] ) ) { 26 continue; 27 } 28 29 $this->pending_posts[ $meta_type ][ $post->ID ] = $post->post_type; 30 } 31 32 // @todo Comment meta support. 33 add_action( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 ); 34 } 35 36 /** 37 * Lazy-loads termmeta for located posts. 38 * 39 * As a rule, term queries (`get_terms()` and `wp_get_object_terms()`) prime the metadata cache for matched 40 * terms by default. However, this can cause a slight performance penalty, especially when that metadata is 41 * not actually used. In the context of a `WP_Query` instance, we're able to avoid this potential penalty. 42 * `update_object_term_cache()`, called from `update_post_caches()`, does not 'update_term_meta_cache'. 43 * Instead, the first time `get_term_meta()` is called from within a `WP_Query` loop, the current method 44 * detects the fact, and then primes the metadata cache for all terms attached to all posts in the loop, 45 * with a single database query. 46 * 47 * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it 48 * directly, from either inside or outside the `WP_Query` object. 49 * 50 * @since 4.5.0 51 * @access public 52 * 53 * @param mixed $check The `$check` param passed from the 'get_term_metadata' hook. 54 * @param int $term_id ID of the term whose metadata is being cached. 55 * @return mixed In order not to short-circuit `get_metadata()`. Generally, this is `null`, but it could be 56 * another value if filtered by a plugin. 57 */ 58 public function lazyload_term_meta( $check, $term_id ) { 59 $post_type_taxonomies = $term_ids = array(); 60 foreach ( $this->pending_posts['term'] as $post_id => $post_type ) { 61 if ( ! isset( $post_type_taxonomies[ $post_type ] ) ) { 62 $post_type_taxonomies[ $post_type ] = get_object_taxonomies( $post_type ); 63 64 } 65 66 foreach ( $post_type_taxonomies[ $post_type ] as $taxonomy ) { 67 // Term cache should already be primed by 'update_post_term_cache'. 68 $terms = get_object_term_cache( $post_id, $taxonomy ); 69 if ( false !== $terms ) { 70 foreach ( $terms as $term ) { 71 if ( ! isset( $term_ids[ $term->term_id ] ) ) { 72 $term_ids[ $term->term_id ] = 1; 73 } 74 } 75 } 76 } 77 } 78 79 if ( $term_ids ) { 80 update_termmeta_cache( array_keys( $term_ids ) ); 81 } 82 83 // No need to run again for this set of posts. 84 $this->pending_posts = array(); 85 remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 ); 86 87 return $check; 88 } 89 } -
src/wp-includes/post.php
diff --git src/wp-includes/post.php src/wp-includes/post.php index 43361d4..7035ead 100644
function wp_delete_auto_drafts() { 5948 5948 } 5949 5949 5950 5950 /** 5951 * Queue posts for metadata lazyloading. 5952 * 5953 * @since 4.5.0 5954 * 5955 * @param array $posts Array of WP_Post objects. 5956 * @param string $meta_type Type of meta to be lazyloaded. Accepts 'term' or 'comment'. 5957 */ 5958 function wp_queue_posts_for_metadata_lazyloading( $posts, $meta_type ) { 5959 static $wp_metadata_lazyloader; 5960 5961 if ( null === $wp_metadata_lazyloader ) { 5962 $wp_metadata_lazyloader = new WP_Metadata_Lazyloader(); 5963 } 5964 5965 $wp_metadata_lazyloader->add_posts( $posts, $meta_type ); 5966 } 5967 5968 /** 5951 5969 * Update the custom taxonomies' term counts when a post's status is changed. 5952 5970 * 5953 5971 * For example, default posts term counts (for custom taxonomies) don't include -
src/wp-includes/query.php
diff --git src/wp-includes/query.php src/wp-includes/query.php index a8fbbc5..e6d7aec 100644
class WP_Query { 3607 3607 3608 3608 3609 3609 if ( $q['update_post_term_cache'] ) { 3610 add_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2);3610 wp_queue_posts_for_metadata_lazyloading( $this->posts, 'term' ); 3611 3611 } 3612 3612 3613 3613 if ( ! $q['suppress_filters'] ) { … … class WP_Query { 4834 4834 } 4835 4835 4836 4836 /** 4837 * Lazy-loads termmeta for located posts.4838 *4839 * As a rule, term queries (`get_terms()` and `wp_get_object_terms()`) prime the metadata cache for matched4840 * terms by default. However, this can cause a slight performance penalty, especially when that metadata is4841 * not actually used. In the context of a `WP_Query` instance, we're able to avoid this potential penalty.4842 * `update_object_term_cache()`, called from `update_post_caches()`, does not 'update_term_meta_cache'.4843 * Instead, the first time `get_term_meta()` is called from within a `WP_Query` loop, the current method4844 * detects the fact, and then primes the metadata cache for all terms attached to all posts in the loop,4845 * with a single database query.4846 *4847 * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it4848 * directly, from either inside or outside the `WP_Query` object.4849 *4850 * @since 4.4.04851 * @access public4852 *4853 * @param mixed $check The `$check` param passed from the 'get_term_metadata' hook.4854 * @param int $term_id ID of the term whose metadata is being cached.4855 * @return mixed In order not to short-circuit `get_metadata()`. Generally, this is `null`, but it could be4856 * another value if filtered by a plugin.4857 */4858 public function lazyload_term_meta( $check, $term_id ) {4859 // We can only lazyload if the entire post object is present.4860 $posts = array();4861 foreach ( $this->posts as $post ) {4862 if ( $post instanceof WP_Post ) {4863 $posts[] = $post;4864 }4865 }4866 4867 if ( ! empty( $posts ) ) {4868 // Fetch cached term_ids for each post. Keyed by term_id for faster lookup.4869 $term_ids = array();4870 foreach ( $posts as $post ) {4871 $taxonomies = get_object_taxonomies( $post->post_type );4872 foreach ( $taxonomies as $taxonomy ) {4873 // Term cache should already be primed by 'update_post_term_cache'.4874 $terms = get_object_term_cache( $post->ID, $taxonomy );4875 if ( false !== $terms ) {4876 foreach ( $terms as $term ) {4877 if ( ! isset( $term_ids[ $term->term_id ] ) ) {4878 $term_ids[ $term->term_id ] = 1;4879 }4880 }4881 }4882 }4883 }4884 4885 /*4886 * Only update the metadata cache for terms belonging to these posts if the term_id passed4887 * to `get_term_meta()` matches one of those terms. This prevents a single call to4888 * `get_term_meta()` from priming metadata for all `WP_Query` objects.4889 */4890 if ( isset( $term_ids[ $term_id ] ) ) {4891 update_termmeta_cache( array_keys( $term_ids ) );4892 remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );4893 }4894 }4895 4896 // If no terms were found, there's no need to run this again.4897 if ( empty( $term_ids ) ) {4898 remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );4899 }4900 4901 return $check;4902 }4903 4904 /**4905 4837 * Lazy-load comment meta when inside of a `WP_Query` loop. 4906 4838 * 4907 4839 * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it -
src/wp-settings.php
diff --git src/wp-settings.php src/wp-settings.php index ef1d2cd..3c21597 100644
require( ABSPATH . WPINC . '/class-wp-roles.php' ); 126 126 require( ABSPATH . WPINC . '/class-wp-role.php' ); 127 127 require( ABSPATH . WPINC . '/class-wp-user.php' ); 128 128 require( ABSPATH . WPINC . '/query.php' ); 129 require( ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php' ); 129 130 require( ABSPATH . WPINC . '/date.php' ); 130 131 require( ABSPATH . WPINC . '/theme.php' ); 131 132 require( ABSPATH . WPINC . '/class-wp-theme.php' ); -
tests/phpunit/tests/term/meta.php
diff --git tests/phpunit/tests/term/meta.php tests/phpunit/tests/term/meta.php index f6c7b90..da387e4 100644
class Tests_Term_Meta extends WP_UnitTestCase { 148 148 } 149 149 } 150 150 151 /**152 * @ticket 34073153 */154 public function test_term_meta_should_be_lazy_loaded_only_for_the_queries_in_which_the_term_has_posts() {155 global $wpdb;156 157 $posts = self::factory()->post->create_many( 3, array( 'post_status' => 'publish' ) );158 register_taxonomy( 'wptests_tax', 'post' );159 $terms = self::factory()->term->create_many( 6, array( 'taxonomy' => 'wptests_tax' ) );160 161 wp_set_object_terms( $posts[0], array( $terms[0], $terms[1] ), 'wptests_tax' );162 wp_set_object_terms( $posts[1], array( $terms[2], $terms[3] ), 'wptests_tax' );163 wp_set_object_terms( $posts[2], array( $terms[0], $terms[4], $terms[5] ), 'wptests_tax' );164 165 foreach ( $terms as $t ) {166 add_term_meta( $t, 'foo', 'bar' );167 }168 169 $q0 = new WP_Query( array( 'p' => $posts[0], 'cache_results' => true ) );170 $q1 = new WP_Query( array( 'p' => $posts[1], 'cache_results' => true ) );171 $q2 = new WP_Query( array( 'p' => $posts[2], 'cache_results' => true ) );172 173 /*174 * $terms[0] belongs to both $posts[0] and $posts[2], so `get_term_meta( $terms[0] )` should prime175 * the cache for term matched by $q0 and $q2.176 */177 178 // First request will hit the database.179 $num_queries = $wpdb->num_queries;180 181 // Prime caches.182 $this->assertSame( 'bar', get_term_meta( $terms[0], 'foo', true ) );183 184 // Two queries: one for $q0 and one for $q2.185 $num_queries += 2;186 $this->assertSame( $num_queries, $wpdb->num_queries );187 188 // Next requests should be in cache.189 $this->assertSame( 'bar', get_term_meta( $terms[1], 'foo', true ) );190 $this->assertSame( 'bar', get_term_meta( $terms[4], 'foo', true ) );191 $this->assertSame( 'bar', get_term_meta( $terms[5], 'foo', true ) );192 $this->assertSame( $num_queries, $wpdb->num_queries );193 194 // Querying for $terms[2] will prime $terms[3] as well.195 $this->assertSame( 'bar', get_term_meta( $terms[2], 'foo', true ) );196 $num_queries++;197 $this->assertSame( $num_queries, $wpdb->num_queries );198 199 $this->assertSame( 'bar', get_term_meta( $terms[3], 'foo', true ) );200 $this->assertSame( $num_queries, $wpdb->num_queries );201 }202 203 public function test_adding_term_meta_should_bust_get_terms_cache() {204 $terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) );205 206 add_term_meta( $terms[0], 'foo', 'bar' );207 208 // Prime cache.209 $found = get_terms( 'wptests_tax', array(210 'hide_empty' => false,211 'fields' => 'ids',212 'meta_query' => array(213 array(214 'key' => 'foo',215 'value' => 'bar',216 ),217 ),218 ) );219 220 $this->assertEqualSets( array( $terms[0] ), $found );221 222 add_term_meta( $terms[1], 'foo', 'bar' );223 224 $found = get_terms( 'wptests_tax', array(225 'hide_empty' => false,226 'fields' => 'ids',227 'meta_query' => array(228 array(229 'key' => 'foo',230 'value' => 'bar',231 ),232 ),233 ) );234 235 $this->assertEqualSets( array( $terms[0], $terms[1] ), $found );236 }237 238 151 public function test_updating_term_meta_should_bust_get_terms_cache() { 239 152 $terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) ); 240 153