[Feature] AOP-unaware container: aspects as typical services, engine-probed lazy proxies - #658
Merged
Merged
Conversation
Add Go\Core\NativeLazyProxy, a shared helper that creates PHP native lazy proxies by asking the engine instead of re-deriving compatibility rules in userland: newLazyProxy() throws for classes PHP cannot make lazy (memoized for long-running workers), and one isUninitializedLazyObject() probe detects property-less classes whose initializer would never run. The trusted create() variant serves framework-owned classes; Interceptor::createLazily() switches to it, unifying the two previously diverged lazy-proxy sites. Asking the engine also retires the stale readonly-before-8.5 version gate: current PHP 8.4.x already creates lazy proxies of readonly classes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rjmxdekGoJdXwwxDjhD1
The container is now a generic DI implementation with zero aspect awareness, and every aspect-specific code path it carried is gone: * registerAspect(), materializeAspect(), validateAspectRegistration(), the per-aspect factory validators map and isDebug() are removed. Aspects register through the generic API from configureAop(): add(Foo::class, new Foo()) eagerly, or addLazyService(Foo::class, static fn() => new Foo(...)) deferred. This also ends the double validation pass (validator hook plus materializeAspect re-check) that ran per aspect initialization - with a factory closure always present, the old constructibility validation is moot, and a wrong factory result is caught by the container's instanceof guard on first use. * New interface-keyed registration listeners: onRegistration(Aspect::class, $listener) fires with the id (never the value, preserving laziness) when a matching deferred service is registered. The kernel arms one listener in debug mode only to track aspect source files for cache freshness at registration time; production registration stays a pure array write. addResource() joins the AspectContainer interface as public for that listener. * Framework service wiring moves out of the container constructor into FrameworkServices::register(), called by the kernel during init(). * isLazyProxyCompatible() is deleted from the service read path - the full property enumeration and parent-chain reflection walk per first touch of every service is replaced by the NativeLazyProxy engine probe, one engine-level boolean in the compatible case. * getService() now delegates to getValue() plus the instanceof guard instead of duplicating the materialize-or-throw block, and the container throws SPL exceptions (InvalidArgumentException, UnexpectedValueException) instead of Go\Aop\AspectException. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rjmxdekGoJdXwwxDjhD1
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #658 +/- ##
============================================
+ Coverage 85.32% 85.40% +0.08%
+ Complexity 1656 1636 -20
============================================
Files 97 99 +2
Lines 4545 4509 -36
============================================
- Hits 3878 3851 -27
+ Misses 667 658 -9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
lisachenko
commented
Sep 3, 2026
NativeLazyProxy, the container's SPL exception types and the FrameworkServices wiring are internal parts, not user-facing changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rjmxdekGoJdXwwxDjhD1
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.
Summary
Makes
Go\Core\Containera genuinely generic, AOP-unaware DI container and removes the hot-path overhead flagged in review: the per-classisLazyProxyCompatible()reflection walk on the service read path, andvalidateAspectRegistration()running twice (plus a partial third time in debug) per aspect initialization.Aspects are typical services
registerAspect(),materializeAspect(),validateAspectRegistration(), the$factoryValidatorsmap andisDebug()are removed from the container and its interface. Aspects register through the existing generic API fromconfigureAop():Aspect enumeration on the weaving path is unchanged —
getServicesByInterface(Aspect::class)already finds deferred aspects by id. Since a factory closure is now always present, the old "default-constructible without factory" validation is moot; a factory returning the wrong type is caught once by the container'sinstanceofguard on first use.Interface-keyed registration listeners (
onRegistration)The one thing that genuinely could not wait for materialization — debug-mode tracking of aspect source files for cache freshness — becomes a generic container feature:
onRegistration(Aspect::class, $listener)fires with the id (never the value, so laziness is fully preserved) whenever a matching deferred service is registered. The kernel arms a single listener in debug mode only; in production no listener exists andaddLazyService()is a pure array write with zero autoloading.addResource()joins the interface as public for this.Engine-probed lazy proxies (
Go\Core\NativeLazyProxy)The 30-line
isLazyProxyCompatible()predicate (property enumeration + parent-chain reflection walk, run on first touch of every service, every request) is deleted. The engine already knows every incompatibility:newLazyProxy()throws for classes PHP cannot make lazy (memoized for worker runtimes), and oneisUninitializedLazyObject()call detects property-less classes whose initializer would never run. In the common compatible case the whole check is one engine-level boolean.Interceptor::createLazily()now uses the same helper via its trustedcreate()path, unifying the two previously diverged lazy-proxy sites. The probe also retires the stale readonly-before-8.5 version gate — current PHP 8.4.x already supports readonly lazy proxies.Also
FrameworkServices::register(), called by the kernel duringinit()—new Container()registers nothing.getService()delegates togetValue()+instanceofguard instead of duplicating the materialize-or-throw block.InvalidArgumentException,UnexpectedValueException) instead ofGo\Aop\AspectException— noGo\Aopimports remain inContainer.BC breaks (4.0-dev)
AspectContainer::registerAspect()removed — useadd()/addLazyService().AspectContainergainsonRegistration()and publicaddResource(); customcontainerClassimplementations must follow.Containerconstruction registers no framework services — callFrameworkServices::register()(the kernel does).Validation
composer cs— clean (PER-CS)composer analyze— clean (PHPStan level 10)composer testunder PHP 8.4 — 2666 tests, 3312 assertions, 0 failures (10 pre-existing skips). The functional suite fails under this environment's PHP 8.5.10 identically onmaster(pointcut parser issue unrelated to this change).🤖 Generated with Claude Code
https://claude.ai/code/session_01W6rjmxdekGoJdXwwxDjhD1
Generated by Claude Code