[PHP-DEV] [RFC] [Discussion] #[\DelayedTargetValidation] attribute

Hi internals,

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

–Daniel

On Wed, Jun 18, 2025, at 01:26, Daniel Scherzer wrote:

Hi internals,

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

–Daniel

Interesting. I’d also argue for the inverse more than this, though. I’d like for my attributes to be validated during compilation instead of delayed to runtime – which, it isn’t actually. Runtime validation ONLY happens when calling ->newInstance() on ReflectionAttribute, and never before then. So, only when an attribute is actually read during reflection is it validated. Further, if you never actually instantiate it … it is never actually validated (i.e., just looking for the presence of an attribute, not the details).

https://3v4l.org/UqQKi

— Rob

Von meinem iPhone gesendet

Am 18.06.2025 um 11:26 schrieb Rob Landers rob@bottled.codes:

On Wed, Jun 18, 2025, at 01:26, Daniel Scherzer wrote:

Hi internals,

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

–Daniel

Interesting. I’d also argue for the inverse more than this, though. I’d like for my attributes to be validated during compilation instead of delayed to runtime – which, it isn’t actually. Runtime validation ONLY happens when calling ->newInstance() on ReflectionAttribute, and never before then. So, only when an attribute is actually read during reflection is it validated. Further, if you never actually instantiate it … it is never actually validated (i.e., just looking for the presence of an attribute, not the details).

https://3v4l.org/UqQKi

As Daniel’s rfc mentions you need to differentiate between compiler and userland attributes.

The compiler attributes are validated at compile time not dueing newInstance.

— Rob

On 18 June 2025 13:09:26 CEST, kontakt@beberlei.de wrote:

Von meinem iPhone gesendet

Am 18.06.2025 um 11:26 schrieb Rob Landers <rob@bottled.codes>:

On Wed, Jun 18, 2025, at 01:26, Daniel Scherzer wrote:
Hi internals,

I'd like to start the discussion for a new RFC about adding a `#[\DelayedTargetValidation]` attribute.

* RFC: PHP: rfc:delayedtargetvalidation_attribute
* Implementation: Add `#[\DelayedTargetValidation]` attribute by DanielEScherzer · Pull Request #18817 · php/php-src · GitHub

--Daniel

Interesting. I’d also argue for the inverse more than this, though. I’d like for my attributes to be validated during compilation instead of delayed to runtime -- which, it isn’t actually. Runtime validation ONLY happens when calling ->newInstance() on ReflectionAttribute, and never before then. So, only when an attribute is actually read during reflection is it validated. Further, if you never actually instantiate it ... it is never actually validated (i.e., just looking for the presence of an attribute, not the details).

Online PHP editor | output for UqQKi

As Daniel’s rfc mentions you need to differentiate between compiler and userland attributes.

The compiler attributes are validated at compile time not dueing newInstance.

— Rob

Just as an information:

We are at the moment in the proces of setting up a registry for userland attributes at the PHP-FIG.

For more information regarding that check out <https://github.com/php-fig/fig-standards/pull/1331&gt;

Cheers

Andreas

--
Andreas Heigl

On Wed, Jun 18, 2025, at 13:09, kontakt@beberlei.de wrote:

Von meinem iPhone gesendet

Am 18.06.2025 um 11:26 schrieb Rob Landers rob@bottled.codes:

On Wed, Jun 18, 2025, at 01:26, Daniel Scherzer wrote:

Hi internals,

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

–Daniel

Interesting. I’d also argue for the inverse more than this, though. I’d like for my attributes to be validated during compilation instead of delayed to runtime – which, it isn’t actually. Runtime validation ONLY happens when calling ->newInstance() on ReflectionAttribute, and never before then. So, only when an attribute is actually read during reflection is it validated. Further, if you never actually instantiate it … it is never actually validated (i.e., just looking for the presence of an attribute, not the details).

https://3v4l.org/UqQKi

As Daniel’s rfc mentions you need to differentiate between compiler and userland attributes.

The compiler attributes are validated at compile time not dueing newInstance.

— Rob

Yes, but when would it check it during runtime? It doesn’t say in the RFC. From what I can tell, it effectively disables the attribute, unless someone happens to enumerate and instantiate all the attributes (which is unlikely).

— Rob

Hi Daniel,

While I’m in favor of the RFC, I’d more like to see the default behavior of internal and userland attributes adjusted to work the same, with both having delayed validation enabled by default. Treating core attributes differently doesn’t make sense to me.

So, if that’s someone you’re willing to work on, I’d very much prefer that.
But if there are downsides to this approach or if this is too complicated for other reasons, this attribute is a net positive in my book to make forward compatibility possible.

One note on the example code:

class Base {
class Child extends Parent { \

Parent isn’t a valid class name, and I think you wanted to extend from Base here? I’d prefer the example to compile so that they can be used for documentation later on where possible.

Kind Regards,
Volker

···

Volker Dusch
Head of Engineering

Tideways GmbH
Königswinterer Str. 116
53227 Bonn
https://tideways.io/imprint

Sitz der Gesellschaft: Bonn
Geschäftsführer: Benjamin Außenhofer (geb. Eberlei)
Registergericht: Amtsgericht Bonn, HRB 22127

On Wed, Jun 18, 2025 at 5:22 AM Volker Dusch <volker@tideways-gmbh.com> wrote:

On Wed, Jun 18, 2025 at 1:28 AM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

Hi Daniel,

One note on the example code:

class Base {
class Child extends Parent { \

Parent isn’t a valid class name, and I think you wanted to extend from Base here? I’d prefer the example to compile so that they can be used for documentation later on where possible.

Kind Regards,
Volker

Fixed, thanks.

Hi Volker

On Wed, Jun 18, 2025 at 2:23 PM Volker Dusch <volker@tideways-gmbh.com> wrote:

On Wed, Jun 18, 2025 at 1:28 AM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

I'd like to start the discussion for a new RFC about adding a `#[\DelayedTargetValidation]` attribute.

* RFC: PHP: rfc:delayedtargetvalidation_attribute
* Implementation: Add `#[\DelayedTargetValidation]` attribute by DanielEScherzer · Pull Request #18817 · php/php-src · GitHub

While I'm in favor of the RFC, I'd more like to see the default behavior of internal and userland attributes adjusted to work the same, with both having delayed validation enabled by default. Treating core attributes differently doesn't make sense to me.

So, if that's someone you're willing to work on, I'd very much prefer that.

I wouldn't support that, because internal attributes with effects are
not usually instantiated.

class C {
    #[\SensitiveParameter] // This doesn't actually do anything, only
works on parameters
    public $prop;
}

I would get no indication that this attribute doesn't behave as I
expect. We have at least a few attributes with confusable targets
(SensitiveParameter, Override, Deprecated, NoDiscard). User attributes
can't have effects without at least reading them through reflection,
although granted they don't necessarily need to be instantiated, and
so might also not trigger the error.

Ilija

Le mer. 18 juin 2025 à 23:49, Ilija Tovilo <tovilo.ilija@gmail.com> a écrit :

Hi Volker

On Wed, Jun 18, 2025 at 2:23 PM Volker Dusch <volker@tideways-gmbh.com> wrote:

On Wed, Jun 18, 2025 at 1:28 AM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

While I’m in favor of the RFC, I’d more like to see the default behavior of internal and userland attributes adjusted to work the same, with both having delayed validation enabled by default. Treating core attributes differently doesn’t make sense to me.

So, if that’s someone you’re willing to work on, I’d very much prefer that.

I 100% agree with what Volker posted here.

I wouldn’t support that, because internal attributes with effects are
not usually instantiated.

class C {
#[\SensitiveParameter] // This doesn’t actually do anything, only
works on parameters
public $prop;
}

I would get no indication that this attribute doesn’t behave as I
expect. We have at least a few attributes with confusable targets
(SensitiveParameter, Override, Deprecated, NoDiscard). User attributes
can’t have effects without at least reading them through reflection,
although granted they don’t necessarily need to be instantiated, and
so might also not trigger the error.

These considerations work exactly the same for userland attributes: if I use #[Whatever] on a location that is not allowed by the declaration of the attribute, nothing will ever tell me at runtime, because nobody will ever try to read that attribute on that unexpected location.

Yet, this “silent” behavior is by design, because that’s the core of the “attributes are declarative metadata” promise. If you make them be enforced by the engine, then this promise falls apart and attributes become way less useful.

The correct solution to the validation problem is to use a static analyzer. There, one can easily spot that some attributes are not correctly placed, and one can then also ignore the report of the SA tool, because eg #[Deprecated] is used on a class on purpose for that library (e.g. for the forward compat use case).

The exception to the declarative nature of attributes that is currently allowed for engine-provided ones is detrimental to me: it allows turning them into “syntax hacks” basically (=engine-enforced-thus-not-declarative rules).

To paraphrase Volker, I’m in favor of the RFC, but only if we cannot agree on the need for internal and userland attributes to have the same conceptual roots (truly declarative ones).

Cheers,
Nicolas

On Tue, Jun 17, 2025 at 4:26 PM Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

Hi internals,

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

–Daniel

It seems a common point of discussion is the difference in behavior between internal and userland attributes, so I wanted to clarify a few things:

  • Userland attributes are always metadata, in part because of the backwards and forward compatibility that those attributes provide - the attribute does not need to exist in order to be used, it just needs to exist (and target the location) when you call ReflectionAttribute::newInstance() to instantiate
  • Internal attributes are a mix between metadata and a way to plug into the engine. There were already existing ways to plug into the engine (e.g. using magic methods or implementing the Countable or ArrayAccess interfaces) but attributes provided a way to plug into the engine when you don’t just want to add a function override, but rather something else (like indicating a parameter should be redacted in backtraces with #[\SensitiveParameter]). It would probably be impossible to do that securely with a userland attribute and userland error handler that manually redacted things…
  • Attributes were designed to have good compatibility - the syntax chosen for PHP 8.0 was, in prior versions of PHP, used for comments - so any code with attributes could also work in PHP 7.4, just without the metadata. Similarly, by not validating userland attributes until they are accessed with reflection, you can add an attribute that does not exist yet (e.g. if using different versions of a library) with minimal complications.
  • Internal attributes are validated at compile time - because they can be. Internal attributes tell the engine to do something, and it makes sense (at least to me) that if the engine cannot do that thing, there should be an error without needing to wait for ReflectionAttribute::newInstance() to be called.

But, the validation of internal attributes at compile time means that they lose the compatibility features of userland attributes, and that is what this RFC is trying to address. To be clear, I think the difference in behavior between userland and internal attributes is a) fundamentally based on the difference in capabilities (pure metadata vs engine behavior) and b) something that should not be changed without significant further investigation.

This new #[\DelayedTargetValidation] is meant to simplify things, and to partially unify the behavior of the errors - if you are getting any errors from internal attributes and want to suppress them at compile time, just add the new attribute everywhere.

-Daniel

Hi Daniel, internals,

On Wed, Jun 18, 2025, 02:29 Daniel Scherzer <daniel.e.scherzer@gmail.com> wrote:

Hi internals,

I’d like to start the discussion for a new RFC about adding a #[\DelayedTargetValidation] attribute.

How about completely disabling errors caused by a bad target at compile time?
And just ignore the attribute, that can be validated at runtime if needed.
IDEs and static code analysis would show the problem and that might be enough.


Alex