Skip to content

REST API: Avoid fetching each template again to build its links - #14849

Open
mpeshev wants to merge 5 commits into
WordPress:trunkfrom
mpeshev:perf-templates-prepare-links
Open

mpeshev wants to merge 5 commits into
WordPress:trunkfrom
mpeshev:perf-templates-prepare-links

Conversation

@mpeshev

@mpeshev mpeshev commented Oct 25, 2026 •

Copy link
Copy Markdown

WP_REST_Templates_Controller::prepare_links() receives only the template ID. When the post type supports revisions, it called get_block_template() for every item only to read wp_id for the revision links (r56819): another WP_Query per item, and for theme file templates another file read and Block Hooks pass, all discarded.

This reuses the template that prepare_item_for_response() is already preparing, handed over through a private property so the prepare_links( $id ) signature stays the same for subclasses.

Measured on 7.2-beta1 with Ipsum, as admin, rest_do_request(), median of 5:

Endpoint Queries Time
/wp/v2/template-parts (8 items) 9 to 1 12.7 to 6.5 ms
/wp/v2/templates (11 items) 17 to 6 23.3 to 17.2 ms

Trac ticket: https://core.trac.wordpress.org/ticket/66381

Testing

  • vendor/bin/phpunit --filter test_get_items_should_not_refetch_each_template_for_links fails without the patch and passes with it; a second test checks that the revision links are unchanged.
  • vendor/bin/phpunit --filter Tests_REST_WpRestTemplatesController (52 tests) and --group block-templates (130 tests) pass.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: finding the issue, drafting the fix and tests, and measuring. I reviewed every line.


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

🤖 Generated with Claude Code

`WP_REST_Templates_Controller::prepare_links()` receives only the template ID. When the post type supports revisions, it called `get_block_template()` for every item to read the template's post ID. For each template in `/wp/v2/templates` and `/wp/v2/template-parts` responses, that ran another `WP_Query` and, for theme file templates, read the file again and re-applied Block Hooks, only to discard the result.

Reuse the template that `prepare_item_for_response()` is already preparing. The signature of `prepare_links()` is unchanged, so subclasses that override it keep working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

See #66381.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mpeshev
mpeshev marked this pull request as ready for review October 25, 2026 18:05
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs.

Core Committers: Use this line as a base for the props when committing in SVN:

Props nofearinc, westonruter.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php Outdated
Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php Outdated
…p fallback runs.

Use a typed `?WP_Block_Template` property instead of an `@var` tag, as suggested in review, and say that the `get_block_template()` fallback in `prepare_links()` is only reached when a subclass calls `prepare_links()` outside of `prepare_item_for_response()`.

See #66381.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment on lines +1261 to +1271
$refetched = 0;
$counter = static function ( $template ) use ( &$refetched ) {
++$refetched;
return $template;
};
add_filter( 'pre_get_block_template', $counter );
$response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', '/wp/v2/templates' ) );
remove_filter( 'pre_get_block_template', $counter );

$this->assertSame( 200, $response->get_status(), 'The request should succeed.' );
$this->assertSame( 0, $refetched, 'Templates should not be fetched again to prepare their links.' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can be simplified using MockAction:

Suggested change
$refetched = 0;
$counter = static function ( $template ) use ( &$refetched ) {
++$refetched;
return $template;
};
add_filter( 'pre_get_block_template', $counter );
$response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', '/wp/v2/templates' ) );
remove_filter( 'pre_get_block_template', $counter );
$this->assertSame( 200, $response->get_status(), 'The request should succeed.' );
$this->assertSame( 0, $refetched, 'Templates should not be fetched again to prepare their links.' );
$mock = new MockAction();
add_filter( 'pre_get_block_template', array( $mock, 'filter' ) );
$response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', '/wp/v2/templates' ) );
$this->assertSame( 200, $response->get_status(), 'The request should succeed.' );
$this->assertSame( 0, $mock->get_call_count(), 'Templates should not be fetched again to prepare their links.' );

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied in 44f2c6b, thanks.

Comment on lines +1284 to +1289
wp_update_post(
array(
'ID' => self::$template_post->ID,
'post_content' => 'Content for a revision.',
)
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just to make sure the update actually happened:

Suggested change
wp_update_post(
array(
'ID' => self::$template_post->ID,
'post_content' => 'Content for a revision.',
)
);
$this->assertIsInt(
wp_update_post(
array(
'ID' => self::$template_post->ID,
'post_content' => 'Content for a revision.',
)
),
true
);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Applied in 44f2c6b, with true moved inside wp_update_post() as its $wp_error argument. As the second argument of assertIsInt() it would be the failure message, and a failed update returns 0, which would still pass.

Comment thread tests/phpunit/tests/rest-api/wpRestTemplatesController.php Outdated
Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php Outdated
mpeshev and others added 2 commits October 26, 2026 00:49
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Count `pre_get_block_template` calls with `MockAction`, assert that the template update succeeds before checking its revision links, and read the found template with `array_first()`.

The update assertion passes `true` as the `$wp_error` argument of `wp_update_post()`, so a failed update returns a `WP_Error` and fails `assertIsInt()` instead of returning 0.

See #66381.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants