-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
How the pieces fit, and why several of them are shaped the way they are.
wp-content/advanced-cache.php is copied verbatim from the plugin's dropins/ directory. Only one value is stamped into it: the version in its signature line. It is not generated.
That distinction matters. Until 1.0.1 the installer generated the drop-in with var_export(), baking absolute paths and a hard-coded runtime file list into it. Two consequences followed: the plugin wrote executable PHP at runtime, and a site whose paths changed — a migration, a restored backup — kept a drop-in pointing at a directory that no longer existed, and silently served uncached forever.
The bundled drop-in resolves everything at runtime instead:
$cacheRoot = WP_CONTENT_DIR . '/cache/gt-performance';
$configFile = $cacheRoot . '/config.json.php';
// read config → get plugin_dir → require the runtime classes → serveIt loads exactly six files and calls DropinRuntime::serve(). If the config is missing, unreadable, or names a directory that no longer exists, it returns and the request falls through to WordPress. A missing drop-in means no cache — never a broken site.
config.json.php and redis-config.json.php are JSON behind a fixed guard line:
<?php exit; /* GT Performance data file. Not executable configuration. */ ?>
{"generation":1,"cache":{…},"plugin_dir":"…"}
They are read with file_get_contents() and json_decode() and are never included or executed.
Two questions this usually raises:
Why keep a .php extension on a JSON file? Because redis-config.json.php contains a host, username and password. The guard means a direct web request is terminated by the PHP interpreter itself, on any server, including one that ignores .htaccess. A plain .json file would be readable by anyone who guessed the path on nginx.
Why is the PHP tag closed? PHP parses an entire file before executing any of it. With the tag left open, the JSON payload would be parsed as PHP and raise a syntax error instead of letting exit terminate the request cleanly.
Page metadata (<hash>.meta.json) is plain JSON — it holds only timestamps, a URL and a generation counter, and sits beside the cached .html in an already-denied directory.
RequestContext is built twice per uncached request: once by the drop-in before WordPress exists, once by PageCacheModule after it does. Both go through the same static helpers on RequestContext.
If those two ever disagree, one of two things happens:
- Keys diverge → every page misses forever, and the cache appears to be "not working" with no error anywhere.
- Bypass decisions diverge → the drop-in serves a cached page to a request WordPress would have bypassed. That is the serious one.
WordPress adds slashes to superglobals in wp_magic_quotes(), which runs after advanced-cache.php. So the WordPress-side builder unslashes and the drop-in does not. A parity test asserts both paths produce identical keys and identical bypass decisions across quoted URLs, control characters, null bytes and header-injection attempts.
Every buffer the plugin opens is registered with an explicit close on shutdown at priority 0 — ahead of core's own wp_ob_end_flush_all() at priority 1. A page cache has to hold its buffer open across the whole template render, so it cannot close it in the function that opened it; registering the close immediately is the next best thing, and it means the plugin owns its buffers rather than relying on core or on PHP's implicit end-of-request flush.
Buffers close innermost-first, so a CDN rewrite wrapping a page-cache capture still sees the inner result.
Everything the plugin declares globally uses GTPERF_ (constants) or gtperf_ (transients, AJAX actions, cron schedules, the shortcode, the Redis key prefix). Options and hooks use the longer gt_performance_ form.
Sites installed before 1.0.1 used GTP_/gtp_. There is no compatibility shim: constants in wp-config.php must be renamed. See Upgrading.
Preload, revalidation and CSS regeneration all run through a database-backed job queue rather than inside a visitor's request. Jobs are claimed with a lock token so two workers cannot process the same job, and terminal jobs are pruned on a schedule.
This is why a stale page is served instantly: the rebuild is queued, not awaited.
src/
├── Cache/ page cache, drop-in, eligibility, keys, purge
├── CDN/ origin-pull URL rewriting
├── Cloudflare/ API client, rule compiler, diagnostics
├── XCloud/ host integration and edge ownership
├── Commerce/ FluentCart / EDD / WooCommerce adapters, Safety Lab
├── Optimization/ CSS, JS, media, fonts, embeds
├── PrivateFragments/
├── Redis/ object-cache drop-in and configuration
├── Database/ scheduled cleanup
├── Queue/ job store and runner
├── Fleet/ signed settings bundles
├── Diagnostics/ Explain This Page, purge receipts, cron health
├── Compatibility/ plugin detection and automatic protection
├── Admin/ settings screens and admin bar
├── CLI/ WP-CLI commands
└── Core/ settings, paths, logging, ciphers, bootstrap
Every module implements one interface with a single register() method, and Plugin::boot() registers them in a fixed order.
GT Performance · GPL-2.0-or-later · Issues and pull requests welcome
Getting started
Caching
Optimization
Delivery
Operations
Reference