This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)
static ReflectionAttribute::invokeWithTargetReflector(?Reflector
$target, callable $callback): void
This will invoke $callback, with no arguments.
During the invocation,
ReflectionAttribute::getCurrentTargetReflector() will return $target.
(This allows testing attribute classes without using them as attributes.)
ReflectionAttribute->getTargetReflector(): \Reflector
This returns the reflector of the symbol on which the attribute is found.
This method mostly exists for completeness: The ReflectionAttribute
must store the target reflector, so one would expect to be able to
obtain it.
Example
#[Attribute(Attribute::TARGET_PARAMETER)]
class MyAutowireAttribute {
public readonly string $serviceId;
public function __construct() {
$reflectionParameter = ReflectionAttribute::getCurrentTargetReflector();
if ($reflectionParameter === null) {
throw new \RuntimeException('This class can only be instantiated
as an attribute.');
}
assert($reflectionParameter instanceof ReflectionParameter);
// @todo Some validation.
$this->serviceId = (string) $reflectionParameter->getType();
}
}
class MyService {
public function __construct(#[MyAutowireAttribute] private readonly
MyOtherService $otherService) {}
}
// Regular usage.
$reflector = (new ReflectionMethod(MyService::class,
'__construct'))->getParameters()[0];
$reflection_attribute = $reflector->getAttributes()[0];
assert($reflection_attribute->getTargetReflector() === $reflector);
$attribute = $reflection_attribute->newInstance();
assert($attribute instanceof MyAutowireAttribute);
assert($attribute->serviceId === MyOtherService::class);
// Simulation mode for tests.
$reflector = (new ReflectionFunction(fn (MyOtherService $arg) =>
null))->getParameters()[0];
$attribute = ReflectionAttribute::invokeWithTargetReflector($reflector,
fn () => new MyAutowireAttribute());
assert($attribute instanceof MyAutowireAttribute);
assert($attribute->serviceId === MyOtherService::class);
// Nested calls.
function test(\Reflector $a, \Reflector $b) {
assert(ReflectionAttribute::getCurrentTargetReflector() === null);
ReflectionAttribute::invokeWithTargetReflector($a, function () use ($a, $b) {
assert(ReflectionAttribute::getCurrentTargetReflector() === $a);
ReflectionAttribute::invokeWithTargetReflector($b, function () use ($b) {
assert(ReflectionAttribute::getCurrentTargetReflector() === $b);
ReflectionAttribute::invokeWithTargetReflector(null, function () {
assert(ReflectionAttribute::getCurrentTargetReflector() === null);
});
});
assert(ReflectionAttribute::getCurrentTargetReflector() === $a);
});
assert(ReflectionAttribute::getCurrentTargetReflector() === null);
}
Alternative proposal
For completeness, I am also proposing an alternative version of this.
The two are not necessarily mutually exclusive, but having both would
introduce some kind of redundancy.
Personally, I prefer the first proposal (see below why).
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrent(): ?\ReflectionAttribute
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the ReflectionAttribute instance on which ->newInstance() was
called.
ReflectionAttribute->getTargetReflector(): \Reflector
This returns the reflector of the symbol on which the attribute is found.
static ReflectionAttribute::create(\Reflector $target, string $name,
array $arguments, bool $is_repeated = false): \ReflectionAttribute
This returns a ReflectionAttribute object that behaves as if the
attribute was found on $target.
This is mostly for testing purposes.
Example
#[Attribute(Attribute::TARGET_PARAMETER)]
class MyAutowireAttribute {
public readonly string $serviceId;
public function __construct() {
$reflectionParameter =
ReflectionAttribute::getCurrent()->getTargetReflector();
[..]
// @todo Some validation.
$this->serviceId = (string) $reflectionParameter->getType();
}
}
class MyService {
public function __construct(#[MyAutowireAttribute] private readonly
MyOtherService $otherService) {}
}
// Regular usage.
$reflection_parameter = (new ReflectionMethod(MyService::class,
'__construct'))->getParameters()[0];
$reflection_attribute = $reflection_parameter->getAttributes()[0];
assert($reflection_attribute->getTargetReflector() === $reflection_parameter);
$attribute_instance = $reflectionAttribute->newInstance();
assert($attribute_instance instanceof MyAutowireAttribute);
assert($attribute_instance->serviceId === MyOtherService::class);
// Simulation mode for tests.
$reflection_parameter = (new ReflectionFunction(fn (MyOtherService
$arg) => null))->getParameters()[0];
$reflection_attribute =
ReflectionAttribute::create($reflection_parameter,
MyAutowireAttribute::class, []);
assert($reflection_attribute->getTargetReflector() === $reflection_parameter);
assert($reflection_attribute->getTargetReflector()->getAttributes() === []);
$attribute_instance = $reflection_attribute->newInstance();
assert($attribute_instance instanceof MyAutowireAttribute);
assert($attribute_instance->serviceId === MyOtherService::class);
Why do I like this version less?
For most use cases, the attribute instance does not need access to the
ReflectionAttribute object.
For the testing scenario, the "fake" ReflectionAttribute object feels
strange, because:
- ReflectionAttribute::create($reflector,
...)->getTargetReflector()->getAttributes() may be empty, or does not
contain the fake attribute. - ReflectionAttribute::create($reflector, ...)->isRepeated() is
completely meaningless. - If we add ReflectionAttribute->getPosition() in the future, the
result from the "fake" one will be off.
Any code that relies on these methods of ReflectionAttribute to look
for other attributes on the same symbol may break with a "fake"
instance.
Details, thoughts
The return type for ReflectionAttribute::getCurrentTargetReflector()
would not simply be "Reflector", but
"\ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant",
assuming that no dedicated interface is introduced until then.
For ReflectionAttribute::getCurrentTargetReflector(), I was wondering
if instead we may want a function like current_attribute_target().
This would be inspired by func_get_args()
.
In the end, the method is still related to reflection, so for now I
decided to keep it here.
For ReflectionAttribute::invokeWithTargetReflector(), we could instead
introduce something with ::push() and ::pop().
This would be more flexible, but it would also lead to people
forgetting to remove a reflector that was set temporarily, leaving the
system polluted.
For ReflectionAttribute::invokeWithTargetReflector() returning NULL,
we could instead have it throw an exception.
But then people might want an alternative method or mode that does
return NULL
when called outside ->newInstance().
By having it return NULL, the calling code can decide whether and
which exception to throw.
Implementation
An instance of ReflectionAttribute would need to maintain a reference
to the reflector it was created from.
The ReflectionAttribute class would need an internal static property
with a stack of ReflectionAttribute instances, OR of Reflector
instances, depending which version of the proposal is chosen.
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.
Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).
Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.php
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.
The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.
I can create an RFC, if I get the Karma :)
But, perhaps we want to discuss a bit first.
-- Andreas
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)
snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.php
Hey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.
I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yes this means logic dependant on the reflector has to be delayed until the method is called. I think this is an acceptable payoff for the solution: it only applies to attributes that explicitly opt in to receive the Reflector, and it helps to not encourage storing the reflector in a property.
In theory I guess it could call a static named constructor, but the other arguments would have to be an array, which would end up being quite clunky if the goal is to derive default argument values from the reflector.
I'm really looking forward to this feature, thanks for introducing this discussion/RFC!
Cheers
Stephen
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.
Yes this means logic dependant on the reflector has to be delayed until the method is called. I think this is an acceptable payoff for the solution: it only applies to attributes that explicitly opt in to receive the Reflector, and it helps to not encourage storing the reflector in a property.
yep, same tradeoff I mentioned in the other mail.
In theory I guess it could call a static named constructor, but the other arguments would have to be an array, which would end up being quite clunky if the goal is to derive default argument values from the reflector.
I'm really looking forward to this feature, thanks for introducing this discussion/RFC!
Cheers
Stephen
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.
Yes, that is what AttributeUtils does today, almost exactly. From the docs:
#[\Attribute]
class AttribWithName implements FromReflectionClass
{
public readonly string $name;
public function __construct(?string $name = null)
{
if ($name) {
$this->name = $name;
}
}
public function fromReflection(\ReflectionClass $subject): void
{
$this->name ??= $subject->getShortName();
}
}
(There's a different interface for each type of thing an attribute can be put on because the parameter type is different.)
--Larry Garfield
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.
Hi Andreas,
I guess the key difference I wanted to highlight is that the existing discussion keeps referencing it as a "setter" - which I think from user land at least will generally be understood to mean setting a property on an object - which then adapted into Larry's mention of specifically setting a property before the constructor is run.
I think it's a bad idea to reference this concept as "setting a property" - my understanding is that it's never a good idea to hang onto Reflector objects longer than absolutely necessary, so I don't think this feature should then result in people doing that due to the impression it gives (i.e. if it was referred to as "setReflection<subtype>()" or if the description for it is "allows an Attribute to store the Reflector target it was declared on" etc)
Cheers
Stephen
Yes this means logic dependant on the reflector has to be delayed until the method is called. I think this is an acceptable payoff for the solution: it only applies to attributes that explicitly opt in to receive the Reflector, and it helps to not encourage storing the reflector in a property.
yep, same tradeoff I mentioned in the other mail.
In theory I guess it could call a static named constructor, but the other arguments would have to be an array, which would end up being quite clunky if the goal is to derive default argument values from the reflector.
I'm really looking forward to this feature, thanks for introducing this discussion/RFC!
Cheers
Stephen
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.Hi Andreas,
I guess the key difference I wanted to highlight is that the existing discussion keeps referencing it as a "setter" - which I think from user land at least will generally be understood to mean setting a property on an object - which then adapted into Larry's mention of specifically setting a property before the constructor is run.
I guess the main reason it "keeps" doing that is that an email is not
a wiki page that could be updated :)
I am happy to use different terminology in an RFC.
(with the current plan, it would only be mentioned under "alternatives
that were considered")
"method injection" seems fine.
But then how would we name such a method?
In Larry's library it is ->fromReflection(..), but this is something I
would typically use for static factories.
To me, ->setReflector() is still ok even if internally it is not a
setter. The method describes the contract, not what happens inside.
Another idea would be ->tellAboutReflector() or maybe ->injectTargetReflector()?
I think it's a bad idea to reference this concept as "setting a property" - my understanding is that it's never a good idea to hang onto Reflector objects longer than absolutely necessary, so I don't think this feature should then result in people doing that due to the impression it gives (i.e. if it was referred to as "setReflection<subtype>()" or if the description for it is "allows an Attribute to store the Reflector target it was declared on" etc)
Now, to resolve the controversial part of this discussion.
We could reduce the RFC to the uncontroversial part:
Provide a ReflectionAttribute->getTargetReflector().
With this, the rest of the proposal can be implemented in userland
using debug_backtrace()
.
https://3v4l.org/Ilrqm#vnull
#[Attribute]
class MyAttribute {
public function __construct() {
$target_reflector =
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS |
DEBUG_BACKTRACE_PROVIDE_OBJECT, 3)[1]['object'];
}
}
Yes it feels dirty, but now it is now longer something we have to
argue about in this list :)
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.Hi Andreas,
I guess the key difference I wanted to highlight is that the existing discussion keeps referencing it as a "setter" - which I think from user land at least will generally be understood to mean setting a property on an object - which then adapted into Larry's mention of specifically setting a property before the constructor is run.
I guess the main reason it "keeps" doing that is that an email is not
a wiki page that could be updated :)
I am happy to use different terminology in an RFC.
(with the current plan, it would only be mentioned under "alternatives
that were considered")
"method injection" seems fine.But then how would we name such a method?
In Larry's library it is ->fromReflection(..), but this is something I
would typically use for static factories.
To me, ->setReflector() is still ok even if internally it is not a
setter. The method describes the contract, not what happens inside.
Another idea would be ->tellAboutReflector() or maybe ->injectTargetReflector()?I think it's a bad idea to reference this concept as "setting a property" - my understanding is that it's never a good idea to hang onto Reflector objects longer than absolutely necessary, so I don't think this feature should then result in people doing that due to the impression it gives (i.e. if it was referred to as "setReflection<subtype>()" or if the description for it is "allows an Attribute to store the Reflector target it was declared on" etc)
Now, to resolve the controversial part of this discussion.
We could reduce the RFC to the uncontroversial part:
Provide a ReflectionAttribute->getTargetReflector().With this, the rest of the proposal can be implemented in userland
usingdebug_backtrace()
.
https://3v4l.org/Ilrqm#vnull#[Attribute]
class MyAttribute {
public function __construct() {
$target_reflector =
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS |
DEBUG_BACKTRACE_PROVIDE_OBJECT, 3)[1]['object'];
}
}Yes it feels dirty, but now it is now longer something we have to
argue about in this list :)
Why do we have to call a constructor before we initialize a property? https://3v4l.org/srdM6
You can do this in regular php, but it is even simpler in the engine.
— Rob
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.Hi Andreas,
I guess the key difference I wanted to highlight is that the existing discussion keeps referencing it as a "setter" - which I think from user land at least will generally be understood to mean setting a property on an object - which then adapted into Larry's mention of specifically setting a property before the constructor is run.
I guess the main reason it "keeps" doing that is that an email is not
a wiki page that could be updated :)
I am happy to use different terminology in an RFC.
(with the current plan, it would only be mentioned under "alternatives
that were considered")
"method injection" seems fine.But then how would we name such a method?
In Larry's library it is ->fromReflection(..), but this is something I
would typically use for static factories.
To me, ->setReflector() is still ok even if internally it is not a
setter. The method describes the contract, not what happens inside.
Another idea would be ->tellAboutReflector() or maybe ->injectTargetReflector()?I think it's a bad idea to reference this concept as "setting a property" - my understanding is that it's never a good idea to hang onto Reflector objects longer than absolutely necessary, so I don't think this feature should then result in people doing that due to the impression it gives (i.e. if it was referred to as "setReflection<subtype>()" or if the description for it is "allows an Attribute to store the Reflector target it was declared on" etc)
Now, to resolve the controversial part of this discussion.
We could reduce the RFC to the uncontroversial part:
Provide a ReflectionAttribute->getTargetReflector().With this, the rest of the proposal can be implemented in userland
usingdebug_backtrace()
.
https://3v4l.org/Ilrqm#vnull#[Attribute]
class MyAttribute {
public function __construct() {
$target_reflector =
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS |
DEBUG_BACKTRACE_PROVIDE_OBJECT, 3)[1]['object'];
}
}Yes it feels dirty, but now it is now longer something we have to
argue about in this list :)Why do we have to call a constructor before we initialize a property? https://3v4l.org/srdM6
You can do this in regular php, but it is even simpler in the engine.
— Rob
This is interesting, but I still add some logistical clutter to the
attribute class.
It does bring some advantage over post-construction method injection,
because you can do the logistics in a trait, and then in the
constructor you have all the information available.
But, even if that part is hidden away in the engine, now we have a
property that we probably want to unset later.
-- Andreas
Sent from my iPhone
On Wed, Jul 2, 2025, at 5:26 PM, Andreas Hennings wrote:
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
A setter method injection is what I did in AttributeUtils, because it was the only real option. Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
--Larry Garfield
As someone that's written yet another userland "solution" for this problem, I have an alternative solution, based somewhat on an internalised concept of "never store Reflectors".
Introduce an interface "ReflectorAttribute" (bike shedding to come); which accepts a single Reflector argument.
If the attribute implements the interface, the method is called immediately following instantiation.
Yep, this is the "method injection" mentioned by Larry, or what I
referred to as "setter injection".
I have not seen your library, but I assume that's where it is going.Hi Andreas,
I guess the key difference I wanted to highlight is that the existing discussion keeps referencing it as a "setter" - which I think from user land at least will generally be understood to mean setting a property on an object - which then adapted into Larry's mention of specifically setting a property before the constructor is run.
I guess the main reason it "keeps" doing that is that an email is not
a wiki page that could be updated :)
I am happy to use different terminology in an RFC.
(with the current plan, it would only be mentioned under "alternatives
that were considered")
"method injection" seems fine.But then how would we name such a method?
In Larry's library it is ->fromReflection(..), but this is something I
would typically use for static factories.
To me, ->setReflector() is still ok even if internally it is not a
setter. The method describes the contract, not what happens inside.
Another idea would be ->tellAboutReflector() or maybe ->injectTargetReflector()?I think it's a bad idea to reference this concept as "setting a property" - my understanding is that it's never a good idea to hang onto Reflector objects longer than absolutely necessary, so I don't think this feature should then result in people doing that due to the impression it gives (i.e. if it was referred to as "setReflection<subtype>()" or if the description for it is "allows an Attribute to store the Reflector target it was declared on" etc)
Now, to resolve the controversial part of this discussion.
We could reduce the RFC to the uncontroversial part:
Provide a ReflectionAttribute->getTargetReflector().With this, the rest of the proposal can be implemented in userland
usingdebug_backtrace()
.
https://3v4l.org/Ilrqm#vnull#[Attribute]
class MyAttribute {
public function __construct() {
$target_reflector =
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS |
DEBUG_BACKTRACE_PROVIDE_OBJECT, 3)[1]['object'];
}
}Yes it feels dirty, but now it is now longer something we have to
argue about in this list :)Why do we have to call a constructor before we initialize a property? https://3v4l.org/srdM6
You can do this in regular php, but it is even simpler in the engine.
— Rob
This is interesting, but I still add some logistical clutter to the
attribute class.
It does bring some advantage over post-construction method injection,
because you can do the logistics in a trait, and then in the
constructor you have all the information available.
But, even if that part is hidden away in the engine, now we have a
property that we probably want to unset later.-- Andreas
I haven’t read the entire thread yet, but why would you want to unset it? In my experience of working with attributes, they have a very short lifetime (usually to the end of the loop/function, and no more) since they’re just metadata. The biggest boon I see here is the ability to easily encapsulate some logic in my attributes without relying on 3rd party behavior. If some library decides to keep them around, I will assume they know what they’re doing.
— Rob
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)snip
Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See
https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpHey, I know that guy! :-)
Another userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.I am unsurprisingly in favor of finding a solution here, as there are innumerable cases where you need the reflectable that the attribute is on; the most common for me is using the name/type of a property as defaults for the attribute.
However, I am very skeptical about a stateful global value as the solution. We've tried very hard to remove those from PHP, mostly successfully. Adding another one back in feels like a major step backwards, and a great place for weird bugs to hide.
Of course you would say that :)
And I might normally agree.
In this case I find the tradeoff to be acceptable, when compared to
the alternatives, and when considering the most common ways this would
be used.
Typically this would be called in a way similar to func_get_args()
or
to using the $this pointer.
A typical global state problem is with set_error_handler()
and
restore_error_handler()
.
Here you need to work with try/finally to fully avoid side effects.
In the current proposal, that try/finally is already built-in.
In general, this state will be the same before and after any operation.
(I have not fully thought about generators and fibers, but I don't see
this becoming a problem.)
One benefit of the current proposal is that it does not introduce new
language features, but really quite boring static methods and
properties.
A setter method injection is what I did in AttributeUtils, because it was the only real option.
In my experience, this alternative leads to more complexity in
attribute classes.
The constructor needs to populate the attribute with temporary values,
which are then replaced in that separate method.
(I call that a "setter", but only because it behaves like one from the
outside, internally we usually don't want to store the reflector).
Also, we can never be fully sure whether an attribute instance is
"complete" or not.
If the attribute instance is coming from
$reflection_attribute->newInstance(), we can be confident that the
->injectReflector() or has been called.
But if the attribute was instantiated manually with "new ...", it
could as well be incomplete.
With ReflectionAttribute::getCurrentTargetReflector(), we can fail
early in the attribute constructor, by throwing on NULL.
Alternatively, I suppose core could use property setter injection (either a magically named property like $__reflector, or a property that itself has an attribute on it, etc.). That would allow it to be set before the constructor is called, and with property hooks would allow processing either immediately or later in the constructor. The downside here is that Attribute are, generally, serializable, but a Reflection object is not. So if someone wanted a serializable attribute they would have to accept the property, use it, and then remember to unset it at some point. That's clumsy.
Exactly.
-- Andreas
--Larry Garfield
A setter method injection is what I did in AttributeUtils, because it was the only real option.
In my experience, this alternative leads to more complexity in
attribute classes.
The constructor needs to populate the attribute with temporary values,
which are then replaced in that separate method.
(I call that a "setter", but only because it behaves like one from the
outside, internally we usually don't want to store the reflector).Also, we can never be fully sure whether an attribute instance is
"complete" or not.
If the attribute instance is coming from
$reflection_attribute->newInstance(), we can be confident that the
->injectReflector() or has been called.
But if the attribute was instantiated manually with "new ...", it
could as well be incomplete.
With ReflectionAttribute::getCurrentTargetReflector(), we can fail
early in the attribute constructor, by throwing on NULL.
I ran into the "completeness problem" in AttributeUtils as well. In my case, it's not just reflection; there's a whole bunch of other things that can be passed back to the attribute to give it more context. My eventual solution was to also have a Finalizable
interface that is guaranteed to be the last thing called, so the attribute can handle any last-minute defaults. I don't love it, but given the severe limitations of readonly
it was the best I could do. (With aviz today, I could likely do better.)
Basically, any time you have multi-step construction you will have a period where the state is potentially incomplete. While passing the reflection target in is one such multi-step case, there are lots of others, so in advanced cases (most of what I do) the object will always have an incomplete phase, and that's unavoidable. So I'm not that worried about there being a brief incomplete-gap in time in core, because I'll always have one anyway, and I've already figured out how to handle it.
--Larry Garfield
A setter method injection is what I did in AttributeUtils, because it was the only real option.
In my experience, this alternative leads to more complexity in
attribute classes.
The constructor needs to populate the attribute with temporary values,
which are then replaced in that separate method.
(I call that a "setter", but only because it behaves like one from the
outside, internally we usually don't want to store the reflector).Also, we can never be fully sure whether an attribute instance is
"complete" or not.
If the attribute instance is coming from
$reflection_attribute->newInstance(), we can be confident that the
->injectReflector() or has been called.
But if the attribute was instantiated manually with "new ...", it
could as well be incomplete.
With ReflectionAttribute::getCurrentTargetReflector(), we can fail
early in the attribute constructor, by throwing on NULL.
I somehow missed this reply earlier...
I ran into the "completeness problem" in AttributeUtils as well. In my case, it's not just reflection; there's a whole bunch of other things that can be passed back to the attribute to give it more context.
There is a trade-off how much logic should live in the attribute vs
the code that parses the attribute.
In symfony DI there is a pattern where you register a callback for an
attribute class, which would allow to make the attribute agnostic of
the application or framework details.
E.g. you could have the same attribute for different DI systems.
(Perhaps this is not the reason why symfony does it in this way.)
On the other hand, keeping logic in the attribute class also has its benefits.
I did play around with dedicated interfaces for attributes per application.
I am not sure I like the overly generic interfaces I see in
AttributeUtils. But I would have to look deeper into it.
My eventual solution was to also have a
Finalizable
interface that is guaranteed to be the last thing called, so the attribute can handle any last-minute defaults. I don't love it, but given the severe limitations ofreadonly
it was the best I could do. (With aviz today, I could likely do better.)
The attribute object does not have to be your final "collectable".
You can have an attribute with a method that returns something else.
In fact I strongly prefer to not pass attribute instances around
everywhere, because they often contain clutter that becomes useless
after the initial discovery.
E.g.
$reflection_attributes =
$reflection_class->getAttributes(ServiceDefinition::class)->getAttributes();
$attribute_instance = $reflection_attributes[0]->newInstance();
$service_definitions =
$attribute_instance->getServiceDefinitions(...$more_args);
Or
$reflection_attributes =
$reflection_method->getAttributes(RouteModifier::class)->getAttributes();
$route = new Route();
foreach ($reflection_attributes as $reflection_attribute) {
$reflection_attribute->newInstance()->modifyRoute($route, ...$more_args);
}
return $route;
This way, you have a complete object in each step, but only the final
object is the one you want to "collect".
Basically, any time you have multi-step construction you will have a period where the state is potentially incomplete.
It is not just about possible incompleteness, it is about being
deterministic as early as possible.
E.g. if two people enter a room, it could make some things much
easier if you know whether they are brother and sister, or a married
couple.
While passing the reflection target in is one such multi-step case, there are lots of others, so in advanced cases (most of what I do)...
When looking at existing attributes in e.g. symfony or Drupal, or my
own experiments, I don't see the huge need to inject other things into
the attribute.
The reflector seems really the most commonly useful to me.
E.g. an attribute on a method/function might have a parameter that is
optional or required depending on the method's return type.
Checking that in the constructor allows us to fail early, and makes
everything more deterministic.
... the object will always have an incomplete phase, and that's unavoidable. So I'm not that worried about there being a brief incomplete-gap in time in core, because I'll always have one anyway, and I've already figured out how to handle it.
I find that in most cases it can actually be avoided.
But I have not really looked into your code much. I have seen some
packages in packagist that use attributesutil, but I would need more
time to study these.
-- Andreas
--Larry Garfield
A setter method injection is what I did in AttributeUtils, because it was the only real option.
In my experience, this alternative leads to more complexity in
attribute classes.
The constructor needs to populate the attribute with temporary values,
which are then replaced in that separate method.
(I call that a "setter", but only because it behaves like one from the
outside, internally we usually don't want to store the reflector).Also, we can never be fully sure whether an attribute instance is
"complete" or not.
If the attribute instance is coming from
$reflection_attribute->newInstance(), we can be confident that the
->injectReflector() or has been called.
But if the attribute was instantiated manually with "new ...", it
could as well be incomplete.
With ReflectionAttribute::getCurrentTargetReflector(), we can fail
early in the attribute constructor, by throwing on NULL.I somehow missed this reply earlier...
I ran into the "completeness problem" in AttributeUtils as well. In my case, it's not just reflection; there's a whole bunch of other things that can be passed back to the attribute to give it more context.
There is a trade-off how much logic should live in the attribute vs
the code that parses the attribute.
In symfony DI there is a pattern where you register a callback for an
attribute class, which would allow to make the attribute agnostic of
the application or framework details.
E.g. you could have the same attribute for different DI systems.
(Perhaps this is not the reason why symfony does it in this way.)
On the other hand, keeping logic in the attribute class also has its benefits.I did play around with dedicated interfaces for attributes per application.
I am not sure I like the overly generic interfaces I see in
AttributeUtils. But I would have to look deeper into it.
Overly generic? I don't follow. The interfaces in AttributeUtils are all, well, AttributeUtils interfaces. You use them to opt-in to specific AU functionality. AU is framework agnostic, but the interface are naturally library-specific.
My eventual solution was to also have a
Finalizable
interface that is guaranteed to be the last thing called, so the attribute can handle any last-minute defaults. I don't love it, but given the severe limitations ofreadonly
it was the best I could do. (With aviz today, I could likely do better.)The attribute object does not have to be your final "collectable".
You can have an attribute with a method that returns something else.
In fact I strongly prefer to not pass attribute instances around
everywhere, because they often contain clutter that becomes useless
after the initial discovery.
Possibly, but my experience with AU is that it's quite convenient to have the attribute be the stored metadata. The methods on the attributes are mostly just to precompute some derived information (which could be a lot or a little). The stored/cached object is only the size of its properties, so as long as there's no set temporary properties there's no clutter to worry about.
Basically, any time you have multi-step construction you will have a period where the state is potentially incomplete.
It is not just about possible incompleteness, it is about being
deterministic as early as possible.
It is always deterministic, as long as the order things happen is consistent.
E.g. if two people enter a room, it could make some things much
easier if you know whether they are brother and sister, or a married
couple.
... I have no earthly idea what you just said.
While passing the reflection target in is one such multi-step case, there are lots of others, so in advanced cases (most of what I do)...
When looking at existing attributes in e.g. symfony or Drupal, or my
own experiments, I don't see the huge need to inject other things into
the attribute.
The reflector seems really the most commonly useful to me.E.g. an attribute on a method/function might have a parameter that is
optional or required depending on the method's return type.
Checking that in the constructor allows us to fail early, and makes
everything more deterministic.
A key part of the design of AttributeUtils is that you always parse a class; if there are attributes on a method in that class, they get parsed and then passed into the class attribute. Similarly for properties, constants, etc. You can also have "sub attributes", which allow multiple attributes on the same target to be smushed together. All of those involve passing the attributes of the child-target to the parent-target's attribute.
I agree, the reflector is the most critical and the part that would most benefit from help from the engine. But there are other valid use cases for multi-step build up.
--Larry Garfield
A setter method injection is what I did in AttributeUtils, because it was the only real option.
In my experience, this alternative leads to more complexity in
attribute classes.
The constructor needs to populate the attribute with temporary values,
which are then replaced in that separate method.
(I call that a "setter", but only because it behaves like one from the
outside, internally we usually don't want to store the reflector).Also, we can never be fully sure whether an attribute instance is
"complete" or not.
If the attribute instance is coming from
$reflection_attribute->newInstance(), we can be confident that the
->injectReflector() or has been called.
But if the attribute was instantiated manually with "new ...", it
could as well be incomplete.
With ReflectionAttribute::getCurrentTargetReflector(), we can fail
early in the attribute constructor, by throwing on NULL.I somehow missed this reply earlier...
I ran into the "completeness problem" in AttributeUtils as well. In my case, it's not just reflection; there's a whole bunch of other things that can be passed back to the attribute to give it more context.
There is a trade-off how much logic should live in the attribute vs
the code that parses the attribute.
In symfony DI there is a pattern where you register a callback for an
attribute class, which would allow to make the attribute agnostic of
the application or framework details.
E.g. you could have the same attribute for different DI systems.
(Perhaps this is not the reason why symfony does it in this way.)
On the other hand, keeping logic in the attribute class also has its benefits.I did play around with dedicated interfaces for attributes per application.
I am not sure I like the overly generic interfaces I see in
AttributeUtils. But I would have to look deeper into it.Overly generic? I don't follow. The interfaces in AttributeUtils are all, well, AttributeUtils interfaces. You use them to opt-in to specific AU functionality. AU is framework agnostic, but the interface are naturally library-specific.
I may have be wrong on this one.
What I think to understand now is that your library allows e.g. your
class attribute to know about attributes on other symbols (e.g.
properties) in the same class.
This is ok, but other libraries might want to do these mechanics
outside of the attribute.
My eventual solution was to also have a
Finalizable
interface that is guaranteed to be the last thing called, so the attribute can handle any last-minute defaults. I don't love it, but given the severe limitations ofreadonly
it was the best I could do. (With aviz today, I could likely do better.)The attribute object does not have to be your final "collectable".
You can have an attribute with a method that returns something else.
In fact I strongly prefer to not pass attribute instances around
everywhere, because they often contain clutter that becomes useless
after the initial discovery.Possibly, but my experience with AU is that it's quite convenient to have the attribute be the stored metadata. The methods on the attributes are mostly just to precompute some derived information (which could be a lot or a little). The stored/cached object is only the size of its properties, so as long as there's no set temporary properties there's no clutter to worry about.
Basically, any time you have multi-step construction you will have a period where the state is potentially incomplete.
It is not just about possible incompleteness, it is about being
deterministic as early as possible.It is always deterministic, as long as the order things happen is consistent.
E.g. if two people enter a room, it could make some things much
easier if you know whether they are brother and sister, or a married
couple.... I have no earthly idea what you just said.
If you have a box with a cat in it, it can be useful to know whether
that cat is dead or alive, and the sooner you know it the better.
All I do here is advertise for having the target reflector available
in the constructor.
There is some bad taste of global state, but I don't see relevant
scenarios where it would manifest as something bad, with that proposed
self-clearing mechanism.
This said, I can easily imagine more than one solution to co-exist.
For many cases, the method injection will be just fine.
-- Andreas
While passing the reflection target in is one such multi-step case, there are lots of others, so in advanced cases (most of what I do)...
When looking at existing attributes in e.g. symfony or Drupal, or my
own experiments, I don't see the huge need to inject other things into
the attribute.
The reflector seems really the most commonly useful to me.E.g. an attribute on a method/function might have a parameter that is
optional or required depending on the method's return type.
Checking that in the constructor allows us to fail early, and makes
everything more deterministic.A key part of the design of AttributeUtils is that you always parse a class; if there are attributes on a method in that class, they get parsed and then passed into the class attribute. Similarly for properties, constants, etc. You can also have "sub attributes", which allow multiple attributes on the same target to be smushed together. All of those involve passing the attributes of the child-target to the parent-target's attribute.
I agree, the reflector is the most critical and the part that would most benefit from help from the engine. But there are other valid use cases for multi-step build up.
--Larry Garfield
This topic was discussed in the past as "Declaration-aware
attributes", and mentioned in the discussion to "Amendments to
Attributes".
I now want to propose a close-to-RFC iteration of this.
(I don't have RFC Karma, my wiki account is "Andreas Hennings (donquixote)")
Primary proposal
I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrentTargetReflector(): ?Reflector
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the reflector of the symbol on which the attribute is found.
(in other words, during
$reflector->getAttributes()[$i]->newInstance(), it will return
$reflector.)
During the execution of
ReflectionAttribute::invokeWithTargetAttribute($target, $callback), it
will return $target.
If the call stack contains multiple calls to the above mentioned
methods, only the closest/deepest one counts.
(This means that php needs to maintain a stack of reflectors.)static ReflectionAttribute::invokeWithTargetReflector(?Reflector
$target, callable $callback): void
This will invoke $callback, with no arguments.
During the invocation,
ReflectionAttribute::getCurrentTargetReflector() will return $target.
(This allows testing attribute classes without using them as attributes.)ReflectionAttribute->getTargetReflector(): \Reflector
This returns the reflector of the symbol on which the attribute is found.
This method mostly exists for completeness: The ReflectionAttribute
must store the target reflector, so one would expect to be able to
obtain it.Example
#[Attribute(Attribute::TARGET_PARAMETER)]
class MyAutowireAttribute {
public readonly string $serviceId;
public function __construct() {
$reflectionParameter = ReflectionAttribute::getCurrentTargetReflector();
if ($reflectionParameter === null) {
throw new \RuntimeException('This class can only be instantiated
as an attribute.');
}
assert($reflectionParameter instanceof ReflectionParameter);
// @todo Some validation.
$this->serviceId = (string) $reflectionParameter->getType();
}
}class MyService {
public function __construct(#[MyAutowireAttribute] private readonly
MyOtherService $otherService) {}
}// Regular usage.
$reflector = (new ReflectionMethod(MyService::class,
'__construct'))->getParameters()[0];
$reflection_attribute = $reflector->getAttributes()[0];
assert($reflection_attribute->getTargetReflector() === $reflector);
$attribute = $reflection_attribute->newInstance();
assert($attribute instanceof MyAutowireAttribute);
assert($attribute->serviceId === MyOtherService::class);// Simulation mode for tests.
$reflector = (new ReflectionFunction(fn (MyOtherService $arg) =>
null))->getParameters()[0];
$attribute = ReflectionAttribute::invokeWithTargetReflector($reflector,
fn () => new MyAutowireAttribute());
assert($attribute instanceof MyAutowireAttribute);
assert($attribute->serviceId === MyOtherService::class);// Nested calls.
function test(\Reflector $a, \Reflector $b) {
assert(ReflectionAttribute::getCurrentTargetReflector() === null);
ReflectionAttribute::invokeWithTargetReflector($a, function () use ($a, $b) {
assert(ReflectionAttribute::getCurrentTargetReflector() === $a);
ReflectionAttribute::invokeWithTargetReflector($b, function () use ($b) {
assert(ReflectionAttribute::getCurrentTargetReflector() === $b);
ReflectionAttribute::invokeWithTargetReflector(null, function () {
assert(ReflectionAttribute::getCurrentTargetReflector() === null);
});
});
assert(ReflectionAttribute::getCurrentTargetReflector() === $a);
});
assert(ReflectionAttribute::getCurrentTargetReflector() === null);
}
Alternative proposal
For completeness, I am also proposing an alternative version of this.
The two are not necessarily mutually exclusive, but having both would
introduce some kind of redundancy.
Personally, I prefer the first proposal (see below why).I propose to introduce 3 new methods on ReflectionAttribute.
static ReflectionAttribute::getCurrent(): ?\ReflectionAttribute
Most of the time, this will return NULL.
During the execution of ReflectionAttribute->newInstance(), it will
return the ReflectionAttribute instance on which ->newInstance() was
called.ReflectionAttribute->getTargetReflector(): \Reflector
This returns the reflector of the symbol on which the attribute is found.static ReflectionAttribute::create(\Reflector $target, string $name,
array $arguments, bool $is_repeated = false): \ReflectionAttribute
This returns a ReflectionAttribute object that behaves as if the
attribute was found on $target.
This is mostly for testing purposes.Example
#[Attribute(Attribute::TARGET_PARAMETER)]
class MyAutowireAttribute {
public readonly string $serviceId;
public function __construct() {
$reflectionParameter =
ReflectionAttribute::getCurrent()->getTargetReflector();
[..]
// @todo Some validation.
$this->serviceId = (string) $reflectionParameter->getType();
}
}class MyService {
public function __construct(#[MyAutowireAttribute] private readonly
MyOtherService $otherService) {}
}// Regular usage.
$reflection_parameter = (new ReflectionMethod(MyService::class,
'__construct'))->getParameters()[0];
$reflection_attribute = $reflection_parameter->getAttributes()[0];
assert($reflection_attribute->getTargetReflector() === $reflection_parameter);
$attribute_instance = $reflectionAttribute->newInstance();
assert($attribute_instance instanceof MyAutowireAttribute);
assert($attribute_instance->serviceId === MyOtherService::class);// Simulation mode for tests.
$reflection_parameter = (new ReflectionFunction(fn (MyOtherService
$arg) => null))->getParameters()[0];
$reflection_attribute =
ReflectionAttribute::create($reflection_parameter,
MyAutowireAttribute::class, []);
assert($reflection_attribute->getTargetReflector() === $reflection_parameter);
assert($reflection_attribute->getTargetReflector()->getAttributes() === []);
$attribute_instance = $reflection_attribute->newInstance();
assert($attribute_instance instanceof MyAutowireAttribute);
assert($attribute_instance->serviceId === MyOtherService::class);Why do I like this version less?
For most use cases, the attribute instance does not need access to the
ReflectionAttribute object.For the testing scenario, the "fake" ReflectionAttribute object feels
strange, because:
- ReflectionAttribute::create($reflector,
...)->getTargetReflector()->getAttributes() may be empty, or does not
contain the fake attribute.- ReflectionAttribute::create($reflector, ...)->isRepeated() is
completely meaningless.- If we add ReflectionAttribute->getPosition() in the future, the
result from the "fake" one will be off.Any code that relies on these methods of ReflectionAttribute to look
for other attributes on the same symbol may break with a "fake"
instance.Details, thoughts
The return type for ReflectionAttribute::getCurrentTargetReflector()
would not simply be "Reflector", but
"\ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant",
assuming that no dedicated interface is introduced until then.For ReflectionAttribute::getCurrentTargetReflector(), I was wondering
if instead we may want a function like current_attribute_target().
This would be inspired byfunc_get_args()
.
In the end, the method is still related to reflection, so for now I
decided to keep it here.For ReflectionAttribute::invokeWithTargetReflector(), we could instead
introduce something with ::push() and ::pop().
This would be more flexible, but it would also lead to people
forgetting to remove a reflector that was set temporarily, leaving the
system polluted.For ReflectionAttribute::invokeWithTargetReflector() returning NULL,
we could instead have it throw an exception.
But then people might want an alternative method or mode that does
returnNULL
when called outside ->newInstance().
By having it return NULL, the calling code can decide whether and
which exception to throw.Implementation
An instance of ReflectionAttribute would need to maintain a reference
to the reflector it was created from.
The ReflectionAttribute class would need an internal static property
with a stack of ReflectionAttribute instances, OR of Reflector
instances, depending which version of the proposal is chosen.Other alternatives
In older discussions, it was suggested to provide the target reflector
as a special constructor parameter.
This is problematic because an attribute expression #[MyAttribute('a',
'b', 'c')] expects to pass values to all the parameters.Another idea was to provide the target reflector through a kind of
setter method on the attribute class.
This can work, but it makes attribute classes harder to write, because
the constructor does not have all the information.
It may also prevent attribute classes from being stateless (depending
how we define stateless).Userland implementations
One userland implementation that was mentioned in this list in the
past is in the 'crell/attributeutils' package.
This one uses a kind of setter injection for the target reflector.
See https://github.com/Crell/AttributeUtils/blob/master/src/FromReflectionClass.phpAnother userland implementation is in the
'ock/reflector-aware-attributes' package.
https://github.com/ock-php/reflector-aware-attributes (I created that one)
This supports both a setter method and getting the target reflector
from the attribute constructor.The problem with any userland implementation is that it only works if
the attribute is instantiated (or processed) using that userland
library.
Simply calling $reflector->getAttributes()[0]->newInstance() would
either return an instance that is incomplete, or it would break, if
the attribute class expects access to its target.
I can create an RFC, if I get the Karma :)
But, perhaps we want to discuss a bit first.-- Andreas
I created an RFC and PR with only the
ReflectionAttribute->getTargetReflector()
I still get memory leaks, says the pipeline.
https://wiki.php.net/rfc/attribute-target-reflector
https://github.com/php/php-src/pull/19066
For now this shall be considered draft, but whoever is interested may
have a look.
-- Andreas