perf(container): defer service construction with factory map instead of eager lazy proxies - #589
Conversation
…of eager lazy proxies Registering the 9 built-in services previously did new ReflectionClass() + newLazyProxy() per service on every request, autoloading each service class and its full parent hierarchy (including the Dissect lexer/parser classes) even on a hot-cache request where none of them are ever used. addLazyService() now only stores the factory closure; the service is constructed, tagged and resource-tracked on first retrieval. Interface-tag queries (getServicesByInterface) materialize pending factories that implement the requested interface, so the weaving and console paths observe the same set of services as before. BC note (4.0): container services are no longer PHP 8.4 lazy proxies; a service registered via addLazyService() is constructed on first getService() / getValue() / matching getServicesByInterface() call instead of being a proxy instance from boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
|
CI status on this head: all required checks are green (PHPStan, PHPUnit lowest/highest on 8.4 and 8.5). The two failing jobs are the experimental PHP 8.6 jobs (
Proposed fix (separate small PR, since it's out of scope here): in $generated = preg_replace('/^#\[\\\\Deprecated\(.*\)\]\n/m', '', $generated);Happy to open that follow-up PR on request. Generated by Claude Code |
getServicesByInterface() probes pending factory ids with is_subclass_of(), so the factory map must only ever hold class-names. Enforce the documented class-string contract with a syntactic check at registration time (no autoloading); arbitrary string keys keep going through add() as before. Addresses review feedback on the lazy factory map. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
|
Update on the PHP 8.6 experimental jobs: the fix now exists as #590 (swap the Generated by Claude Code |
What
Container::addLazyService()previously executednew ReflectionClass($id)+newLazyProxy()for each of the 9 built-in services on every request, autoloading each service class and its whole parent hierarchy — including the Dissect lexer/parser classes — even on a hot-cache request where none of those services are ever touched.This change makes registration truly lazy:
addLazyService()now only stores the factory closure — no reflection, no proxy, no autoloading at boot.getService()/getValue()), with a re-entrancy guard.getServicesByInterface()materializes pending factories that implement the requested interface before answering, so the weaving and console paths observe exactly the same set of services as before. This path never runs on a hot request.has()accounts for both constructed values and pending factories.Why (measured)
Boot profile of the fixture project (
tests/Fixtures/project, 9 aspects, warm cache,debug=false, opcache file cache, median of 15 runs; kernel init + 6 woven class autoloads + first intercepted call):AspectKernel::init()Cold-start behavior is unchanged (weaving dominates there and all services materialize anyway).
BC note (4.0)
Services registered via
addLazyService()are no longer PHP 8.4 lazy proxy instances available from boot; they are constructed on first retrieval. Code that relied ongetValue()returning an uninitialized proxy observes the factory running at retrieval time instead. The container's public API surface is unchanged.tests/Core/ContainerTest.phpwas updated accordingly: the proxy-specific test becametestLazyServiceIsNotConstructedUntilFirstRetrieval, which pins the new deferred-construction semantics (registration alone never invokes the factory).Part of the boot-time series
First of five sequential PRs from the hot-start boot profiling effort (container laziness → lazy aspects → lazy transformers → trusted prebuilt cache → cache-state split). Each lands independently; later ones build on merged earlier ones.
🤖 Generated with Claude Code
https://claude.ai/code/session_01V1Z87HZ2iz23WPTtTYqFxE
Generated by Claude Code