Skip to content

[RFC] Add #[\DelayedTargetValidation] attribute #18817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ PHP 8.5 UPGRADE NOTES
. Applying #[\Attribute] to an abstract class, enum, interface, or trait triggers
an error during compilation. Previously, the attribute could be added, but when
ReflectionAttribute::newInstance() was called an error would be thrown.
The error can be delayed from compilation to runtime using the new
#[\DelayedTargetValidation] attribute.

- DOM:
. Cloning a DOMNamedNodeMap, DOMNodeList, Dom\NamedNodeMap, Dom\NodeList,
Expand Down Expand Up @@ -180,6 +182,11 @@ PHP 8.5 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/final_promotion
. #[\Override] can now be applied to properties.
RFC: https://wiki.php.net/rfc/override_properties
. The #[\DelayedTargetValidation] attribute can be used to suppress
compile-time errors from core (or extension) attributes that are used on
invalid targets. These errors are instead reported at runtime if and when
ReflectionAttribute::newInstance() is called.
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute

- Curl:
. Added support for share handles that are persisted across multiple PHP
Expand Down Expand Up @@ -506,6 +513,11 @@ PHP 8.5 UPGRADE NOTES
hooks are final, and whether the property is virtual. This also affects
the output of ReflectionClass::__toString() when a class contains hooked
properties.
. ReflectionAttribute::newInstance() can now throw errors for internal
attributes if the attribute was applied on an invalid target and the
error was delayed from compile-time to runtime via the
#[\DelayedTargetValidation] attribute.
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute

- Session:
. session_start is stricter in regard to the option argument.
Expand Down Expand Up @@ -623,6 +635,12 @@ PHP 8.5 UPGRADE NOTES
7. New Classes and Interfaces
========================================

- Core:
. DelayedTargetValidation is an attribute that, when added, delays any errors
from *other* internal attributes about being applied to invalid targets from
compile-time to runtime.
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
Comment on lines +638 to +642
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we ever added attributes here. Makes sense, but I would just mention the class name + RFC and not the full explanation. Can you just commit the related change for NoDiscard to master?


- Curl:
. CurlSharePersistentHandle representing a share handle that is persisted
across multiple PHP requests.
Expand Down
12 changes: 12 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ PHP 8.5 INTERNALS UPGRADE NOTES
extra layer of indirection can be removed. In other cases a zval can
be heap-allocated and stored in the pointer as a minimal change to keep
compatibility.
. The `target` parameter for validator callbacks for internal attributes
can now include in the bitmask the new flags
ZEND_ATTRIBUTE_NO_TARGET_VALIDATION and
ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION. The first indicates that, if the
attribute is being applied to an invalid target, no error should be thrown
at compile time. The second indicates that validation is being performed
at runtime and that if an attribute is being applied to an invalid target,
a catchable error should be thrown. Target validation is different from
the actual functionality of the attribute; e.g. the #[\Override] attribute
will trigger an error if the function does not override a method from the
parent class or from an interface; that error does not get delayed.
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute

- Hash
. Hash functions now use proper hash_spec_result enum for return values
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
--TEST--
#[\DelayedTargetValidation] has errors at runtime
--FILE--
<?php

#[DelayedTargetValidation]
#[NoDiscard]
class Demo {

#[DelayedTargetValidation]
#[Attribute]
public const FOO = 'BAR';

#[DelayedTargetValidation]
#[Attribute]
public string $v1;

public string $v2 {
#[DelayedTargetValidation]
#[Attribute]
get => $this->v2;
#[DelayedTargetValidation]
#[Attribute]
set => $value;
}

#[DelayedTargetValidation]
#[Attribute]
public function __construct(
#[DelayedTargetValidation]
#[Attribute]
public string $v3
) {
$this->v1 = $v3;
echo __METHOD__ . "\n";
}
}

#[DelayedTargetValidation]
#[Attribute]
function demoFn() {
echo __FUNCTION__ . "\n";
}

#[DelayedTargetValidation]
#[Attribute]
const EXAMPLE = true;

$cases = [
new ReflectionClass('Demo'),
new ReflectionClassConstant('Demo', 'FOO'),
new ReflectionProperty('Demo', 'v1'),
new ReflectionProperty('Demo', 'v2')->getHook(PropertyHookType::Get),
new ReflectionProperty('Demo', 'v2')->getHook(PropertyHookType::Set),
new ReflectionMethod('Demo', '__construct'),
new ReflectionParameter([ 'Demo', '__construct' ], 'v3'),
new ReflectionProperty('Demo', 'v3'),
new ReflectionFunction('demoFn'),
new ReflectionConstant('EXAMPLE'),
];
foreach ($cases as $r) {
echo str_repeat("*", 20) . "\n";
echo $r . "\n";
$attributes = $r->getAttributes();
var_dump($attributes);
try {
$attributes[1]->newInstance();
} catch (Error $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
}

?>
--EXPECTF--
********************
Class [ <user> <iterateable> class Demo ] {
@@ %s %d-%d

- Constants [1] {
Constant [ public string FOO ] { BAR }
}

- Static properties [0] {
}

- Static methods [0] {
}

- Properties [3] {
Property [ public string $v1 ]
Property [ public string $v2 { get; set; } ]
Property [ public string $v3 ]
}

- Methods [1] {
Method [ <user, ctor> public method __construct ] {
@@ %s %d - %d

- Parameters [1] {
Parameter #0 [ <required> string $v3 ]
}
}
}
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "NoDiscard"
}
}
Error: Attribute "NoDiscard" cannot target class (allowed targets: function, method)
********************
Constant [ public string FOO ] { BAR }

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target class constant (allowed targets: class)
********************
Property [ public string $v1 ]

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target property (allowed targets: class)
********************
Method [ <user> public method $v2::get ] {
@@ %s %d - %d

- Parameters [0] {
}
- Return [ string ]
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target method (allowed targets: class)
********************
Method [ <user> public method $v2::set ] {
@@ %s %d - %d

- Parameters [1] {
Parameter #0 [ <required> string $value ]
}
- Return [ void ]
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target method (allowed targets: class)
********************
Method [ <user, ctor> public method __construct ] {
@@ %s %d - %d

- Parameters [1] {
Parameter #0 [ <required> string $v3 ]
}
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target method (allowed targets: class)
********************
Parameter #0 [ <required> string $v3 ]
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target parameter (allowed targets: class)
********************
Property [ public string $v3 ]

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target property (allowed targets: class)
********************
Function [ <user> function demoFn ] {
@@ %s %d - %d
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target function (allowed targets: class)
********************
Constant [ bool EXAMPLE ] { 1 }

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target constant (allowed targets: class)
Loading
Loading