diff --git a/.gitignore b/.gitignore index 106a68b..675fea7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ vendor/ coverage/ .phpunit.result.cache + +# Generated class-loader cache (a build artefact, not source) +class-loader-cache/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9040d..23dc70e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file, per [the Keep a Changelog standard](http://keepachangelog.com/) and will adhere to [Semantic Versioning](http://semver.org/). ## [Unreleased] - TBD +### Added +- Build-time class-cache generation: a `tenup-framework-generate-class-cache` command (installed to `vendor/bin/`) and a `composer generate-class-cache` alias that build the cache in CI without bootstrapping WordPress. See [Build and Deployment](docs/Build-and-Deployment.md) ([#30](https://github.com/10up/wp-framework/issues/30)). +- Hidden admin page (`admin.php?page=tenup-framework-loaders`, `manage_options`) that aggregates every class-loader cache on the site — across all framework copies — and shows each cache's path, status, loaded classes, and an on-demand live-vs-cache staleness check. Admin-only (no front-end overhead) and read-only. Disable with the `tenup_framework_enable_loader_debug` filter or the `TENUP_FRAMEWORK_DISABLE_LOADER_DEBUG` constant. See [Debugging class loaders](docs/Debugging.md). +- The loader debug page reports per-loader timing: how long class **discovery** took (a cache read when cached, a live filesystem scan otherwise) and how long **class lookup** (reflection, instantiation and registration) took. The staleness check also reports how long its live discovery ran, so the cache's saving on a given site is measurable. + +### Changed +- The class-loader cache is now **read-only at runtime** and opt-in. The framework reads a pre-built cache if present and discovers live otherwise, but never writes one on the server — fixing stale caches that could only be cleared by hand ([#30](https://github.com/10up/wp-framework/issues/30)). +- A corrupt or truncated shipped cache is caught at runtime and the request falls back to a live scan instead of fataling, so a bad cache degrades performance rather than taking the site down. The fallback fires a `tenup_framework_cache_load_failed` action (for logging or alerting) and the loader debug page flags that loader red as "Cache failed to load — running live" instead of reporting it as in use. +- Bumped the cache filename so a cache written by an older version is ignored after upgrade rather than served stale. +- `TENUP_FRAMEWORK_DISABLE_CLASS_CACHE` now forces live discovery (ignores any shipped cache). + +### Removed +- Automatic runtime cache generation and its environment gating — `should_use_cache()`, the `production`/`staging` checks, and the `VIP_GO_APP_ENVIRONMENT` handling. Caching is now produced at build time instead. ## [1.2.0] - 2025-03-20 ### Changed diff --git a/bin/tenup-framework-generate-class-cache b/bin/tenup-framework-generate-class-cache new file mode 100755 index 0000000..23458fb --- /dev/null +++ b/bin/tenup-framework-generate-class-cache @@ -0,0 +1,76 @@ +#!/usr/bin/env php + [
' . esc_html__( 'Each card is a directory passed to ModuleInitialization::init_classes(), with the state of its class-loader cache. Caches are built at deploy time and read — never written — at runtime.', 'tenup-framework' ) . '
'; + + if ( empty( $loaders ) ) { + echo '' . esc_html( $value ) . '| ' . esc_html__( 'Class', 'tenup-framework' ) . ' | ' . esc_html__( 'File', 'tenup-framework' ) . ' |
|---|---|
' . esc_html( $class ) . ' | ' . esc_html( $file ) . ' |
' . esc_html__( 'Check this cache for staleness', 'tenup-framework' ) . '
'; + return; + } + + $live_start = hrtime( true ); + $live = ModuleInitialization::instance()->discover_live( $directory ); + $live_seconds = ( hrtime( true ) - $live_start ) / 1e9; + + $loaded = array_values( $classes ); + $removed = array_diff( $loaded, $live ); // In cache but no longer on disk. + $added = array_diff( $live, $loaded ); // On disk but missing from the cache. + + $timing = sprintf( + /* translators: %s: formatted duration. */ + __( 'Live discovery took %s.', 'tenup-framework' ), + self::format_duration( $live_seconds ) + ); + + if ( empty( $removed ) && empty( $added ) ) { + echo '' . esc_html__( 'On disk but missing from the cache:', 'tenup-framework' ) . '
' . esc_html( $class ) . '' . esc_html__( 'In the cache but no longer on disk:', 'tenup-framework' ) . '
' . esc_html( $class ) . '' . esc_html__( 'Regenerate the cache in your build (composer generate-class-cache) or remove the file and redeploy.', 'tenup-framework' ) . '
'; + echo '