<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[55687] trunk/src/wp-includes/block-template-utils.php: Themes: improve performance of `get_block_templates()`.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/55687">55687</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/55687","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>oandregal</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-04-26 14:38:43 +0000 (Wed, 26 Apr 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Themes: improve performance of `get_block_templates()`.

`get_block_templates()` is responsible for finding block templates that match a given search. The function receives a query parameter with the relevant metadata (slugs of the templates, areas of the template parts, etc) to find the user templates (database) and theme templates (file directory).

This function can be made more performant by changing how it works. Before this change, it processed all the block templates and discarded the ones that didn't match the query after it occurred. This commit makes it so it discards the templates that don't match the query before processing them. As a result, it only has to process the subset of templates that will be used, instead of all of them.

This change impacts any theme with block templates. TwentyTwentyThree reports a 15% improvement in Time To First Byte.

Props spacedmonkey, jorgefilipecosta, youknowriad, flixos90, mukesh27.
Fixes <a href="https://core.trac.wordpress.org/ticket/57756">#57756</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesblocktemplateutilsphp">trunk/src/wp-includes/block-template-utils.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesblocktemplateutilsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/block-template-utils.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/block-template-utils.php    2023-04-26 14:25:19 UTC (rev 55686)
+++ trunk/src/wp-includes/block-template-utils.php      2023-04-26 14:38:43 UTC (rev 55687)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -290,20 +290,41 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * Retrieves the template files from the theme.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.9.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.3.0 Added the `$query` parameter.
</ins><span class="cx" style="display: block; padding: 0 10px">  * @access private
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param string $template_type 'wp_template' or 'wp_template_part'.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * @return array Template.
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @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
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function _get_block_templates_files( $template_type ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function _get_block_templates_files( $template_type, $query = array() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return null;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        $themes         = array(
-               get_stylesheet() => get_stylesheet_directory(),
-               get_template()   => get_template_directory(),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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(),
</ins><span class="cx" style="display: block; padding: 0 10px">         );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        // Add the parent theme if it's not the same as the current theme.
+       if ( $stylesheet !== $template ) {
+               $themes[ $template ] = get_template_directory();
+       }
</ins><span class="cx" style="display: block; padding: 0 10px">         $template_files = array();
</span><span class="cx" style="display: block; padding: 0 10px">        foreach ( $themes as $theme_slug => $theme_dir ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $template_base_paths  = get_block_theme_folders( $theme_slug );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -317,6 +338,17 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                // Subtract ending '.html'.
</span><span class="cx" style="display: block; padding: 0 10px">                                -5
</span><span class="cx" style="display: block; padding: 0 10px">                        );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+                       // 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;
+                       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         $new_template_item = array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'slug'  => $template_slug,
</span><span class="cx" style="display: block; padding: 0 10px">                                'path'  => $template_file,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -325,11 +357,40 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( 'wp_template_part' === $template_type ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $template_files[] = _add_block_template_part_area_info( $new_template_item );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         /*
+                                * 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;
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                        if ( 'wp_template' === $template_type ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                $template_files[] = _add_block_template_info( $new_template_item );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         /*
+                                * 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;
+                               }
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -921,8 +982,9 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $wp_query_args['tax_query']['relation'] = 'AND';
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        if ( isset( $query['slug__in'] ) ) {
-               $wp_query_args['post_name__in'] = $query['slug__in'];
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( ! empty( $query['slug__in'] ) ) {
+               $wp_query_args['post_name__in']  = $query['slug__in'];
+               $wp_query_args['posts_per_page'] = count( array_unique( $query['slug__in'] ) );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        // This is only needed for the regular templates/template parts post type listing and editor.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -957,34 +1019,14 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! isset( $query['wp_id'] ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $template_files = _get_block_templates_files( $template_type );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         /*
+                * 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 );
</ins><span class="cx" style="display: block; padding: 0 10px">                 foreach ( $template_files as $template_file ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        $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(
-                               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;
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $query_result[] = _build_block_template_result_from_file( $template_file, $template_type );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre>
</div>
</div>

</body>
</html>