[PHP-DEV] [RFC idea] Target-aware attributes

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 AttributeUtils/src/FromReflectionClass.php at master · Crell/AttributeUtils · GitHub

Another userland implementation is in the
'ock/reflector-aware-attributes' package.
GitHub - ock-php/reflector-aware-attributes: Provides mechanisms for attribute objects to know about the symbol they are attached to. (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 :slight_smile:
But, perhaps we want to discuss a bit first.

-- Andreas

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
AttributeUtils/src/FromReflectionClass.php at master · Crell/AttributeUtils · GitHub

Hey, I know that guy! :slight_smile:

Another userland implementation is in the
'ock/reflector-aware-attributes' package.
GitHub - ock-php/reflector-aware-attributes: Provides mechanisms for attribute objects to know about the symbol they are attached to. (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 3 Jul 2025, at 23:40, Larry Garfield <larry@garfieldtech.com> wrote:

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
AttributeUtils/src/FromReflectionClass.php at master · Crell/AttributeUtils · GitHub

Hey, I know that guy! :slight_smile:

Another userland implementation is in the
'ock/reflector-aware-attributes' package.
GitHub - ock-php/reflector-aware-attributes: Provides mechanisms for attribute objects to know about the symbol they are attached to. (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

On Thu, 3 Jul 2025 at 18:29, Larry Garfield <larry@garfieldtech.com> wrote:

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
> AttributeUtils/src/FromReflectionClass.php at master · Crell/AttributeUtils · GitHub

Hey, I know that guy! :slight_smile:

> Another userland implementation is in the
> 'ock/reflector-aware-attributes' package.
> GitHub - ock-php/reflector-aware-attributes: Provides mechanisms for attribute objects to know about the symbol they are attached to. (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 :slight_smile:
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

On Thu, 3 Jul 2025 at 19:17, Stephen Reay <php-lists@koalephant.com> wrote:

Sent from my iPhone
> On 3 Jul 2025, at 23:40, Larry Garfield <larry@garfieldtech.com> wrote:
>
> 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
>> AttributeUtils/src/FromReflectionClass.php at master · Crell/AttributeUtils · GitHub
>
> Hey, I know that guy! :slight_smile:
>
>> Another userland implementation is in the
>> 'ock/reflector-aware-attributes' package.
>> GitHub - ock-php/reflector-aware-attributes: Provides mechanisms for attribute objects to know about the symbol they are attached to. (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

On Thu, Jul 3, 2025, at 12:49 PM, Andreas Hennings wrote:

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

On Thu, Jul 3, 2025, at 12:54 PM, Andreas Hennings wrote:

> 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

On 4 Jul 2025, at 00:54, Andreas Hennings andreas@dqxtech.net wrote:

On Thu, 3 Jul 2025 at 19:17, Stephen Reay <php-lists@koalephant.com> wrote:

Sent from my iPhone

On 3 Jul 2025, at 23:40, Larry Garfield larry@garfieldtech.com wrote:

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
AttributeUtils/src/FromReflectionClass.php at master · Crell/AttributeUtils · GitHub

Hey, I know that guy! :slight_smile:

Another userland implementation is in the
‘ock/reflector-aware-attributes’ package.
GitHub - ock-php/reflector-aware-attributes: Provides mechanisms for attribute objects to know about the symbol they are attached to. (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()” 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