Themes: Improve performance of get_block_theme_folders function

This commit enhances the performance of the get_block_theme_folders function by introducing a new method called get_block_template_folders within the WP_Theme class. Previously, this function suffered from poor performance due to repeated file lookups using file_exists. The new method implements basic caching, storing the result in the theme's cache, similar to how block themes are cached in the block_theme property (see [55236]).

Additionally, this change improves error handling by checking if a theme exists before attempting to look up the file. It also enhances test coverage. 

Props spacedmonkey, thekt12, swissspidy, flixos90, costdev, mukesh27.
Fixes #58319.

git-svn-id: https://develop.svn.wordpress.org/trunk@56621 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Jonny Harris 2023-09-19 16:15:52 +00:00
parent 48b9b6cbab
commit 9a734f3751
8 changed files with 284 additions and 67 deletions

View File

@ -37,21 +37,15 @@ if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) {
* }
*/
function get_block_theme_folders( $theme_stylesheet = null ) {
$theme_name = null === $theme_stylesheet ? get_stylesheet() : $theme_stylesheet;
$root_dir = get_theme_root( $theme_name );
$theme_dir = "$root_dir/$theme_name";
if ( file_exists( $theme_dir . '/block-templates' ) || file_exists( $theme_dir . '/block-template-parts' ) ) {
$theme = wp_get_theme( (string) $theme_stylesheet );
if ( ! $theme->exists() ) {
// Return the default folders if the theme doesn't exist.
return array(
'wp_template' => 'block-templates',
'wp_template_part' => 'block-template-parts',
'wp_template' => 'templates',
'wp_template_part' => 'parts',
);
}
return array(
'wp_template' => 'templates',
'wp_template_part' => 'parts',
);
return $theme->get_block_template_folders();
}
/**

View File

@ -194,6 +194,25 @@ final class WP_Theme implements ArrayAccess {
*/
private $cache_hash;
/**
* Block template folders.
*
* @since 6.4.0
* @var string[]
*/
private $block_template_folders;
/**
* Default values for template folders.
*
* @since 6.4.0
* @var string[]
*/
private $default_template_folders = array(
'wp_template' => 'templates',
'wp_template_part' => 'parts',
);
/**
* Flag for whether the themes cache bucket should be persistently cached.
*
@ -262,7 +281,7 @@ final class WP_Theme implements ArrayAccess {
$cache = $this->cache_get( 'theme' );
if ( is_array( $cache ) ) {
foreach ( array( 'block_theme', 'errors', 'headers', 'template' ) as $key ) {
foreach ( array( 'block_template_folders', 'block_theme', 'errors', 'headers', 'template' ) as $key ) {
if ( isset( $cache[ $key ] ) ) {
$this->$key = $cache[ $key ];
}
@ -287,16 +306,18 @@ final class WP_Theme implements ArrayAccess {
} else {
$this->errors = new WP_Error( 'theme_no_stylesheet', __( 'Stylesheet is missing.' ) );
}
$this->template = $this->stylesheet;
$this->block_theme = false;
$this->template = $this->stylesheet;
$this->block_theme = false;
$this->block_template_folders = $this->default_template_folders;
$this->cache_add(
'theme',
array(
'block_theme' => $this->block_theme,
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
'block_template_folders' => $this->block_template_folders,
'block_theme' => $this->block_theme,
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
)
);
if ( ! file_exists( $this->theme_root ) ) { // Don't cache this one.
@ -304,18 +325,20 @@ final class WP_Theme implements ArrayAccess {
}
return;
} elseif ( ! is_readable( $this->theme_root . '/' . $theme_file ) ) {
$this->headers['Name'] = $this->stylesheet;
$this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
$this->template = $this->stylesheet;
$this->block_theme = false;
$this->headers['Name'] = $this->stylesheet;
$this->errors = new WP_Error( 'theme_stylesheet_not_readable', __( 'Stylesheet is not readable.' ) );
$this->template = $this->stylesheet;
$this->block_theme = false;
$this->block_template_folders = $this->default_template_folders;
$this->cache_add(
'theme',
array(
'block_theme' => $this->block_theme,
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
'block_template_folders' => $this->block_template_folders,
'block_theme' => $this->block_theme,
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
)
);
return;
@ -345,10 +368,11 @@ final class WP_Theme implements ArrayAccess {
$this->cache_add(
'theme',
array(
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'block_template_folders' => $this->get_block_template_folders(),
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
)
);
@ -378,11 +402,12 @@ final class WP_Theme implements ArrayAccess {
$this->cache_add(
'theme',
array(
'block_theme' => $this->block_theme,
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
'block_template_folders' => $this->get_block_template_folders(),
'block_theme' => $this->block_theme,
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
)
);
return;
@ -419,11 +444,12 @@ final class WP_Theme implements ArrayAccess {
$this->cache_add(
'theme',
array(
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
'block_template_folders' => $this->get_block_template_folders(),
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
)
);
$this->parent = new WP_Theme( $this->template, $this->theme_root, $this );
@ -447,11 +473,12 @@ final class WP_Theme implements ArrayAccess {
$_child->cache_add(
'theme',
array(
'block_theme' => $_child->is_block_theme(),
'headers' => $_child->headers,
'errors' => $_child->errors,
'stylesheet' => $_child->stylesheet,
'template' => $_child->template,
'block_template_folders' => $_child->get_block_template_folders(),
'block_theme' => $_child->is_block_theme(),
'headers' => $_child->headers,
'errors' => $_child->errors,
'stylesheet' => $_child->stylesheet,
'template' => $_child->template,
)
);
// The two themes actually reference each other with the Template header.
@ -467,11 +494,12 @@ final class WP_Theme implements ArrayAccess {
$this->cache_add(
'theme',
array(
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
'block_template_folders' => $this->get_block_template_folders(),
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
)
);
}
@ -488,11 +516,12 @@ final class WP_Theme implements ArrayAccess {
// We're good. If we didn't retrieve from cache, set it.
if ( ! is_array( $cache ) ) {
$cache = array(
'block_theme' => $this->is_block_theme(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
'block_theme' => $this->is_block_theme(),
'block_template_folders' => $this->get_block_template_folders(),
'headers' => $this->headers,
'errors' => $this->errors,
'stylesheet' => $this->stylesheet,
'template' => $this->template,
);
// If the parent theme is in another root, we'll want to cache this. Avoids an entire branch of filesystem calls above.
if ( isset( $theme_root_template ) ) {
@ -779,15 +808,16 @@ final class WP_Theme implements ArrayAccess {
foreach ( array( 'theme', 'screenshot', 'headers', 'post_templates' ) as $key ) {
wp_cache_delete( $key . '-' . $this->cache_hash, 'themes' );
}
$this->template = null;
$this->textdomain_loaded = null;
$this->theme_root_uri = null;
$this->parent = null;
$this->errors = null;
$this->headers_sanitized = null;
$this->name_translated = null;
$this->block_theme = null;
$this->headers = array();
$this->template = null;
$this->textdomain_loaded = null;
$this->theme_root_uri = null;
$this->parent = null;
$this->errors = null;
$this->headers_sanitized = null;
$this->name_translated = null;
$this->block_theme = null;
$this->block_template_folders = null;
$this->headers = array();
$this->__construct( $this->stylesheet, $this->theme_root );
}
@ -1714,6 +1744,37 @@ final class WP_Theme implements ArrayAccess {
return (array) apply_filters( 'site_allowed_themes', $allowed_themes[ $blog_id ], $blog_id );
}
/**
* Returns the folder names of the block template directories.
*
* @since 6.4.0
*
* @return string[] {
* Folder names used by block themes.
*
* @type string $wp_template Theme-relative directory name for block templates.
* @type string $wp_template_part Theme-relative directory name for block template parts.
* }
*/
public function get_block_template_folders() {
// Return set/cached value if available.
if ( isset( $this->block_template_folders ) ) {
return $this->block_template_folders;
}
$this->block_template_folders = $this->default_template_folders;
$stylesheet_directory = $this->get_stylesheet_directory();
// If the theme uses deprecated block template folders.
if ( file_exists( $stylesheet_directory . '/block-templates' ) || file_exists( $stylesheet_directory . '/block-template-parts' ) ) {
$this->block_template_folders = array(
'wp_template' => 'block-templates',
'wp_template_part' => 'block-template-parts',
);
}
return $this->block_template_folders;
}
/**
* Enables a theme for all sites on the current network.
*

View File

@ -0,0 +1,3 @@
<!-- wp:paragraph -->
<p>Small Header Template Part</p>
<!-- /wp:paragraph -->

View File

@ -0,0 +1,8 @@
/*
Theme Name: Block Theme Child Deprecated Path
Theme URI: https://wordpress.org/
Description: For testing purposes only.
Template: block-theme
Version: 1.0.0
Text Domain: block-theme-child-deprecated-path
*/

View File

@ -0,0 +1,3 @@
<!-- wp:paragraph -->
<p>Index Template</p>
<!-- /wp:paragraph -->

View File

@ -0,0 +1,71 @@
{
"version": 1,
"settings": {
"color": {
"palette": [
{
"slug": "light",
"name": "Light",
"color": "#f5f7f9"
},
{
"slug": "dark",
"name": "Dark",
"color": "#000"
}
],
"gradients": [
{
"name": "Custom gradient",
"gradient": "linear-gradient(135deg,rgba(0,0,0) 0%,rgb(0,0,0) 100%)",
"slug": "custom-gradient"
}
],
"custom": false,
"customGradient": false
},
"typography": {
"fontSizes": [
{
"name": "Custom",
"slug": "custom",
"size": "100px"
}
],
"customFontSize": false,
"customLineHeight": true
},
"spacing": {
"units": [
"rem"
],
"customPadding": true
},
"blocks": {
"core/paragraph": {
"color": {
"palette": [
{
"slug": "light",
"name": "Light",
"color": "#f5f7f9"
}
]
}
}
}
},
"customTemplates": [
{
"name": "page-home",
"title": "Homepage template"
}
],
"templateParts": [
{
"name": "small-header",
"title": "Small Header",
"area": "header"
}
]
}

View File

@ -275,6 +275,82 @@ class Tests_Block_Template extends WP_UnitTestCase {
$this->assertSame( array( false, true ), $in_the_loop_logs, 'Main query loop was triggered incorrectly' );
}
/**
* @ticket 58319
*
* @covers ::get_block_theme_folders
*
* @dataProvider data_get_block_theme_folders
*
* @param string $theme The theme's stylesheet.
* @param string[] $expected The expected associative array of block theme folders.
*/
public function test_get_block_theme_folders( $theme, $expected ) {
$wp_theme = wp_get_theme( $theme );
$wp_theme->cache_delete(); // Clear cache.
$this->assertSame( $expected, get_block_theme_folders( $theme ), 'Incorrect block theme folders were retrieved.' );
$reflection = new ReflectionMethod( $wp_theme, 'cache_get' );
$reflection->setAccessible( true );
$theme_cache = $reflection->invoke( $wp_theme, 'theme' );
$cached_value = $theme_cache['block_template_folders'];
$reflection->setAccessible( false );
$this->assertSame( $expected, $cached_value, 'The cached value is incorrect.' );
}
/**
* Data provider.
*
* @return array[]
*/
public function data_get_block_theme_folders() {
return array(
'block-theme' => array(
'block-theme',
array(
'wp_template' => 'templates',
'wp_template_part' => 'parts',
),
),
'block-theme-deprecated-path' => array(
'block-theme-deprecated-path',
array(
'wp_template' => 'block-templates',
'wp_template_part' => 'block-template-parts',
),
),
'block-theme-child' => array(
'block-theme-child',
array(
'wp_template' => 'templates',
'wp_template_part' => 'parts',
),
),
'block-theme-child-deprecated-path' => array(
'block-theme-child-deprecated-path',
array(
'wp_template' => 'block-templates',
'wp_template_part' => 'block-template-parts',
),
),
'this-is-an-invalid-theme' => array(
'this-is-an-invalid-theme',
array(
'wp_template' => 'templates',
'wp_template_part' => 'parts',
),
),
'null' => array(
null,
array(
'wp_template' => 'templates',
'wp_template_part' => 'parts',
),
),
);
}
/**
* Registers a test block to log `in_the_loop()` results.
*

View File

@ -176,6 +176,7 @@ class Tests_Theme_ThemeDir extends WP_UnitTestCase {
'REST Theme',
'Block Theme',
'Block Theme Child Theme',
'Block Theme Child Deprecated Path',
'Block Theme Child with no theme.json',
'Block Theme Child Theme With Fluid Layout',
'Block Theme Child Theme With Fluid Typography',