Skip to content

perf(kernel): register transformation pipeline lazily on first cache miss - #592

Merged
lisachenko merged 5 commits into
masterfrom
claude/goaop-i1-lazy-transformers
Aug 26, 2026
Merged

perf(kernel): register transformation pipeline lazily on first cache miss#592
lisachenko merged 5 commits into
masterfrom
claude/goaop-i1-lazy-transformers

Conversation

@lisachenko

Copy link
Copy Markdown
Member

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() registers CachingTransformer as a deferred container definition (riding on the factory map from perf(container): defer service construction with factory map instead of eager lazy proxies #589) — no stream_filter_register(), no transformer construction at boot.
  • New idempotent 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, rewritten include statements 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.
  • CacheWarmer calls ensureRegistered() before streaming files through the filter URI it builds directly (also covers debug: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:

master (with #589+#591) this PR
AspectKernel::init() 1.28 ms 0.90 ms
full request 3.27 ms 2.71 ms (−17 %)
included files 123 117

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 by createSourceTransformers(), which returns the inner transformer chain (the CachingTransformer wrapper 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

…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
Comment thread src/Core/AspectKernel.php Outdated
Comment thread src/Core/AspectKernel.php Outdated
claude added 3 commits August 25, 2026 20:09
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
Comment thread src/Core/AspectKernel.php Outdated
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
@lisachenko
lisachenko marked this pull request as ready for review August 26, 2026 09:23
@lisachenko
lisachenko merged commit fa63593 into master Aug 26, 2026
7 checks passed
@lisachenko
lisachenko deleted the claude/goaop-i1-lazy-transformers branch August 26, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants