perf(kernel): register transformation pipeline lazily on first cache miss - #592
Merged
Conversation
…miss The stream filter and the transformer objects (CachingTransformer, FilterInjectorTransformer, MagicConstantTransformer) were constructed and registered on every request, although a warm-cache request never streams a single file through the filter. - AspectKernel::init() now registers CachingTransformer as a deferred container definition; SourceTransformingLoader::ensureRegistered() brings up the filter + pipeline idempotently from the cache-miss paths (autoload miss via FilterInjectorTransformer::rewrite(), cache warmup). - FilterInjectorTransformer::rewrite() self-configures from the booted kernel on first call (its call sites - autoloader miss path and rewritten include statements inside cached files - can run before any transformer object exists); configure() became set-once instead of throwing. - MagicConstantTransformer::resolveFileName() (called at runtime from woven code) resolves its paths on demand from the kernel options instead of relying on boot-time constructor side effects. - CacheWarmer calls ensureRegistered() before streaming files through the filter URI it builds directly. BC note (4.0): protected AspectKernel::registerTransformers() is replaced by createSourceTransformers() returning the inner transformer chain. Also adds the CHANGELOG entries for this and the two previous container laziness changes (#589, #591). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
lisachenko
commented
Aug 25, 2026
lisachenko
commented
Aug 25, 2026
Per review: each transformer is now a typical deferred container service; createSourceTransformers() assembles the chain by the SourceTransformer interface tag (registration order = transformation order), excluding the CachingTransformer wrapper which is the pipeline entry point. A kernel can plug its own transformer in with a single addLazyService() call from configureAop(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
…ntainer definition Per review: the transformer chain assembly lives inside the CachingTransformer container definition now - kernels customize the pipeline purely through container service registration, no protected kernel hook remains. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
…om pipelines Downstream libraries (e.g. AspectMock) overrode the removed registerTransformers() to build a fully custom transformer chain - swapping the standard WeavingTransformer for their own weaver, reordering and omitting built-ins. Restore that extension point in container-definition form: the protected registerTransformerServices(AspectContainer) hook registers the inner chain as deferred services (registration order = chain order, feature-gated as before); overriding it controls the pipeline completely, while a single addLazyService() from configureAop() still appends a transformer. The CachingTransformer wrapper every pipeline needs stays registered by init(). Re-registering a pending service id replaces its factory while keeping its chain position (covered by a new container test). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
lisachenko
commented
Aug 26, 2026
Per review: the chain consumers iterate values only, so the class-name keys can be preserved as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The stream filter and the transformer objects (
CachingTransformer,FilterInjectorTransformer,MagicConstantTransformer) were constructed and registered on every request, although a warm-cache request never streams a single file through the filter. This PR defers all of it to the first cache miss:AspectKernel::init()registersCachingTransformeras a deferred container definition (riding on the factory map from perf(container): defer service construction with factory map instead of eager lazy proxies #589) — nostream_filter_register(), no transformer construction at boot.SourceTransformingLoader::ensureRegistered($container)brings up the filter + pipeline from the miss paths.FilterInjectorTransformer::rewrite()self-configures from the booted kernel on first call — its call sites (autoloader miss path, rewrittenincludestatements inside cached files) can run before any transformer object exists.configure()became set-once (first configuration wins) instead of throwing on a second call.MagicConstantTransformer::resolveFileName()— called at runtime from woven code — resolves its paths on demand from the kernel options instead of relying on boot-time constructor side effects.CacheWarmercallsensureRegistered()before streaming files through the filter URI it builds directly (also coversdebug:weaving).Also adds the missing CHANGELOG entries for this and the two previous laziness changes (#589, #591).
Why (measured)
Fixture project, warm cache,
debug=false, opcache file cache, paired back-to-back runs (median of 21), PHP 8.5:AspectKernel::init()Cumulative since the series started: init 1.88 → 0.90 ms, files 142 → 117 on the same benchmark.
BC note (4.0)
Protected
AspectKernel::registerTransformers()is replaced bycreateSourceTransformers(), which returns the inner transformer chain (theCachingTransformerwrapper is now owned by the kernel/container). Kernels overriding the old hook must migrate.FilterInjectorTransformer::configure()no longer throws when called twice — the first configuration wins.Validation
PHPStan clean; 2480 tests green on PHP 8.5, 8.4 and 8.6.0beta2 locally (cold weaving, warmup command and debug commands all exercise the lazy pipeline through the functional tests).
Part of the boot-time series
Third of five sequential PRs (container laziness → lazy aspects → lazy transformers → trusted prebuilt cache → cache-state split).
🤖 Generated with Claude Code
https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
Generated by Claude Code