Make WordPress Core

Opened 8 years ago

Last modified 10 days ago

#42513 reviewing defect (bug)

WP_Theme::get_post_templates() is extremely inefficient for large themes

Reported by: gschoppe's profile gschoppe Owned by: sergeybiryukov's profile SergeyBiryukov
Milestone: 7.1 Priority: normal
Severity: normal Version: 4.8.3
Component: Themes Keywords: good-first-bug has-patch has-unit-tests
Focuses: performance Cc:

Description

WP_Theme::get_post_templates() uses file_get_contents() to read the complete contents of each theme php file in the root of a theme into memory, twice, and processes the result with a custom Regular Expression.

As a result, significantly more data than necessary is processed when loading large themes.

In addition, because the standard get_file_data() function is not called (as it is with all other header comments), the "extra_{$context}_headers" filter is never called, which is inconsistent behavior with other file_header related operations, such as the WP_Theme constructor.

Attachments (1)

refactor_class_wp_theme_get_post_templates.patch (2.4 KB) - added by gschoppe 8 years ago.
Patch refactoring WP_Theme::get_post_templates() to use get_file_data

Download all attachments as: .zip

Change History (11)

@gschoppe
8 years ago

Patch refactoring WP_Theme::get_post_templates() to use get_file_data

#1 @gschoppe
8 years ago

To make it easier to review the issue without having to create a server and simulate high load, I've created a pathological theme that, when activated, should demonstrate the increased loading time. The file size was too large to upload directly to this ticket, so I have hosted it on google drive:
https://drive.google.com/open?id=1t0S_yErQXQJZGlgDBu_Uo9UP3xIJvVJb

The load time should be significant.

#2 @birgire
8 years ago

  • Keywords has-patch needs-unit-tests added

@gschoppe Welcome to WordPress trac.

This seems like a good catch.

This will probably need a unit test as well. There's one already for the get_post_templates() method in Tests_Admin_includesTheme::test_get_post_templates_child_theme() so maybe it would cover this as well, instead of a new one?

I think I've spotted another part that could benefit from this, namely the get_file_description() function, that contains:

$template_data = implode( '', file( $file_path ) );

where file() reads the entire file into an array.

The theme editor uses (4.9+) wp_print_theme_file_tree() recursively and therefore we have multiple get_file_description() function calls.

So with your suggestion of using get_file_data() it seems that we could adjust get_file_description() as well to be more efficient. Best create a new ticket for that, if this is the way to go here.

#3 @gschoppe
8 years ago

In the process of writing unit tests, I discovered that get_file data() seems to not support single line file header declarations, such as

<?php // Template Name: Test ?>

This may be why the code was not previously using get_file_data(). I am now looking into whether it is feasible to add support for single line headers to get_file_data() as this lack of support seems to be an oversight, and this format needs uniform syntax across all use cases.

#4 @gschoppe
8 years ago

I have opened a related ticket to refactor the regex used to parse file header comments, so that get_file_data will support the single line variant.

Related Ticket: #42517

This ticket was mentioned in Slack in #core-performance by swissspidy. View the logs.


15 months ago

#6 @swissspidy
15 months ago

  • Focuses administration removed
  • Keywords needs-patch good-first-bug added; has-patch needs-unit-tests removed
  • Milestone changed from Awaiting Review to Future Release

Not too worried about the performance impact here as the result is stored in cache.

Being able to use get_file_data() is blocked by #42517.
Calling file_get_contents() only once would be a trivial change that makes sense to do.

This ticket was mentioned in PR #8310 on WordPress/wordpress-develop by @sukhendu2002.


15 months ago
#7

  • Keywords has-patch added; needs-patch removed

This ticket was mentioned in PR #11545 on WordPress/wordpress-develop by @MythThrazz.


2 weeks ago
#8

  • Keywords has-unit-tests added

## Summary

  • Replace dual file_get_contents() calls with a single get_file_data() call, which reads only the first 8KB of each file instead of the entire contents
  • Move get_block_templates() query inside the cache block so the DB query only fires on a cache miss
  • Add unit tests covering caching behavior, get_file_data() integration, and cache invalidation on theme switch

## Details

WP_Theme::get_post_templates() previously called file_get_contents() twice per PHP file — once for Template Name and once for Template Post Type — reading the entire file each time. This replaces both calls with a single get_file_data() call that reads only the first 8KB (consistent with how WordPress parses plugin and theme headers elsewhere in core).

Additionally, get_block_templates() was called outside the cache block, executing a WP_Query with posts_per_page=-1 on every invocation regardless of cache state. Moving it inside the cache block eliminates redundant database queries on cache hits.

Related: https://core.trac.wordpress.org/ticket/42517

## Test plan

  • [x] Existing Tests_Admin_IncludesTheme tests pass (8/8, 55 assertions)
  • [x] PHPCS clean on both modified files
  • [x] Single-line template headers (<?php // Template Name: ... ?>) parsed correctly
  • [x] Multi-line docblock headers parsed correctly
  • [x] Template Post Type with multiple types parsed correctly
  • [x] Child theme template inheritance works correctly
  • [ ] Verify performance improvement on a theme with many PHP files

---

AI assistance: Yes
Tool(s): Claude Code (Anthropic)
Model(s): Claude Opus 4.6
Used for: Analysis of the performance bottleneck, implementation of the optimization, and writing unit tests. All code was reviewed and tested.

---

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

#9 @MythThrazz
2 weeks ago

New PR in #11545 replacing PR #8310 with a more comprehensive fix:

  • Replaces dual file_get_contents() with a single get_file_data() call (reads only first 8KB instead of the entire file)
  • Moves get_block_templates() inside the cache block to avoid a DB query on every call
  • Includes unit tests

The blocker from #42517 (single-line header support) appears to be resolved — get_file_data() regex already
handles <?php // Template Name: ... ?> format, confirmed by existing tests in
tests/phpunit/data/themedir1/page-templates/template-header.php.

#10 @SergeyBiryukov
10 days ago

  • Milestone changed from Future Release to 7.1
  • Owner set to SergeyBiryukov
  • Status changed from new to reviewing
Note: See TracTickets for help on using tickets.