Make WordPress Core

Changeset 56978


Ignore:
Timestamp:
10/20/2023 07:06:46 PM (3 years ago)
Author:
joemcgill
Message:

Themes: Make caches for block patterns clearable.

In [56765], theme block pattern files were cached to a transient as a performance enhancement. However, transients are not easily clearable when caches are flushed on environments not using a persistent cache, which can lead to errors if the theme files are renamed, edited, or moved.

This changes the caching mechanism to use wp_cache_set() instead, and caches these values to the global group so they are still persistent on environments using an object cache, and will be cleared by a cache flush.

In addition, the helper _wp_get_block_patterns has been moved WP_Theme::get_block_patterns for consistency with other block related theme methods and cache helpers for these values, WP_Theme::get_pattern_cache and WP_Theme::set_pattern_cache, have been made private.

Relevant unit tests updated.

Props: afercia, flixos90, mukesh27, joemcgill.
Fixes #59633. See #59591, #59490.

Location:
trunk
Files:
3 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-patterns.php

    r56931 r56978  
    342342
    343343        foreach ( $themes as $theme ) {
    344                 $patterns    = _wp_get_block_patterns( $theme );
     344                $patterns    = $theme->get_block_patterns();
    345345                $dirpath     = $theme->get_stylesheet_directory() . '/patterns/';
    346346                $text_domain = $theme->get( 'TextDomain' );
    … …  
    388388}
    389389add_action( 'init', '_register_theme_block_patterns' );
    390 
    391 /**
    392  * Gets block pattern data for a specified theme.
    393  * Each pattern is defined as a PHP file and defines
    394  *  its metadata using plugin-style headers. The minimum required definition is:
    395  *
    396  *      /**
    397  *       * Title: My Pattern
    398  *       * Slug: my-theme/my-pattern
    399  *       *
    400  *
    401  *  The output of the PHP source corresponds to the content of the pattern, e.g.:
    402  *
    403  *      <main><p><?php echo "Hello"; ?></p></main>
    404  *
    405  *  If applicable, this will collect from both parent and child theme.
    406  *
    407  *  Other settable fields include:
    408  *
    409  *    - Description
    410  *    - Viewport Width
    411  *    - Inserter         (yes/no)
    412  *    - Categories       (comma-separated values)
    413  *    - Keywords         (comma-separated values)
    414  *    - Block Types      (comma-separated values)
    415  *    - Post Types       (comma-separated values)
    416  *    - Template Types   (comma-separated values)
    417  *
    418  * @since 6.4.0
    419  * @access private
    420  *
    421  * @param WP_Theme $theme Theme object.
    422  * @return array Block pattern data.
    423  */
    424 function _wp_get_block_patterns( WP_Theme $theme ) {
    425         $can_use_cached = ! wp_is_development_mode( 'theme' );
    426 
    427         $pattern_data = $theme->get_pattern_cache();
    428         if ( is_array( $pattern_data ) ) {
    429                 if ( $can_use_cached ) {
    430                         return $pattern_data;
    431                 }
    432                 // If in development mode, clear pattern cache.
    433                 $theme->delete_pattern_cache();
    434         }
    435 
    436         $dirpath      = $theme->get_stylesheet_directory() . '/patterns/';
    437         $pattern_data = array();
    438 
    439         if ( ! file_exists( $dirpath ) ) {
    440                 if ( $can_use_cached ) {
    441                         $theme->set_pattern_cache( $pattern_data );
    442                 }
    443                 return $pattern_data;
    444         }
    445         $files = glob( $dirpath . '*.php' );
    446         if ( ! $files ) {
    447                 if ( $can_use_cached ) {
    448                         $theme->set_pattern_cache( $pattern_data );
    449                 }
    450                 return $pattern_data;
    451         }
    452 
    453         $default_headers = array(
    454                 'title'         => 'Title',
    455                 'slug'          => 'Slug',
    456                 'description'   => 'Description',
    457                 'viewportWidth' => 'Viewport Width',
    458                 'inserter'      => 'Inserter',
    459                 'categories'    => 'Categories',
    460                 'keywords'      => 'Keywords',
    461                 'blockTypes'    => 'Block Types',
    462                 'postTypes'     => 'Post Types',
    463                 'templateTypes' => 'Template Types',
    464         );
    465 
    466         $properties_to_parse = array(
    467                 'categories',
    468                 'keywords',
    469                 'blockTypes',
    470                 'postTypes',
    471                 'templateTypes',
    472         );
    473 
    474         foreach ( $files as $file ) {
    475                 $pattern = get_file_data( $file, $default_headers );
    476 
    477                 if ( empty( $pattern['slug'] ) ) {
    478                         _doing_it_wrong(
    479                                 __FUNCTION__,
    480                                 sprintf(
    481                                         /* translators: 1: file name. */
    482                                         __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
    483                                         $file
    484                                 ),
    485                                 '6.0.0'
    486                         );
    487                         continue;
    488                 }
    489 
    490                 if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
    491                         _doing_it_wrong(
    492                                 __FUNCTION__,
    493                                 sprintf(
    494                                         /* translators: 1: file name; 2: slug value found. */
    495                                         __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
    496                                         $file,
    497                                         $pattern['slug']
    498                                 ),
    499                                 '6.0.0'
    500                         );
    501                 }
    502 
    503                 // Title is a required property.
    504                 if ( ! $pattern['title'] ) {
    505                         _doing_it_wrong(
    506                                 __FUNCTION__,
    507                                 sprintf(
    508                                         /* translators: 1: file name. */
    509                                         __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
    510                                         $file
    511                                 ),
    512                                 '6.0.0'
    513                         );
    514                         continue;
    515                 }
    516 
    517                 // For properties of type array, parse data as comma-separated.
    518                 foreach ( $properties_to_parse as $property ) {
    519                         if ( ! empty( $pattern[ $property ] ) ) {
    520                                 $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
    521                         } else {
    522                                 unset( $pattern[ $property ] );
    523                         }
    524                 }
    525 
    526                 // Parse properties of type int.
    527                 $property = 'viewportWidth';
    528                 if ( ! empty( $pattern[ $property ] ) ) {
    529                         $pattern[ $property ] = (int) $pattern[ $property ];
    530                 } else {
    531                         unset( $pattern[ $property ] );
    532                 }
    533 
    534                 // Parse properties of type bool.
    535                 $property = 'inserter';
    536                 if ( ! empty( $pattern[ $property ] ) ) {
    537                         $pattern[ $property ] = in_array(
    538                                 strtolower( $pattern[ $property ] ),
    539                                 array( 'yes', 'true' ),
    540                                 true
    541                         );
    542                 } else {
    543                         unset( $pattern[ $property ] );
    544                 }
    545 
    546                 $key = str_replace( $dirpath, '', $file );
    547 
    548                 $pattern_data[ $key ] = $pattern;
    549         }
    550 
    551         if ( $can_use_cached ) {
    552                 $theme->set_pattern_cache( $pattern_data );
    553         }
    554 
    555         return $pattern_data;
    556 }
  • trunk/src/wp-includes/class-wp-theme.php

    r56943 r56978  
    846846
    847847        /**
    848          * Gets block pattern cache.
    849          *
    850          * @since 6.4.0
    851          *
    852          * @return array|false Returns an array of patterns if cache is found, otherwise false.
    853          */
    854         public function get_pattern_cache() {
    855                 if ( ! $this->exists() ) {
    856                         return false;
    857                 }
    858                 $pattern_data = get_transient( 'wp_theme_patterns_' . $this->stylesheet );
    859                 if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
    860                         return $pattern_data['patterns'];
    861                 }
    862                 return false;
    863         }
    864 
    865         /**
    866          * Sets block pattern cache.
    867          *
    868          * @since 6.4.0
    869          *
    870          * @param array $patterns Block patterns data to set in cache.
    871          */
    872         public function set_pattern_cache( array $patterns ) {
    873                 $pattern_data = array(
    874                         'version'  => $this->get( 'Version' ),
    875                         'patterns' => $patterns,
    876                 );
    877                 set_transient( 'wp_theme_patterns_' . $this->stylesheet, $pattern_data );
    878         }
    879 
    880         /**
    881          * Clears block pattern cache.
    882          *
    883          * @since 6.4.0
    884          */
    885         public function delete_pattern_cache() {
    886                 delete_transient( 'wp_theme_patterns_' . $this->stylesheet );
    887         }
    888 
    889         /**
    890848         * Gets a raw, unformatted theme header.
    891849         *
    … …  
    18421800
    18431801        /**
     1802         * Gets block pattern data for a specified theme.
     1803         * Each pattern is defined as a PHP file and defines
     1804         * its metadata using plugin-style headers. The minimum required definition is:
     1805         *
     1806         *     /**
     1807         *      * Title: My Pattern
     1808         *      * Slug: my-theme/my-pattern
     1809         *      *
     1810         *
     1811         * The output of the PHP source corresponds to the content of the pattern, e.g.:
     1812         *
     1813         *     <main><p><?php echo "Hello"; ?></p></main>
     1814         *
     1815         * If applicable, this will collect from both parent and child theme.
     1816         *
     1817         * Other settable fields include:
     1818         *
     1819         *     - Description
     1820         *     - Viewport Width
     1821         *     - Inserter         (yes/no)
     1822         *     - Categories       (comma-separated values)
     1823         *     - Keywords         (comma-separated values)
     1824         *     - Block Types      (comma-separated values)
     1825         *     - Post Types       (comma-separated values)
     1826         *     - Template Types   (comma-separated values)
     1827         *
     1828         * @since 6.4.0
     1829         *
     1830         * @return array Block pattern data.
     1831         */
     1832        public function get_block_patterns() {
     1833                $can_use_cached = ! wp_is_development_mode( 'theme' );
     1834
     1835                $pattern_data = $this->get_pattern_cache();
     1836                if ( is_array( $pattern_data ) ) {
     1837                        if ( $can_use_cached ) {
     1838                                return $pattern_data;
     1839                        }
     1840                        // If in development mode, clear pattern cache.
     1841                        $this->delete_pattern_cache();
     1842                }
     1843
     1844                $dirpath      = $this->get_stylesheet_directory() . '/patterns/';
     1845                $pattern_data = array();
     1846
     1847                if ( ! file_exists( $dirpath ) ) {
     1848                        if ( $can_use_cached ) {
     1849                                $this->set_pattern_cache( $pattern_data );
     1850                        }
     1851                        return $pattern_data;
     1852                }
     1853                $files = glob( $dirpath . '*.php' );
     1854                if ( ! $files ) {
     1855                        if ( $can_use_cached ) {
     1856                                $this->set_pattern_cache( $pattern_data );
     1857                        }
     1858                        return $pattern_data;
     1859                }
     1860
     1861                $default_headers = array(
     1862                        'title'         => 'Title',
     1863                        'slug'          => 'Slug',
     1864                        'description'   => 'Description',
     1865                        'viewportWidth' => 'Viewport Width',
     1866                        'inserter'      => 'Inserter',
     1867                        'categories'    => 'Categories',
     1868                        'keywords'      => 'Keywords',
     1869                        'blockTypes'    => 'Block Types',
     1870                        'postTypes'     => 'Post Types',
     1871                        'templateTypes' => 'Template Types',
     1872                );
     1873
     1874                $properties_to_parse = array(
     1875                        'categories',
     1876                        'keywords',
     1877                        'blockTypes',
     1878                        'postTypes',
     1879                        'templateTypes',
     1880                );
     1881
     1882                foreach ( $files as $file ) {
     1883                        $pattern = get_file_data( $file, $default_headers );
     1884
     1885                        if ( empty( $pattern['slug'] ) ) {
     1886                                _doing_it_wrong(
     1887                                        __FUNCTION__,
     1888                                        sprintf(
     1889                                                /* translators: 1: file name. */
     1890                                                __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
     1891                                                $file
     1892                                        ),
     1893                                        '6.0.0'
     1894                                );
     1895                                continue;
     1896                        }
     1897
     1898                        if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern['slug'] ) ) {
     1899                                _doing_it_wrong(
     1900                                        __FUNCTION__,
     1901                                        sprintf(
     1902                                                /* translators: 1: file name; 2: slug value found. */
     1903                                                __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
     1904                                                $file,
     1905                                                $pattern['slug']
     1906                                        ),
     1907                                        '6.0.0'
     1908                                );
     1909                        }
     1910
     1911                        // Title is a required property.
     1912                        if ( ! $pattern['title'] ) {
     1913                                _doing_it_wrong(
     1914                                        __FUNCTION__,
     1915                                        sprintf(
     1916                                                /* translators: 1: file name. */
     1917                                                __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
     1918                                                $file
     1919                                        ),
     1920                                        '6.0.0'
     1921                                );
     1922                                continue;
     1923                        }
     1924
     1925                        // For properties of type array, parse data as comma-separated.
     1926                        foreach ( $properties_to_parse as $property ) {
     1927                                if ( ! empty( $pattern[ $property ] ) ) {
     1928                                        $pattern[ $property ] = array_filter( wp_parse_list( (string) $pattern[ $property ] ) );
     1929                                } else {
     1930                                        unset( $pattern[ $property ] );
     1931                                }
     1932                        }
     1933
     1934                        // Parse properties of type int.
     1935                        $property = 'viewportWidth';
     1936                        if ( ! empty( $pattern[ $property ] ) ) {
     1937                                $pattern[ $property ] = (int) $pattern[ $property ];
     1938                        } else {
     1939                                unset( $pattern[ $property ] );
     1940                        }
     1941
     1942                        // Parse properties of type bool.
     1943                        $property = 'inserter';
     1944                        if ( ! empty( $pattern[ $property ] ) ) {
     1945                                $pattern[ $property ] = in_array(
     1946                                        strtolower( $pattern[ $property ] ),
     1947                                        array( 'yes', 'true' ),
     1948                                        true
     1949                                );
     1950                        } else {
     1951                                unset( $pattern[ $property ] );
     1952                        }
     1953
     1954                        $key = str_replace( $dirpath, '', $file );
     1955
     1956                        $pattern_data[ $key ] = $pattern;
     1957                }
     1958
     1959                if ( $can_use_cached ) {
     1960                        $this->set_pattern_cache( $pattern_data );
     1961                }
     1962
     1963                return $pattern_data;
     1964        }
     1965
     1966        /**
     1967         * Gets block pattern cache.
     1968         *
     1969         * @since 6.4.0
     1970         *
     1971         * @return array|false Returns an array of patterns if cache is found, otherwise false.
     1972         */
     1973        private function get_pattern_cache() {
     1974                if ( ! $this->exists() ) {
     1975                        return false;
     1976                }
     1977                $pattern_data = wp_cache_get( 'wp_theme_patterns_' . $this->stylesheet );
     1978                if ( is_array( $pattern_data ) && $pattern_data['version'] === $this->get( 'Version' ) ) {
     1979                        return $pattern_data['patterns'];
     1980                }
     1981                return false;
     1982        }
     1983
     1984        /**
     1985         * Sets block pattern cache.
     1986         *
     1987         * @since 6.4.0
     1988         *
     1989         * @param array $patterns Block patterns data to set in cache.
     1990         */
     1991        private function set_pattern_cache( array $patterns ) {
     1992                $pattern_data = array(
     1993                        'version'  => $this->get( 'Version' ),
     1994                        'patterns' => $patterns,
     1995                );
     1996                wp_cache_set( 'wp_theme_patterns_' . $this->stylesheet, $pattern_data );
     1997        }
     1998
     1999        /**
     2000         * Clears block pattern cache.
     2001         *
     2002         * @since 6.4.0
     2003         */
     2004        public function delete_pattern_cache() {
     2005                wp_cache_delete( 'wp_theme_patterns_' . $this->stylesheet );
     2006        }
     2007
     2008        /**
    18442009         * Enables a theme for all sites on the current network.
    18452010         *
  • trunk/tests/phpunit/tests/theme/wpGetGlobalStylesheet.php

    r56042 r56978  
    2727
    2828        public function tear_down() {
     29                // Reset development mode after each test.
     30                unset( $GLOBALS['_wp_tests_development_mode'] );
     31
    2932                // Reset the theme support.
    3033                if ( $this->remove_theme_support_at_teardown ) {
  • trunk/tests/phpunit/tests/theme/wpThemeGetBlockPatterns.php

    r56977 r56978  
    11<?php
    22/**
    3  * Tests for _wp_get_block_patterns.
     3 * Tests for WP_Theme::get_block_patterns.
    44 *
    55 * @package WordPress
    … …  
    88 *
    99 * @group blocks
     10 * @group themes
    1011 *
    11  * @covers ::_wp_get_block_patterns
     12 * @covers WP_Theme::get_block_patterns
    1213 */
    13 class Tests_Blocks_WpGetBlockPatterns extends WP_UnitTestCase {
     14class Tests_Theme_WPThemeGetBlockPatterns extends WP_UnitTestCase {
     15
     16        public static function wpSetUpBeforeClass() {
     17                // Ensure development mode is reset before running these tests.
     18                unset( $GLOBALS['_wp_tests_development_mode'] );
     19        }
     20
     21        public static function wpTearDownAfterClass() {
     22                // Ensure development mode is reset after running these tests.
     23                unset( $GLOBALS['_wp_tests_development_mode'] );
     24        }
     25
     26        /**
     27         * Test helper to access the private get_pattern_cache method of a theme.
     28         *
     29         * @param WP_Theme $wp_theme A WP_Theme object.
     30         * @return array|false Returns an array of patterns if cache is found, otherwise false.
     31         */
     32        private function get_pattern_cache( $wp_theme ) {
     33                $reflection = new ReflectionMethod( $wp_theme, 'get_pattern_cache' );
     34                $reflection->setAccessible( true );
     35
     36                $pattern_cache = $reflection->invoke( $wp_theme, 'get_pattern_cache' );
     37                $reflection->setAccessible( false );
     38
     39                return $pattern_cache;
     40        }
     41
    1442        /**
    1543         * @ticket 59490
    1644         *
    17          * @dataProvider data_wp_get_block_patterns
     45         * @dataProvider data_get_block_patterns
    1846         *
    19          * @param string $theme    The theme's slug.
    20          * @param array  $expected The expected pattern data.
     47         * @param string $theme_slug The theme's slug.
     48         * @param array  $expected   The expected pattern data.
    2149         */
    22         public function test_should_return_block_patterns( $theme, $expected ) {
    23                 $patterns = _wp_get_block_patterns( wp_get_theme( $theme ) );
     50        public function test_should_return_block_patterns( $theme_slug, $expected ) {
     51                $theme    = wp_get_theme( $theme_slug );
     52                $patterns = $theme->get_block_patterns();
    2453                $this->assertSameSets( $expected, $patterns );
    2554        }
    … …  
    2756        /**
    2857         * @ticket 59490
     58         *
     59         * @covers WP_Theme::delete_pattern_cache
    2960         */
    30         public function test_delete_theme_cache() {
     61        public function test_delete_pattern_cache() {
    3162                $theme = wp_get_theme( 'block-theme-patterns' );
    32                 _wp_get_block_patterns( $theme );
     63
     64                $this->assertTrue( $theme->exists(), 'The test theme could not be found.' );
     65
     66                $theme->get_block_patterns();
     67
    3368                $this->assertSameSets(
    3469                        array(
    … …  
    4075                                ),
    4176                        ),
    42                         $theme->get_pattern_cache(),
    43                         'The transient for block theme patterns should be set'
     77                        $this->get_pattern_cache( $theme ),
     78                        'The cache for block theme patterns should match the expected.'
    4479                );
    4580                $theme->delete_pattern_cache();
    4681                $this->assertFalse(
    47                         $theme->get_pattern_cache(),
    48                         'The transient for block theme patterns should have been cleared'
     82                        $this->get_pattern_cache( $theme ),
     83                        'The cache for block theme patterns should have been cleared.'
    4984                );
    5085        }
    … …  
    5388         * @ticket 59490
    5489         */
    55         public function test_should_clear_transient_after_switching_theme() {
     90        public function test_should_clear_cache_after_switching_theme() {
    5691                switch_theme( 'block-theme' );
    5792                $theme1 = wp_get_theme();
    58                 _wp_get_block_patterns( $theme1 );
     93
     94                $this->assertTrue( $theme1->exists(), 'The block-theme test theme could not be found.' );
     95
     96                $theme1->get_block_patterns();
    5997                $this->assertSameSets(
    6098                        array(),
    61                         $theme1->get_pattern_cache(),
    62                         'The transient for block theme should be set'
     99                        $this->get_pattern_cache( $theme1 ),
     100                        'The cache for block theme should be empty.'
    63101                );
     102
    64103                switch_theme( 'block-theme-patterns' );
    65                 $this->assertFalse( $theme1->get_pattern_cache(), 'Transient should not be set for block theme after switch theme' );
     104
    66105                $theme2 = wp_get_theme();
    67                 $this->assertFalse( $theme2->get_pattern_cache(), 'Transient should not be set for block theme patterns before being requested' );
    68                 _wp_get_block_patterns( $theme2 );
     106                $this->assertTrue( $theme2->exists(), 'The block-theme-patterns test theme could not be found.' );
     107
     108                $this->assertFalse( $this->get_pattern_cache( $theme1 ), 'Cache should not be set for block theme after switch theme.' );
     109                $this->assertFalse( $this->get_pattern_cache( $theme2 ), 'Cache should not be set for block theme patterns before being requested.' );
     110
     111                $theme2->get_block_patterns( $theme2 );
    69112                $this->assertSameSets(
    70113                        array(
    … …  
    77120
    78121                        ),
    79                         $theme2->get_pattern_cache(),
    80                         'The transient for block theme patterns should be set'
     122                        $this->get_pattern_cache( $theme2 ),
     123                        'The cache for block theme patterns should match the expected.'
    81124                );
    82125        }
    … …  
    87130         * @return array[]
    88131         */
    89         public function data_wp_get_block_patterns() {
     132        public function data_get_block_patterns() {
    90133                return array(
    91134                        array(
    … …  
    120163
    121164        /**
    122          * Tests that _wp_get_block_patterns() clears existing transient when in theme development mode.
     165         * Tests that WP_Theme::get_block_patterns() clears existing cache when in theme development mode.
    123166         *
    124167         * @ticket 59591
    125168         */
    126         public function test_should_clear_existing_transient_when_in_development_mode() {
     169        public function test_should_clear_existing_cache_when_in_development_mode() {
    127170                $theme = wp_get_theme( 'block-theme-patterns' );
    128171
     172                $this->assertTrue( $theme->exists(), 'The test theme could not be found.' );
     173
    129174                // Calling the function should set the cache.
    130                 _wp_get_block_patterns( $theme );
     175                $theme->get_block_patterns();
    131176                $this->assertSameSets(
    132177                        array(
    … …  
    138183                                ),
    139184                        ),
    140                         $theme->get_pattern_cache(),
    141                         'The transient for block theme patterns should be set'
     185                        $this->get_pattern_cache( $theme ),
     186                        'The cache for block theme patterns should be set.'
    142187                );
    143188
    144189                // Calling the function while in theme development mode should clear the cache.
    145190                $GLOBALS['_wp_tests_development_mode'] = 'theme';
    146                 _wp_get_block_patterns( $theme );
     191                $theme->get_block_patterns( $theme );
    147192                unset( $GLOBALS['_wp_tests_development_mode'] ); // Reset to not pollute other tests.
    148193                $this->assertFalse(
    149                         $theme->get_pattern_cache(),
    150                         'The transient for block theme patterns should have been cleared due to theme development mode'
     194                        $this->get_pattern_cache( $theme ),
     195                        'The cache for block theme patterns should have been cleared due to theme development mode.'
    151196                );
    152197        }
Note: See TracChangeset for help on using the changeset viewer.