Hello internals,
My name is Zoltán Gyárfás András (aka Zoli). I am a long-time PHP developer, primarily working on large PHP codebases where constructor-based dependency injection is used extensively.
Before preparing a formal RFC, I would like to clarify the scope and intent of a small, opt-in idea and gather early feedback from the list.
The idea is to introduce a minimal reflection-based constructor autowiring primitive into the core, exposed explicitly via the Reflection API, for example:
ReflectionClass::newInstanceAutowire(array $overrides = [], ?callable $resolver = null): object
This proposal is intentionally narrow. To avoid misunderstandings, I would like to clearly explain what the idea does and does not include.
Key points of the idea, explained in detail:
-
Explicit opt-in (no change to the new operator)
Autowiring would only happen when the developer explicitly calls the new Reflection API.
The semantics of the new operator remain unchanged. Existing code paths are not affected, and there is no implicit dependency resolution anywhere in the language. -
No global container or service registry
The proposal does not introduce a global container, service locator, or registry of any kind.
Each autowiring operation is local to the call site and bound to the current call stack. No global state is created or reused across calls. -
No implicit interface-to-implementation mapping
When a constructor depends on an interface or abstract class, the core does not attempt to guess or discover a concrete implementation.
Such mappings are inherently policy decisions and vary widely between frameworks. Instead, an explicit resolver callback is required if non-instantiable types are involved. -
Scalar parameters require overrides or defaults
Scalar and builtin parameters are treated as configuration values. The core does not read environment variables, configuration files, or globals.
As a result, scalar parameters must either have default values or be provided explicitly via the $overrides argument. -
Interface and abstract types require an explicit resolver callback
Interfaces and abstract classes are never instantiated automatically.
If encountered during autowiring, the core either delegates resolution to the provided resolver callback or fails with a clear exception. This keeps architectural decisions firmly in userland. -
Deterministic circular dependency detection
Autowiring necessarily builds an object graph. The proposal includes mandatory detection of circular dependencies within that graph.
When a cycle is detected, a deterministic and descriptive exception is thrown, rather than allowing infinite recursion or a stack overflow. -
Request-scope caching of constructor metadata only
For performance reasons, constructor metadata (parameter lists, types, defaults) may be cached for the duration of the request.
No object instances are cached, no lifetimes are managed, and no persistent or global caches are introduced.
At this stage, I am primarily interested in feedback on whether this level of restraint is sufficient to keep the feature aligned with PHP’s “mechanism, not policy” philosophy, and whether there are any immediate concerns regarding reflection, error handling, or performance.
If the direction seems reasonable, I plan to follow up with a draft RFC on wiki.php.net that incorporates the feedback from this discussion.
Thank you for your time and insights.
Best regards,
Zoli
eng. ANDRÁS Zoltán-Gyárfás
tel: +40 745 797 798
mail: azolee@gmail.com
Hey Zoli,
On Sun, 28 Dec 2025 at 10:40, ANDRÁS Zoltán Gyárfás azolee@gmail.com
wrote:
Hello internals,
My name is Zoltán Gyárfás András (aka Zoli). I am a long-time PHP
developer, primarily working on large PHP codebases where constructor-based
dependency injection is used extensively.Before preparing a formal RFC, I would like to clarify the scope and
intent of a small, opt-in idea and gather early feedback from the list.The idea is to introduce a minimal reflection-based constructor
autowiring primitive into the core, exposed explicitly via the
Reflection API, for example:ReflectionClass::newInstanceAutowire(array $overrides = [], ?callable $resolver = null): object
This proposal is intentionally narrow. To avoid misunderstandings, I would
like to clearly explain what the idea does and does not include.Key points of the idea, explained in detail:
1. Explicit opt-in (no change to the new operator)
Autowiring would only happen when the developer explicitly calls the new
Reflection API.
The semantics of the new operator remain unchanged. Existing code paths
are not affected, and there is no implicit dependency resolution anywhere
in the language.2. No global container or service registry
The proposal does not introduce a global container, service locator, or
registry of any kind.
Each autowiring operation is local to the call site and bound to the
current call stack. No global state is created or reused across calls.3. No implicit interface-to-implementation mapping
When a constructor depends on an interface or abstract class, the core
does not attempt to guess or discover a concrete implementation.
Such mappings are inherently policy decisions and vary widely between
frameworks. Instead, an explicit resolver callback is required if
non-instantiable types are involved.4. Scalar parameters require overrides or defaults
Scalar and builtin parameters are treated as configuration values. The
core does not read environment variables, configuration files, or globals.
As a result, scalar parameters must either have default values or be
provided explicitly via the $overrides argument.5. Interface and abstract types require an explicit resolver callback
Interfaces and abstract classes are never instantiated automatically.
If encountered during autowiring, the core either delegates resolution to
the provided resolver callback or fails with a clear exception. This keeps
architectural decisions firmly in userland.6. Deterministic circular dependency detection
Autowiring necessarily builds an object graph. The proposal includes
mandatory detection of circular dependencies within that graph.
When a cycle is detected, a deterministic and descriptive exception is
thrown, rather than allowing infinite recursion or a stack overflow.7. Request-scope caching of constructor metadata only
For performance reasons, constructor metadata (parameter lists, types,
defaults) may be cached for the duration of the request.
No object instances are cached, no lifetimes are managed, and no
persistent or global caches are introduced.At this stage, I am primarily interested in feedback on whether this level
of restraint is sufficient to keep the feature aligned with PHP’s
“mechanism, not policy” philosophy, and whether there are any immediate
concerns regarding reflection, error handling, or performance.If the direction seems reasonable, I plan to follow up with a draft RFC on
wiki.php.net that incorporates the feedback from this discussion.Thank you for your time and insights.
Best regards,
Zoli
I'm unconvinced about using reflection for this: reflection is thought of a
"deep trap" for inspecting and manipulating code in ways that aren't
generally possible in userland, and isn't really a factory.
Everything you described can be implemented in userland, using existing
reflection API.
While I like the signature you are going with (or at least its direction),
I believe that a userland implementation that proves to be "so popular that
everyone would use it" is needed first.
For instance, the implementation you propose already goes towards
supporting DI systems that do autowiring at runtime, excluding those that
do some AoT compilation of factories.
Also, unless provided by resolvers, all dependencies aren't cached in any
way (also something that tends to be configurable in various DI libraries).
I suggest prroviding this as a userland library or extension first,
specifically to validate whether there is a substantial performance
improvement over current solutions.
Marco Pivetta
Hi Marco,
thank you for the thoughtful feedback - I agree with the concerns you raised, as they help clarify the right next step.
On Reflection: my initial motivation for using ReflectionClass was that it already owns the “dynamic instantiation” surface (newInstance, newInstanceArgs) and provides an explicit opt-in entry point without introducing new global concepts. That said, I fully take your point that Reflection is primarily an inspection API and can be seen as a “deep trap” when it starts to look like a general-purpose factory. I don’t want to force that framing if it turns out to be the wrong abstraction.
On “this can be done in userland”: I agree. The proposal is not about enabling something fundamentally impossible today, but about whether moving a very common runtime autowiring loop (constructor parameter analysis, type handling, cycle detection, diagnostics) closer to the engine could provide enough benefit to justify standardization. As you point out, that needs to be demonstrated, not assumed.
Your suggestion to validate this in userland (or as an extension) first makes sense, and I plan to follow that path before pushing further on a core RFC. Concretely, the next steps I’m considering are:
A reference userland implementation that follows the same strict semantics discussed (explicit overrides, resolver for non-instantiables, deterministic cycle detection, request-scope metadata caching).
A small benchmark suite comparing:
typical userland reflection-based autowiring approaches,
the reference implementation,
and, if feasible, a proof-of-concept extension implementing the same algorithm in C.
Using that data to answer whether there is a meaningful performance or complexity win that userland alone cannot reasonably achieve.
Regarding AoT / compiled factories: I agree that the proposed primitive naturally aligns better with runtime autowiring, and that many containers deliberately avoid runtime reflection for production paths. My intent was not to replace or constrain those approaches, but to explore whether a low-level helper could still be useful in non-compiled modes (development, dynamic plugins, fallback paths). Whether that use case is broad enough is exactly what the userland validation should clarify.
If you have any guidance on what you would consider a representative benchmark or a meaningful performance threshold for “worth moving into core”, I’d appreciate it.
Thanks again for the feedback,
Zoli
eng. ANDRÁS Zoltán-Gyárfás
tel: +40 745 797 798
mail: azolee@gmail.com
Hey Zoli,
Hello internals,
My name is Zoltán Gyárfás András (aka Zoli). I am a long-time PHP developer, primarily working on large PHP codebases where constructor-based dependency injection is used extensively.
Before preparing a formal RFC, I would like to clarify the scope and intent of a small, opt-in idea and gather early feedback from the list.
The idea is to introduce a minimal reflection-based constructor autowiring primitive into the core, exposed explicitly via the Reflection API, for example:
ReflectionClass::newInstanceAutowire(array $overrides = [], ?callable $resolver = null): object
This proposal is intentionally narrow. To avoid misunderstandings, I would like to clearly explain what the idea does and does not include.Key points of the idea, explained in detail:
Explicit opt-in (no change to the new operator)
Autowiring would only happen when the developer explicitly calls the new Reflection API.
The semantics of the new operator remain unchanged. Existing code paths are not affected, and there is no implicit dependency resolution anywhere in the language.No global container or service registry
The proposal does not introduce a global container, service locator, or registry of any kind.
Each autowiring operation is local to the call site and bound to the current call stack. No global state is created or reused across calls.No implicit interface-to-implementation mapping
When a constructor depends on an interface or abstract class, the core does not attempt to guess or discover a concrete implementation.
Such mappings are inherently policy decisions and vary widely between frameworks. Instead, an explicit resolver callback is required if non-instantiable types are involved.Scalar parameters require overrides or defaults
Scalar and builtin parameters are treated as configuration values. The core does not read environment variables, configuration files, or globals.
As a result, scalar parameters must either have default values or be provided explicitly via the $overrides argument.Interface and abstract types require an explicit resolver callback
Interfaces and abstract classes are never instantiated automatically.
If encountered during autowiring, the core either delegates resolution to the provided resolver callback or fails with a clear exception. This keeps architectural decisions firmly in userland.Deterministic circular dependency detection
Autowiring necessarily builds an object graph. The proposal includes mandatory detection of circular dependencies within that graph.
When a cycle is detected, a deterministic and descriptive exception is thrown, rather than allowing infinite recursion or a stack overflow.Request-scope caching of constructor metadata only
For performance reasons, constructor metadata (parameter lists, types, defaults) may be cached for the duration of the request.
No object instances are cached, no lifetimes are managed, and no persistent or global caches are introduced.At this stage, I am primarily interested in feedback on whether this level of restraint is sufficient to keep the feature aligned with PHP’s “mechanism, not policy” philosophy, and whether there are any immediate concerns regarding reflection, error handling, or performance.
If the direction seems reasonable, I plan to follow up with a draft RFC on wiki.php.net http://wiki.php.net/ that incorporates the feedback from this discussion.
Thank you for your time and insights.
Best regards,
Zoli
I'm unconvinced about using reflection for this: reflection is thought of a "deep trap" for inspecting and manipulating code in ways that aren't generally possible in userland, and isn't really a factory.
Everything you described can be implemented in userland, using existing reflection API.While I like the signature you are going with (or at least its direction), I believe that a userland implementation that proves to be "so popular that everyone would use it" is needed first.
For instance, the implementation you propose already goes towards supporting DI systems that do autowiring at runtime, excluding those that do some AoT compilation of factories.
Also, unless provided by resolvers, all dependencies aren't cached in any way (also something that tends to be configurable in various DI libraries).I suggest prroviding this as a userland library or extension first, specifically to validate whether there is a substantial performance improvement over current solutions.
Marco Pivetta
Hey Zoli,
On Tue, 30 Dec 2025 at 08:17, ANDRÁS Zoltán Gyárfás azolee@gmail.com
wrote:
If you have any guidance on what you would consider a representative
benchmark or a meaningful performance threshold for “worth moving into
core”, I’d appreciate it.
In the grand majority of cases where autowiring is meaningful (at least for
PHP), you care about a "cold start" of the application (although that's
changing with ReactPHP/FrankenPHP/Roadrunner/etc.).
You may design a PHPBench or Hyperfine test that:
- starts a PHP process, autowires something, shuts down. Hyperfine helps a
lot here. - repeats the above, but within a warm process (classes already loaded,
tight loop repeating the operation). PHPBench is a bit more precise here.
You probably care about these cases:
- zero-arguments constructor
- simple concrete class references
- default constructor arguments
- resolvers
- autowiring of a complex multi-level structure
In all the above cases, you probably care about the usage with autowiring
API vs the usage via direct instantiation, so you can see the exact
overhead.
Marco Pivetta
Hi Marco,
Thank you, that’s very clear and actionable.
I’ll focus the benchmarks on cold-start and warm-process scenarios as you outlined, and compare autowiring overhead directly against manual instantiation across the relevant cases.
I’ll follow up once I have data to share.
Best regards,
Zoli
eng. ANDRÁS Zoltán-Gyárfás
tel: +40 745 797 798
mail: azolee@gmail.com
Hey Zoli,
If you have any guidance on what you would consider a representative benchmark or a meaningful performance threshold for “worth moving into core”, I’d appreciate it.
In the grand majority of cases where autowiring is meaningful (at least for PHP), you care about a "cold start" of the application (although that's changing with ReactPHP/FrankenPHP/Roadrunner/etc.).
You may design a PHPBench or Hyperfine test that:
- starts a PHP process, autowires something, shuts down. Hyperfine helps a lot here.
- repeats the above, but within a warm process (classes already loaded, tight loop repeating the operation). PHPBench is a bit more precise here.
You probably care about these cases:
- zero-arguments constructor
- simple concrete class references
- default constructor arguments
- resolvers
- autowiring of a complex multi-level structure
In all the above cases, you probably care about the usage with autowiring API vs the usage via direct instantiation, so you can see the exact overhead.
Marco Pivetta
Hi all,
I’ve completed the benchmarks based on the guidance in the thread.
In short:
Cold-start measurements (fresh PHP process per run) show no meaningful difference between direct instantiation and reflection-based autowiring. Process startup dominates, and reflection overhead is effectively lost in the noise.
Warm-process benchmarks show a consistent ~3-4x overhead for runtime autowiring compared to direct instantiation, even with request-scope constructor plan caching. This appears to be a model-level cost rather than something easily eliminated by implementation details alone.
Based on these results, I don’t see a lazy-proxy-like engine-only win here. Userland implementations already seem close to the practical ceiling for runtime autowiring, and further gains would likely be incremental.
Given that, I agree this does not currently make a compelling case for a core addition, and that runtime autowiring remains better addressed in userland or framework-specific solutions.
Thanks to everyone who provided feedback and guidance.
Best regards,
Zoli
eng. ANDRÁS Zoltán-Gyárfás
tel: +40 745 797 798
mail: azolee@gmail.com
Hey Zoli,
If you have any guidance on what you would consider a representative benchmark or a meaningful performance threshold for “worth moving into core”, I’d appreciate it.
In the grand majority of cases where autowiring is meaningful (at least for PHP), you care about a "cold start" of the application (although that's changing with ReactPHP/FrankenPHP/Roadrunner/etc.).
You may design a PHPBench or Hyperfine test that:
- starts a PHP process, autowires something, shuts down. Hyperfine helps a lot here.
- repeats the above, but within a warm process (classes already loaded, tight loop repeating the operation). PHPBench is a bit more precise here.
You probably care about these cases:
- zero-arguments constructor
- simple concrete class references
- default constructor arguments
- resolvers
- autowiring of a complex multi-level structure
In all the above cases, you probably care about the usage with autowiring API vs the usage via direct instantiation, so you can see the exact overhead.
Marco Pivetta
Hello internals,
My name is Zoltán Gyárfás András (aka Zoli). I am a long-time PHP
developer, primarily working on large PHP codebases where
constructor-based dependency injection is used extensively.Before preparing a formal RFC, I would like to clarify the scope and
intent of a small, opt-in idea and gather early feedback from the list.The idea is to introduce a minimal reflection-based constructor
autowiring primitive into the core, exposed explicitly via the
Reflection API, for example:
ReflectionClass::newInstanceAutowire(array $overrides = [], ?callable $resolver = null): object
This proposal is intentionally narrow. To avoid misunderstandings, I
would like to clearly explain what the idea does and does not
include.Key points of the idea, explained in detail:
1. Explicit opt-in (no change to the
newoperator)
Autowiring would only happen when the developer explicitly calls the
new Reflection API.
The semantics of thenewoperator remain unchanged. Existing code
paths are not affected, and there is no implicit dependency resolution
anywhere in the language.2. No global container or service registry
The proposal does not introduce a global container, service locator, or
registry of any kind.
Each autowiring operation is local to the call site and bound to the
current call stack. No global state is created or reused across calls.3. No implicit interface-to-implementation mapping
When a constructor depends on an interface or abstract class, the core
does not attempt to guess or discover a concrete implementation.
Such mappings are inherently policy decisions and vary widely between
frameworks. Instead, an explicit resolver callback is required if
non-instantiable types are involved.4. Scalar parameters require overrides or defaults
Scalar and builtin parameters are treated as configuration values. The
core does not read environment variables, configuration files, or
globals.
As a result, scalar parameters must either have default values or be
provided explicitly via the$overridesargument.5. Interface and abstract types require an explicit resolver callback
Interfaces and abstract classes are never instantiated automatically.
If encountered during autowiring, the core either delegates resolution
to the provided resolver callback or fails with a clear exception. This
keeps architectural decisions firmly in userland.6. Deterministic circular dependency detection
Autowiring necessarily builds an object graph. The proposal includes
mandatory detection of circular dependencies within that graph.
When a cycle is detected, a deterministic and descriptive exception is
thrown, rather than allowing infinite recursion or a stack overflow.7. Request-scope caching of constructor metadata only
For performance reasons, constructor metadata (parameter lists, types,
defaults) may be cached for the duration of the request.
No object instances are cached, no lifetimes are managed, and no
persistent or global caches are introduced.At this stage, I am primarily interested in feedback on whether this
level of restraint is sufficient to keep the feature aligned with PHP’s
“mechanism, not policy” philosophy, and whether there are any immediate
concerns regarding reflection, error handling, or performance.If the direction seems reasonable, I plan to follow up with a draft RFC
on wiki.php.net that incorporates the feedback from this discussion.Thank you for your time and insights.
Best regards,
Zoli
I am unclear what advantage this offers over the status quo, or who the intended user is. Is the intent to be "an alternative to existing DI containers" (in which case, what does it offer that would make me use it instead of Symfony DI, PHP-DI, rolling my own, etc.), or is it "a tool that existing DI containers can use to be better" (in which case, how is it better than the existing options for them)? Those are two different goals that would have two different designs.
For example, lazy object proxies already existed in user-space. However, the amount of fugly code it required was high, so moving that logic into the engine where it could take advantage of engine-only features to provide a far cleaner API and better performance was a win, and allows the removal of lots of fugly code from existing projects (when they upgrade). I'm not clear where such a win can be found with this proposal.
--Larry Garfield
Hello internals,
My name is Zoltán Gyárfás András (aka Zoli). I am a long-time PHP
developer, primarily working on large PHP codebases where
constructor-based dependency injection is used extensively.Before preparing a formal RFC, I would like to clarify the scope and
intent of a small, opt-in idea and gather early feedback from the list.The idea is to introduce a minimal reflection-based constructor
autowiring primitive into the core, exposed explicitly via the
Reflection API, for example:
ReflectionClass::newInstanceAutowire(array $overrides = [], ?callable $resolver = null): object
This proposal is intentionally narrow. To avoid misunderstandings, I
would like to clearly explain what the idea does and does not
include.Key points of the idea, explained in detail:
1. Explicit opt-in (no change to the
newoperator)
Autowiring would only happen when the developer explicitly calls the
new Reflection API.
The semantics of thenewoperator remain unchanged. Existing code
paths are not affected, and there is no implicit dependency resolution
anywhere in the language.2. No global container or service registry
The proposal does not introduce a global container, service locator, or
registry of any kind.
Each autowiring operation is local to the call site and bound to the
current call stack. No global state is created or reused across calls.3. No implicit interface-to-implementation mapping
When a constructor depends on an interface or abstract class, the core
does not attempt to guess or discover a concrete implementation.
Such mappings are inherently policy decisions and vary widely between
frameworks. Instead, an explicit resolver callback is required if
non-instantiable types are involved.4. Scalar parameters require overrides or defaults
Scalar and builtin parameters are treated as configuration values. The
core does not read environment variables, configuration files, or
globals.
As a result, scalar parameters must either have default values or be
provided explicitly via the$overridesargument.5. Interface and abstract types require an explicit resolver callback
Interfaces and abstract classes are never instantiated automatically.
If encountered during autowiring, the core either delegates resolution
to the provided resolver callback or fails with a clear exception. This
keeps architectural decisions firmly in userland.6. Deterministic circular dependency detection
Autowiring necessarily builds an object graph. The proposal includes
mandatory detection of circular dependencies within that graph.
When a cycle is detected, a deterministic and descriptive exception is
thrown, rather than allowing infinite recursion or a stack overflow.7. Request-scope caching of constructor metadata only
For performance reasons, constructor metadata (parameter lists, types,
defaults) may be cached for the duration of the request.
No object instances are cached, no lifetimes are managed, and no
persistent or global caches are introduced.At this stage, I am primarily interested in feedback on whether this
level of restraint is sufficient to keep the feature aligned with PHP’s
“mechanism, not policy” philosophy, and whether there are any immediate
concerns regarding reflection, error handling, or performance.If the direction seems reasonable, I plan to follow up with a draft RFC
on wiki.php.net that incorporates the feedback from this discussion.Thank you for your time and insights.
Best regards,
Zoli
I am unclear what advantage this offers over the status quo, or who the
intended user is. Is the intent to be "an alternative to existing DI
containers" (in which case, what does it offer that would make me use it
instead of Symfony DI, PHP-DI, rolling my own, etc.), or is it "a tool that
existing DI containers can use to be better" (in which case, how is it
better than the existing options for them)? Those are two different goals
that would have two different designs.For example, lazy object proxies already existed in user-space. However,
the amount of fugly code it required was high, so moving that logic into
the engine where it could take advantage of engine-only features to provide
a far cleaner API and better performance was a win, and allows the removal
of lots of fugly code from existing projects (when they upgrade). I'm not
clear where such a win can be found with this proposal.--Larry Garfield
I meant to send this question yesterday—it pretty much sums up Larry’s
question, though:
What problem(s) does this solve that can’t or isn’t already solved by a
userland implementation?
I see a lot of detail on the technical aspects of what you’re proposing,
but I don’t see anything about why you’re proposing it.
Cheers,
Ben
Hi Larry, hi Ben,
Thank you - these questions are fair, and I agree they go to the core of what needs to be justified.
Clarifying the goal
The intent is not to provide an alternative to existing DI containers. If framed that way, I agree there is no strong case. What I am exploring is whether there is room for a small, low-level mechanism that containers (and projects that do lightweight wiring without a full container) could optionally build on.
These are two different goals, and the first one is explicitly not what I am aiming for.
Where a potential “win” could exist
At this stage, this is a hypothesis, not a conclusion. The possible benefits I am investigating are:
Standardizing a common runtime mechanism
Many projects independently implement very similar runtime autowiring loops: constructor inspection, type handling, cycle detection, and error reporting. While all of this can be done in userland, it is repeatedly reimplemented with small differences and edge cases.
Engine-level access to metadata with request-scope caching
Userland implementations rely heavily on Reflection objects and repeated allocations. In principle, an engine-level primitive could work directly with internal structures and cache a simple constructor plan for the duration of a request. Whether this results in a meaningful performance improvement needs to be measured.
More consistent diagnostics for failure cases
When runtime autowiring fails today, error messages and behavior vary widely between implementations. A shared primitive could provide deterministic and actionable diagnostics for common failure modes such as missing scalars, non-instantiable dependencies, or circular graphs.
Larry’s lazy-proxy example is a good reference for what “worth it” looks like: removing a large amount of complex or ugly code by using engine-only capabilities. I am not claiming the same level of benefit here. The question is whether the points above add up to a smaller but still meaningful win.
Why this cannot just be asserted
I agree that “this can be done in userland” is the default position, and that any move toward core needs evidence. At this point, I do not think the case can be made without concrete data.
Next step
Based on your feedback (and Marco’s), my plan is to:
implement a reference userland version with the proposed semantics,
benchmark it against typical reflection-based autowiring approaches,
and, if useful, compare it to a small proof-of-concept extension implementing the same algorithm in C.
This should allow us to answer concretely:
whether there is a meaningful performance or complexity win,
and whether the primitive would be broadly useful enough across runtime-autowiring use cases to justify standardization.
If you have guidance on what you would consider a representative benchmark scenario or a meaningful threshold for “this belongs in core”, I would appreciate it.
Thanks again for the questions - they helped sharpen the problem statement.
Best regards,
Zoli
eng. ANDRÁS Zoltán-Gyárfás
tel: +40 745 797 798
mail: azolee@gmail.com
Hello internals,
My name is Zoltán Gyárfás András (aka Zoli). I am a long-time PHP
developer, primarily working on large PHP codebases where
constructor-based dependency injection is used extensively.Before preparing a formal RFC, I would like to clarify the scope and
intent of a small, opt-in idea and gather early feedback from the list.The idea is to introduce a minimal reflection-based constructor
autowiring primitive into the core, exposed explicitly via the
Reflection API, for example:
ReflectionClass::newInstanceAutowire(array $overrides = [], ?callable $resolver = null): object
This proposal is intentionally narrow. To avoid misunderstandings, I
would like to clearly explain what the idea does and does not
include.Key points of the idea, explained in detail:
1. Explicit opt-in (no change to the
newoperator)
Autowiring would only happen when the developer explicitly calls the
new Reflection API.
The semantics of thenewoperator remain unchanged. Existing code
paths are not affected, and there is no implicit dependency resolution
anywhere in the language.2. No global container or service registry
The proposal does not introduce a global container, service locator, or
registry of any kind.
Each autowiring operation is local to the call site and bound to the
current call stack. No global state is created or reused across calls.3. No implicit interface-to-implementation mapping
When a constructor depends on an interface or abstract class, the core
does not attempt to guess or discover a concrete implementation.
Such mappings are inherently policy decisions and vary widely between
frameworks. Instead, an explicit resolver callback is required if
non-instantiable types are involved.4. Scalar parameters require overrides or defaults
Scalar and builtin parameters are treated as configuration values. The
core does not read environment variables, configuration files, or
globals.
As a result, scalar parameters must either have default values or be
provided explicitly via the$overridesargument.5. Interface and abstract types require an explicit resolver callback
Interfaces and abstract classes are never instantiated automatically.
If encountered during autowiring, the core either delegates resolution
to the provided resolver callback or fails with a clear exception. This
keeps architectural decisions firmly in userland.6. Deterministic circular dependency detection
Autowiring necessarily builds an object graph. The proposal includes
mandatory detection of circular dependencies within that graph.
When a cycle is detected, a deterministic and descriptive exception is
thrown, rather than allowing infinite recursion or a stack overflow.7. Request-scope caching of constructor metadata only
For performance reasons, constructor metadata (parameter lists, types,
defaults) may be cached for the duration of the request.
No object instances are cached, no lifetimes are managed, and no
persistent or global caches are introduced.At this stage, I am primarily interested in feedback on whether this
level of restraint is sufficient to keep the feature aligned with PHP’s
“mechanism, not policy” philosophy, and whether there are any immediate
concerns regarding reflection, error handling, or performance.If the direction seems reasonable, I plan to follow up with a draft RFC
on wiki.php.net http://wiki.php.net/ that incorporates the feedback from this discussion.Thank you for your time and insights.
Best regards,
Zoli
I am unclear what advantage this offers over the status quo, or who the intended user is. Is the intent to be "an alternative to existing DI containers" (in which case, what does it offer that would make me use it instead of Symfony DI, PHP-DI, rolling my own, etc.), or is it "a tool that existing DI containers can use to be better" (in which case, how is it better than the existing options for them)? Those are two different goals that would have two different designs.
For example, lazy object proxies already existed in user-space. However, the amount of fugly code it required was high, so moving that logic into the engine where it could take advantage of engine-only features to provide a far cleaner API and better performance was a win, and allows the removal of lots of fugly code from existing projects (when they upgrade). I'm not clear where such a win can be found with this proposal.
--Larry Garfield
I meant to send this question yesterday—it pretty much sums up Larry’s question, though:
What problem(s) does this solve that can’t or isn’t already solved by a userland implementation?
I see a lot of detail on the technical aspects of what you’re proposing, but I don’t see anything about why you’re proposing it.
Cheers,
Ben