Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve get_block_templates performance #4097

Closed
wants to merge 16 commits into from
Closed
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 78 additions & 36 deletions src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -290,20 +290,41 @@ function _get_block_template_file( $template_type, $slug ) {
* Retrieves the template files from the theme.
*
* @since 5.9.0
* @since 6.3.0 Added the `$query` parameter.
* @access private
*
* @param string $template_type 'wp_template' or 'wp_template_part'.
* @return array Template.
* @param array $query {
* Arguments to retrieve templates. Optional, empty by default.
*
* @type array $slug__in List of slugs to include.
* @type array $slug__not_in List of slugs to skip.
* @type string $area A 'wp_template_part_area' taxonomy value to filter by (for wp_template_part template type only).
* @type string $post_type Post type to get the templates for.
* }
*
* @return array Template
*/
function _get_block_templates_files( $template_type ) {
function _get_block_templates_files( $template_type, $query = array() ) {
if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
return null;
}

$themes = array(
get_stylesheet() => get_stylesheet_directory(),
get_template() => get_template_directory(),
// Prepare metadata from $query.
$slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
$slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array();
$area = isset( $query['area'] ) ? $query['area'] : null;
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';

$stylesheet = get_stylesheet();
$template = get_template();
$themes = array(
$stylesheet => get_stylesheet_directory(),
);
// Add the parent theme if it's not the same as the current theme.
if ( $stylesheet !== $template ) {
$themes[ $template ] = get_template_directory();
}
$template_files = array();
foreach ( $themes as $theme_slug => $theme_dir ) {
$template_base_paths = get_block_theme_folders( $theme_slug );
Expand All @@ -317,6 +338,17 @@ function _get_block_templates_files( $template_type ) {
// Subtract ending '.html'.
-5
);

// Skip this item if its slug doesn't match any of the slugs to include.
if ( ! empty( $slugs_to_include ) && ! in_array( $template_slug, $slugs_to_include, true ) ) {
continue;
}

// Skip this item if its slug matches any of the slugs to skip.
if ( ! empty( $slugs_to_skip ) && in_array( $template_slug, $slugs_to_skip, true ) ) {
continue;
}

$new_template_item = array(
'slug' => $template_slug,
'path' => $template_file,
Expand All @@ -325,11 +357,40 @@ function _get_block_templates_files( $template_type ) {
);

if ( 'wp_template_part' === $template_type ) {
$template_files[] = _add_block_template_part_area_info( $new_template_item );
/*
* Structure of a wp_template_part item:
*
* - slug
* - path
* - theme
* - type
* - area
* - title (optional)
*/
$candidate = _add_block_template_part_area_info( $new_template_item );
if ( ! isset( $area ) || ( isset( $area ) && $area === $candidate['area'] ) ) {
$template_files[] = $candidate;
}
}

if ( 'wp_template' === $template_type ) {
$template_files[] = _add_block_template_info( $new_template_item );
/*
* Structure of a wp_template item:
*
* - slug
* - path
* - theme
* - type
* - title (optional)
* - postTypes (optional)
*/
$candidate = _add_block_template_info( $new_template_item );
if (
! $post_type ||
( $post_type && isset( $candidate['postTypes'] ) && in_array( $post_type, $candidate['postTypes'], true ) )
) {
$template_files[] = $candidate;
}
}
}
}
Expand Down Expand Up @@ -921,8 +982,9 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' )
$wp_query_args['tax_query']['relation'] = 'AND';
}

if ( isset( $query['slug__in'] ) ) {
$wp_query_args['post_name__in'] = $query['slug__in'];
if ( ! empty( $query['slug__in'] ) ) {
$wp_query_args['post_name__in'] = $query['slug__in'];
oandregal marked this conversation as resolved.
Show resolved Hide resolved
$wp_query_args['posts_per_page'] = count( array_unique( $query['slug__in'] ) );
}

// This is only needed for the regular templates/template parts post type listing and editor.
Expand Down Expand Up @@ -957,34 +1019,14 @@ function get_block_templates( $query = array(), $template_type = 'wp_template' )
}
oandregal marked this conversation as resolved.
Show resolved Hide resolved

if ( ! isset( $query['wp_id'] ) ) {
$template_files = _get_block_templates_files( $template_type );
/*
* If the query has found some use templates, those have priority
* over the theme-provided ones, so we skip querying and building them.
*/
$query['slug__not_in'] = wp_list_pluck( $query_result, 'slug' );
$template_files = _get_block_templates_files( $template_type, $query );
foreach ( $template_files as $template_file ) {
$template = _build_block_template_result_from_file( $template_file, $template_type );

if ( $post_type && ! $template->is_custom ) {
continue;
}

if ( $post_type &&
isset( $template->post_types ) &&
! in_array( $post_type, $template->post_types, true )
) {
continue;
}

$is_not_custom = false === array_search(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic checks for both templates coming from the user ("custom" as it was defined here) and previously inserted templates (for example, it checks that a parent template is not added if the child already defined it). This wasn't properly ported and caused an issue.

get_stylesheet() . '//' . $template_file['slug'],
wp_list_pluck( $query_result, 'id' ),
true
);
$fits_slug_query =
! isset( $query['slug__in'] ) || in_array( $template_file['slug'], $query['slug__in'], true );
$fits_area_query =
! isset( $query['area'] ) || $template_file['area'] === $query['area'];
$should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
if ( $should_include ) {
$query_result[] = $template;
}
$query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
Copy link
Member Author

@oandregal oandregal Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key change of this PR: instead of building the template object for all items and filter them after, we filter before this step, so we have to build less items. We can do this because all the data used to filter the items is already available in the step before.

Copy link
Member Author

@oandregal oandregal Feb 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The impact of this change can be seen in the XDebug profiler by looking at the number of times the _build_block_template_result_from_file function is called:

Before (22 times) After (5 times)
image image

}
}

Expand Down