====== PHP RFC: #[\DelayedTargetValidation] attribute ====== * Version: 0.1 * Date: 2025.06.17 * Author: Daniel Scherzer, daniel.e.scherzer@gmail.com * Status: Under Discussion * First Published at: https://wiki.php.net/rfc/DelayedTargetValidation_attribute ===== Introduction ===== Attributes were first introduced in [[rfc:attributes_v2]], with the current syntax adopted in [[rfc:shorter_attribute_syntax_change]]. Enums were added in [[rfc:enumerations]], with enums targeted the same as classes and enum cases targeted the same as class constants. With the addition of compile-time non-class constants from [[rfc:attributes-on-constants]], attributes can now target: * functions (including closures and short closures) * classes (including anonymous classes), interfaces, traits, enums * class constants or enum cases * class properties * class methods * function/method parameters * compile-time non-class constants The targets that an attribute supports are indicated in the declaration, e.g. the #[\Override] attribute is declared with #[Attribute(Attribute::TARGET_METHOD)] final class Override { public function __construct() {} } which indicates that the attribute can be used only for methods. Although some of the implementation details changed since the [[rfc:attributes_v2]] RFC, a key distinction that that RFC drew still remains:
This proposal differentiates between two different kinds of attributes: * Compiler Attributes (validated at compile time) * Userland Attributes (validated during Reflection API access)
This validation includes ensuring that an attribute is only used in the places that it supports - e.g. the #[\Override] attribute cannot be used on a non-class function. However, the compile-time validation means that any errors cannot be caught, nor code gated behind version flags. Accordingly, this RFC proposes a new attribute, #[\DelayedTargetValidation], that, when applied, delays any errors from compiler attributes until runtime. ===== Proposal ===== Let there be a new attribute: #[\Attribute(\Attribute::TARGET_ALL)] final class DelayedTargetValidation { public function __construct() {} } When this attribute is present on a target, then for any other attributes provided by PHP (as opposed to defined in userland) on the same target, any errors from a bad target are ignored at compile time, and instead emitted at runtime the same way userland attributes have targets validated. ===== Example ===== Consider, for example, the #[\Override] attribute introduced in PHP 8.3. Since PHP 5.3, it has been possible for child classes to override the class constants of parent classes. However, the attribute cannot currently be used for class constants. Additionally, since at least PHP 5.0 it has been possible for child classes to override the class properties of parent classes (e.g. to expand visibility). However, the #[\Override] attribute cannot be used for class properties either. Let us suppose that in PHP 8.6, it became possible to use #[\Override] on class constants, and in PHP 8.7 it also became possible to use #[\Override] on class properties. To be clear, this RFC does not propose expanding the targets of #[\Override]; the point of #[\DelayedTargetValidation] if to allow for future compatability if a later RFC expands the targets. For the sake of demonstration, the example below assumes that the targets of #[\Override] are expanded in 8.6 and 8.7. If a library author wanted to annotate that their class constant, method, and property all override the parent class: class Base { public const NAME = ''; protected int $id; protected function getName(): string { return static::NAME; } } class Child extends Parent { #[\Override] public const NAME = 'Child'; // Just increasing visibility #[\Override] public int $id; // Add the ID to the name #[\Override] protected function getName(): string { return parent::getName() . $this->id; } } If the author wanted to make use of #[\Override] in this manner, the library would **require** PHP 8.7, because in PHP 8.6 and below, the compile-time validation of the attribute would complain. However, the author may not want to drop support for PHP 8.5 and 8.6, and thus refrain from using the attribute for a few years, //even when the code is being run on PHP 8.7//. Instead, under this RFC, the author could use: class Child extends Parent { #[\DelayedTargetValidation] #[\Override] public const NAME = 'Child'; // Just increasing visibility #[\DelayedTargetValidation] #[\Override] public int $id; // Add the ID to the name #[\Override] protected function getName(): string { return parent::getName() . $this->id; } } Which would result in the following: * On PHP 8.5 and PHP 8.6, the places where the attribute should not be used will //not// trigger a compile-time error, only a runtime error when using reflection * On PHP 8.6 (for class constants) and PHP 8.7 (for both the constant and the property) the code will benefit from the engine verifying that the property/constant is overriding the parent class. This new attribute is intended for //forward// compatibility. The runtime errors from Reflection can be caught if needed, but otherwise the code will run and the inapplicable attributes will just be ignored. To be clear, this RFC does not propose that #[\Override] be usable on class constants or properties. The inspiration for this RFC was the discussion in the internals mailing list about expanding the places where #[\Deprecated] could be used - if that attribute gets new targets for PHP 8.6, having the #[\DelayedTargetValidation] attribute in 8.5 would allow libraries to use the #[\Deprecated] attribute in more places (that PHP 8.6 supports) without triggering errors on PHP 8.5. ===== Backward Incompatible Changes ===== The DelayedTargetValidation class in the global namespace is now provided by PHP and cannot be used in userland; a GitHub search for "class DelayedTargetValidation" revealed 0 hits as of 2025.06.12, so the impact should be minimal, but noted here for completion. Code that expects ReflectionAttribute::newInstance() to never throw if the attribute is provided by PHP rather than userland may need to be adjusted. ===== Proposed PHP Version(s) ===== Next version of PHP (8.5). ===== RFC Impact ===== ==== To Existing Extensions ==== ==== To Reflection ==== ReflectionAttribute::newInstance() currently skips target validation for internal attributes, on the assumption that the validation was done at compile-time; it will need to be updated. ===== Open Issues ===== Make sure there are no open issues when the vote starts! ===== Unaffected PHP Functionality ===== The places where core attributes actually provide functionality (e.g. verifying that a method really overrides a parent methods when using #[\Override]) are unaffected by this attribute; it only delays the errors that would be reported from using the attribute in the wrong places. In the case of #[\Override], for example, it does **not** suppress errors about a method not actually overriding a parent method - that is the functionality of the attribute, rather than the target validation. ===== Future Scope ===== This section details areas where the feature might be improved in future, but that are not currently proposed in this RFC. ===== Proposed Voting Choices ===== Implement the #[\DelayedTargetValidation] attribute as described? ===== Patches and Tests ===== https://github.com/php/php-src/pull/18817 ===== Implementation ===== After the project is implemented, this section should contain - the version(s) it was merged into - a link to the git commit(s) - a link to the PHP manual entry for the feature - a link to the language specification section (if any) ===== References ===== - Recent additions to the discussion about the original #[\Deprecated] RFC, see https://news-web.php.net/php.internals/127486 - Thread about expanding #[\Deprecated] uses, https://news-web.php.net/php.internals/127305 - Attributes RFC, [[rfc:attributes_v2]] - #[\Override] attribute RFC, [[rfc:marking_overriden_methods]] - #[\Deprecated] attribute RFC, [[rfc:deprecated_attribute]] ===== Rejected Features ===== Keep this updated with features that were discussed on the mail lists.