Skip to content

Move caching into SourceTransformingLoader, remove CachingTransformer - #595

Merged
lisachenko merged 2 commits into
masterfrom
claude/goaop-remove-caching-transformer
Aug 27, 2026
Merged

Move caching into SourceTransformingLoader, remove CachingTransformer#595
lisachenko merged 2 commits into
masterfrom
claude/goaop-remove-caching-transformer

Conversation

@lisachenko

Copy link
Copy Markdown
Member

Summary

Follow-up to the boot-time series (#589, #591, #592, #593, #594), as requested: CachingTransformer is removed and the caching logic lives right in SourceTransformingLoader::filter(), next to the StreamMetaData construction. If a usable cache record exists for the streamed file, its content is returned from there as the result — otherwise the code falls back to transformCode().

What changed

  • SourceTransformingLoader::filter() decides in this order:
    1. Guard — no cache path, or cache path equals the original file: the source passes through untouched (previously the RESULT_ABORTED branch of the wrapper).
    2. Cache hit (findCachedContent()) — the cached file content (or, for a known-untransformed file, the buffered original source) is emitted as-is: no StreamMetaData, no parsing, no transformer is ever constructed. All freshness rules are ported verbatim: filemtime comparison, recorded-cacheUri match, hasAnyResourceChangedSince(), and the PREBUILT_CACHE trusted-record short-circuit.
    3. Cache miss — parse into StreamMetaData, run the transformer chain, persist via saveToCache() (ported verbatim: __AopProxied suffix rename, mkdir/LOCK_EX/chmod, setCacheState() with the class-map recording from perf(cache): split runtime include map from transformation metadata #594 intact).
  • transformCode() now returns the overall TransformerResultEnum (the logic formerly in CachingTransformer::processTransformers()); the chain is assembled lazily on the first miss from the tagged container services (getServicesByInterface(SourceTransformer::class)), so no unset() exclusion is needed anymore.
  • Removed src/Instrument/Transformer/CachingTransformer.php, its container definition in AspectKernel::init(), and SourceTransformingLoader::addTransformer(). The registerTransformerServices() extension hook is unchanged.

Why it helps

The old cached branch went through the whole filter pipeline anyway and then re-parsed the cached file (ReflectionEngine::parseFile()) just to rebuild the token stream. Now a hit skips parsing entirely — which finally makes the warm-cache fast path apply in debug mode too, with unchanged invalidation semantics.

Local numbers (fixture app, opcache file cache, median of 21):

Mode (PHP 8.5) master this PR Δ
debug warm boot+request 15.1–16.2 ms 5.7–6.0 ms −62 %
debug warm files included 187 127 −60 files
production warm 3.9 ms 3.9 ms unchanged
cold (first boot) ~103 ms ~103 ms unchanged

PHP 8.4 debug warm shows the same shape: 16.2 ms → 5.5 ms (−66 %).

Validation

  • phpunit green on PHP 8.5 and 8.4 (2486 tests), including the functional warmup / debug:weaving / inconsistent-weaving coverage that exercises the moved logic on real caches
  • phpstan (level max) clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE


Generated by Claude Code

…gTransformer

The cache decision now lives directly in the stream filter, next to the
StreamMetaData construction: a usable cache record makes filter() emit the
cached file content as-is - without parsing the source, constructing any
transformer or building StreamMetaData. Only a real miss parses the source,
runs the transformer chain (assembled lazily from the tagged container
services) and persists the result.

This removes the CachingTransformer wrapper entirely and with it the old
cached-branch double work of re-parsing the cached file just to rebuild the
token stream - so the warm-cache fast path now applies in debug mode too,
while all freshness checks (filemtime, recorded cacheUri, tracked resources)
are preserved verbatim.

BC break (4.0): CachingTransformer is gone, transformCode() returns the
overall TransformerResultEnum, addTransformer() is removed - transformers
are registered as container services via registerTransformerServices().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
@lisachenko

Copy link
Copy Markdown
Member Author

i do not see any tests here - please cover with tests

Unit tests for every branch of the filter's cache decision: fresh
transformed/untransformed records served without constructing any
transformer, stale records falling back to the chain, miss persisting
the woven file and the untransformed record, abort short-circuiting the
chain, PREBUILT_CACHE trusting a stale record without consulting the
freshness collaborators, and the pass-through guard when the cache path
equals the original file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE

Copy link
Copy Markdown
Member Author

Added in fd84b7e: tests/Instrument/ClassLoading/SourceTransformingLoaderTest.php covers every branch of the filter's cache decision through the real stream filter (php://filter/read=.../resource=...), in separate processes because of the loader's static state:

  • fresh transformed record → cached file content served, getServicesByInterface() never called (no transformer constructed)
  • fresh untransformed record → original source served, same no-transformer guarantee
  • miss → chain runs, woven file written, cache state recorded with its cacheUri
  • stale record (older than the source) → falls back to the chain
  • abstaining chain → recorded as untransformed, no cache file written
  • aborting transformer → rest of the chain skipped, nothing written
  • PREBUILT_CACHE → deliberately stale record trusted, hasAnyResourceChangedSince() never consulted
  • guard (cache path == original file) → source passes through untouched, no transformers, no state written

Full suite green on PHP 8.5/8.4 (2494 tests), PHPStan clean.


Generated by Claude Code

@lisachenko
lisachenko marked this pull request as ready for review August 27, 2026 11:50
@lisachenko
lisachenko merged commit 24e6f98 into master Aug 27, 2026
7 checks passed
@lisachenko
lisachenko deleted the claude/goaop-remove-caching-transformer branch August 27, 2026 11:51
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