Make WordPress Core


Ignore:
Timestamp:
10/11/2022 05:15:11 PM (2 years ago)
Author:
hellofromTonya
Message:

Editor: Fix performance regression in WP_Theme_JSON_Resolver.

A significant performance regression was added late in WP 6.1 beta cycle when some of the existing caching for theme.json processing was removed. The rationale for removing the caching was this code was now used before all the blocks are registered (aka get template data, etc.) and resulted in stale cache that created issues (see Gutenberg Issue 44434 and Gutenberg Issue 44619). The changes were limited to only reads from the file system. However, it introduced a big impact in performance.

This commit adds caching and checks for blocks with different origins. How? It add caching for the calculated data for core, theme, and user based on the blocks that are registered. If the blocks haven't changed since the last time they were calculated for the origin, the cached data is returned. Otherwise, the data is recalculated and cached.

Essentially, this brings back the previous cache, but refreshing it when the blocks change.

It partially adds unit tests for these changes. Additional tests will be added.

References:

Follow-up to [54251], [54399].

Props aristath, oandregal, bernhard-reiter, spacedmonkey, hellofromTonya.
See #56467.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php

    r54446 r54493  
    3434    private $queries = array();
    3535
     36    /**
     37     * WP_Theme_JSON_Resolver::$blocks_cache property.
     38     *
     39     * @var ReflectionProperty
     40     */
     41    private static $property_blocks_cache;
     42
     43    /**
     44     * Original value of the WP_Theme_JSON_Resolver::$blocks_cache property.
     45     *
     46     * @var array
     47     */
     48    private static $property_blocks_cache_orig_value;
     49
     50    /**
     51     * WP_Theme_JSON_Resolver::$core property.
     52     *
     53     * @var ReflectionProperty
     54     */
     55    private static $property_core;
     56
     57    /**
     58     * Original value of the WP_Theme_JSON_Resolver::$core property.
     59     *
     60     * @var WP_Theme_JSON
     61     */
     62    private static $property_core_orig_value;
     63
     64    public static function set_up_before_class() {
     65        parent::set_up_before_class();
     66
     67        static::$property_blocks_cache = new ReflectionProperty( WP_Theme_JSON_Resolver::class, 'blocks_cache' );
     68        static::$property_blocks_cache->setAccessible( true );
     69        static::$property_blocks_cache_orig_value = static::$property_blocks_cache->getValue();
     70
     71        static::$property_core = new ReflectionProperty( WP_Theme_JSON_Resolver::class, 'core' );
     72        static::$property_core->setAccessible( true );
     73        static::$property_core_orig_value = static::$property_core->getValue();
     74    }
     75
     76    public static function tear_down_after_class() {
     77        static::$property_blocks_cache->setValue( WP_Theme_JSON_Resolver::class, static::$property_blocks_cache_orig_value );
     78        static::$property_core->setValue( WP_Theme_JSON_Resolver::class, static::$property_core_orig_value );
     79        parent::tear_down_after_class();
     80    }
     81
    3682    public function set_up() {
    3783        parent::set_up();
     
    56102        wp_clean_themes_cache();
    57103        unset( $GLOBALS['wp_themes'] );
     104
     105        // Reset data between tests.
     106        WP_Theme_JSON_Resolver::clean_cached_data();
    58107        parent::tear_down();
    59108    }
     
    191240    }
    192241
     242    private function get_registered_block_names( $hard_reset = false ) {
     243        static $expected_block_names;
     244
     245        if ( ! $hard_reset && ! empty( $expected_block_names ) ) {
     246            return $expected_block_names;
     247        }
     248
     249        $expected_block_names = array();
     250        $resolver             = WP_Block_Type_Registry::get_instance();
     251        $blocks               = $resolver->get_all_registered();
     252        foreach ( array_keys( $blocks ) as $block_name ) {
     253            $expected_block_names[ $block_name ] = true;
     254        }
     255
     256        return $expected_block_names;
     257    }
     258
     259    /**
     260     * Tests when WP_Theme_JSON_Resolver::$blocks_cache is empty or does not match
     261     * the all registered blocks.
     262     *
     263     * Though this is a non-public method, it is vital to other functionality.
     264     * Therefore, tests are provided to validate it functions as expected.
     265     *
     266     * @dataProvider data_has_same_registered_blocks_when_all_blocks_not_cached
     267     * @ticket 56467
     268     *
     269     * @param string $origin The origin to test.
     270     */
     271    public function test_has_same_registered_blocks_when_all_blocks_not_cached( $origin, array $cache = array() ) {
     272        $has_same_registered_blocks = new ReflectionMethod( WP_Theme_JSON_Resolver::class, 'has_same_registered_blocks' );
     273        $has_same_registered_blocks->setAccessible( true );
     274        $expected_cache = $this->get_registered_block_names();
     275
     276        // Set up the blocks cache for the origin.
     277        $blocks_cache            = static::$property_blocks_cache->getValue();
     278        $blocks_cache[ $origin ] = $cache;
     279        static::$property_blocks_cache->setValue( null, $blocks_cache );
     280
     281        $this->assertFalse( $has_same_registered_blocks->invoke( null, $origin ), 'WP_Theme_JSON_Resolver::has_same_registered_blocks() should return false when same blocks are not cached' );
     282        $blocks_cache = static::$property_blocks_cache->getValue();
     283        $this->assertSameSets( $expected_cache, $blocks_cache[ $origin ], 'WP_Theme_JSON_Resolver::$blocks_cache should contain all expected block names for the given origin' );
     284    }
     285
     286    /**
     287     * Data provider.
     288     *
     289     * @return array
     290     */
     291    public function data_has_same_registered_blocks_when_all_blocks_not_cached() {
     292        return array(
     293            'origin: core; cache: empty'       => array(
     294                'origin' => 'core',
     295            ),
     296            'origin: blocks; cache: empty'     => array(
     297                'origin' => 'blocks',
     298            ),
     299            'origin: theme; cache: empty'      => array(
     300                'origin' => 'theme',
     301            ),
     302            'origin: user; cache: empty'       => array(
     303                'origin' => 'user',
     304            ),
     305            'origin: core; cache: not empty'   => array(
     306                'origin' => 'core',
     307                'cache'  => array(
     308                    'core/block' => true,
     309                ),
     310            ),
     311            'origin: blocks; cache: not empty' => array(
     312                'origin' => 'blocks',
     313                'cache'  => array(
     314                    'core/block'    => true,
     315                    'core/comments' => true,
     316                ),
     317            ),
     318            'origin: theme; cache: not empty'  => array(
     319                'origin' => 'theme',
     320                'cache'  => array(
     321                    'core/cover' => true,
     322                ),
     323            ),
     324            'origin: user; cache: not empty'   => array(
     325                'origin' => 'user',
     326                'cache'  => array(
     327                    'core/gallery' => true,
     328                ),
     329            ),
     330        );
     331    }
     332
     333    /**
     334     * Tests when WP_Theme_JSON_Resolver::$blocks_cache is empty or does not match
     335     * the all registered blocks.
     336     *
     337     * Though this is a non-public method, it is vital to other functionality.
     338     * Therefore, tests are provided to validate it functions as expected.
     339     *
     340     * @dataProvider data_has_same_registered_blocks_when_all_blocks_are_cached
     341     * @ticket 56467
     342     *
     343     * @param string $origin The origin to test.
     344     */
     345    public function test_has_same_registered_blocks_when_all_blocks_are_cached( $origin ) {
     346        $has_same_registered_blocks = new ReflectionMethod( WP_Theme_JSON_Resolver::class, 'has_same_registered_blocks' );
     347        $has_same_registered_blocks->setAccessible( true );
     348        $expected_cache = $this->get_registered_block_names();
     349
     350        // Set up the cache with all registered blocks.
     351        $blocks_cache            = static::$property_blocks_cache->getValue();
     352        $blocks_cache[ $origin ] = $this->get_registered_block_names();
     353        static::$property_blocks_cache->setValue( null, $blocks_cache );
     354
     355        $this->assertTrue( $has_same_registered_blocks->invoke( null, $origin ), 'WP_Theme_JSON_Resolver::has_same_registered_blocks() should return true when using the cache' );
     356        $this->assertSameSets( $expected_cache, $blocks_cache[ $origin ], 'WP_Theme_JSON_Resolver::$blocks_cache should contain all expected block names for the given origin' );
     357    }
     358
     359    /**
     360     * Data provider.
     361     *
     362     * @return array
     363     */
     364    public function data_has_same_registered_blocks_when_all_blocks_are_cached() {
     365        return array(
     366            'core'   => array( 'core' ),
     367            'blocks' => array( 'blocks' ),
     368            'theme'  => array( 'theme' ),
     369            'user'   => array( 'user' ),
     370        );
     371    }
     372
     373    /**
     374     * @dataProvider data_get_core_data
     375     * @covers WP_Theme_JSON_Resolver::get_core_data
     376     * @ticket 56467
     377     */
     378    public function test_get_core_data( $should_fire_filter, $core_is_cached, $blocks_are_cached ) {
     379        WP_Theme_JSON_Resolver::clean_cached_data();
     380
     381        // If should cache core, then fire the method to cache it before running the tests.
     382        if ( $core_is_cached ) {
     383            WP_Theme_JSON_Resolver::get_core_data();
     384        }
     385
     386        // If should cache registered blocks, then set them up before running the tests.
     387        if ( $blocks_are_cached ) {
     388            $blocks_cache         = static::$property_blocks_cache->getValue();
     389            $blocks_cache['core'] = $this->get_registered_block_names();
     390            static::$property_blocks_cache->setValue( null, $blocks_cache );
     391        }
     392
     393        $expected_filter_count = did_filter( 'theme_json_default' );
     394        $actual                = WP_Theme_JSON_Resolver::get_core_data();
     395        if ( $should_fire_filter ) {
     396            $expected_filter_count++;
     397        }
     398
     399        $this->assertSame( $expected_filter_count, did_filter( 'theme_json_default' ), 'The filter "theme_json_default" should fire the given number of times' );
     400        $this->assertInstanceOf( WP_Theme_JSON::class, $actual, 'WP_Theme_JSON_Resolver::get_core_data() should return instance of WP_Theme_JSON' );
     401        $this->assertSame( static::$property_core->getValue(), $actual, 'WP_Theme_JSON_Resolver::$core property should be the same object as returned from WP_Theme_JSON_Resolver::get_core_data()' );
     402    }
     403
     404    /**
     405     * Data provider.
     406     *
     407     * @return array
     408     */
     409    public function data_get_core_data() {
     410        return array(
     411            'When both caches are empty'     => array(
     412                'should_fire_filter' => true,
     413                'core_is_cached'     => false,
     414                'blocks_are_cached'  => false,
     415            ),
     416            'When the blocks_cache is not empty and matches' => array(
     417                'should_fire_filter' => true,
     418                'core_is_cached'     => false,
     419                'blocks_are_cached'  => true,
     420            ),
     421            'When blocks_cache is empty but core cache is not' => array(
     422                'should_fire_filter' => true,
     423                'core_is_cached'     => true,
     424                'blocks_are_cached'  => false,
     425            ),
     426            'When both caches are not empty' => array(
     427                'should_fire_filter' => true,
     428                'core_is_cached'     => true,
     429                'blocks_are_cached'  => false,
     430            ),
     431        );
     432    }
     433
    193434    /**
    194435     * @ticket 52991
Note: See TracChangeset for help on using the changeset viewer.