Skip to content

Commit 63acc4b

Browse files
[RFC] Add #[\DelayedTargetValidation] attribute (#18817)
https://wiki.php.net/rfc/delayedtargetvalidation_attribute
1 parent 1cff181 commit 63acc4b

32 files changed

+1765
-62
lines changed

UPGRADING

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ PHP 8.5 UPGRADE NOTES
5353
. Applying #[\Attribute] to an abstract class, enum, interface, or trait triggers
5454
an error during compilation. Previously, the attribute could be added, but when
5555
ReflectionAttribute::newInstance() was called an error would be thrown.
56+
The error can be delayed from compilation to runtime using the new
57+
#[\DelayedTargetValidation] attribute.
5658

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

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

532544
- Session:
533545
. session_start is stricter in regard to the option argument.
@@ -648,6 +660,8 @@ PHP 8.5 UPGRADE NOTES
648660
- Core:
649661
. NoDiscard attribute was added.
650662
RFC: https://wiki.php.net/rfc/marking_return_value_as_important
663+
. DelayedTargetValidation attribute was added.
664+
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
651665

652666
- Curl:
653667
. CurlSharePersistentHandle representing a share handle that is persisted

UPGRADING.INTERNALS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ PHP 8.5 INTERNALS UPGRADE NOTES
2828
extra layer of indirection can be removed. In other cases a zval can
2929
be heap-allocated and stored in the pointer as a minimal change to keep
3030
compatibility.
31+
. The validator callbacks for internal attribute now return `zend_string *`
32+
rather than `void`; instead of emitting an error when an attribute is
33+
applied incorrectly, the error message should be returned as a zend_string
34+
pointer. If the error will be delayed until runtime, it is stored in the
35+
new `validation_error` field of the `zend_attribute` struct.
36+
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
3137

3238
- Hash
3339
. Hash functions now use proper hash_spec_result enum for return values
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
--TEST--
2+
#[\DelayedTargetValidation] has errors at runtime
3+
--FILE--
4+
<?php
5+
6+
#[DelayedTargetValidation]
7+
#[NoDiscard]
8+
class Demo {
9+
10+
#[DelayedTargetValidation]
11+
#[Attribute]
12+
public const FOO = 'BAR';
13+
14+
#[DelayedTargetValidation]
15+
#[Attribute]
16+
public string $v1;
17+
18+
public string $v2 {
19+
#[DelayedTargetValidation]
20+
#[Attribute]
21+
get => $this->v2;
22+
#[DelayedTargetValidation]
23+
#[Attribute]
24+
set => $value;
25+
}
26+
27+
#[DelayedTargetValidation]
28+
#[Attribute]
29+
public function __construct(
30+
#[DelayedTargetValidation]
31+
#[Attribute]
32+
public string $v3
33+
) {
34+
$this->v1 = $v3;
35+
echo __METHOD__ . "\n";
36+
}
37+
}
38+
39+
#[DelayedTargetValidation]
40+
#[Attribute]
41+
function demoFn() {
42+
echo __FUNCTION__ . "\n";
43+
}
44+
45+
#[DelayedTargetValidation]
46+
#[Attribute]
47+
const EXAMPLE = true;
48+
49+
$cases = [
50+
new ReflectionClass('Demo'),
51+
new ReflectionClassConstant('Demo', 'FOO'),
52+
new ReflectionProperty('Demo', 'v1'),
53+
new ReflectionProperty('Demo', 'v2')->getHook(PropertyHookType::Get),
54+
new ReflectionProperty('Demo', 'v2')->getHook(PropertyHookType::Set),
55+
new ReflectionMethod('Demo', '__construct'),
56+
new ReflectionParameter([ 'Demo', '__construct' ], 'v3'),
57+
new ReflectionProperty('Demo', 'v3'),
58+
new ReflectionFunction('demoFn'),
59+
new ReflectionConstant('EXAMPLE'),
60+
];
61+
foreach ($cases as $r) {
62+
echo str_repeat("*", 20) . "\n";
63+
echo $r . "\n";
64+
$attributes = $r->getAttributes();
65+
var_dump($attributes);
66+
try {
67+
$attributes[1]->newInstance();
68+
} catch (Error $e) {
69+
echo get_class($e) . ": " . $e->getMessage() . "\n";
70+
}
71+
}
72+
73+
?>
74+
--EXPECTF--
75+
********************
76+
Class [ <user> <iterateable> class Demo ] {
77+
@@ %s %d-%d
78+
79+
- Constants [1] {
80+
Constant [ public string FOO ] { BAR }
81+
}
82+
83+
- Static properties [0] {
84+
}
85+
86+
- Static methods [0] {
87+
}
88+
89+
- Properties [3] {
90+
Property [ public string $v1 ]
91+
Property [ public string $v2 { get; set; } ]
92+
Property [ public string $v3 ]
93+
}
94+
95+
- Methods [1] {
96+
Method [ <user, ctor> public method __construct ] {
97+
@@ %s %d - %d
98+
99+
- Parameters [1] {
100+
Parameter #0 [ <required> string $v3 ]
101+
}
102+
}
103+
}
104+
}
105+
106+
array(2) {
107+
[0]=>
108+
object(ReflectionAttribute)#%d (1) {
109+
["name"]=>
110+
string(23) "DelayedTargetValidation"
111+
}
112+
[1]=>
113+
object(ReflectionAttribute)#%d (1) {
114+
["name"]=>
115+
string(9) "NoDiscard"
116+
}
117+
}
118+
Error: Attribute "NoDiscard" cannot target class (allowed targets: function, method)
119+
********************
120+
Constant [ public string FOO ] { BAR }
121+
122+
array(2) {
123+
[0]=>
124+
object(ReflectionAttribute)#%d (1) {
125+
["name"]=>
126+
string(23) "DelayedTargetValidation"
127+
}
128+
[1]=>
129+
object(ReflectionAttribute)#%d (1) {
130+
["name"]=>
131+
string(9) "Attribute"
132+
}
133+
}
134+
Error: Attribute "Attribute" cannot target class constant (allowed targets: class)
135+
********************
136+
Property [ public string $v1 ]
137+
138+
array(2) {
139+
[0]=>
140+
object(ReflectionAttribute)#%d (1) {
141+
["name"]=>
142+
string(23) "DelayedTargetValidation"
143+
}
144+
[1]=>
145+
object(ReflectionAttribute)#%d (1) {
146+
["name"]=>
147+
string(9) "Attribute"
148+
}
149+
}
150+
Error: Attribute "Attribute" cannot target property (allowed targets: class)
151+
********************
152+
Method [ <user> public method $v2::get ] {
153+
@@ %s %d - %d
154+
155+
- Parameters [0] {
156+
}
157+
- Return [ string ]
158+
}
159+
160+
array(2) {
161+
[0]=>
162+
object(ReflectionAttribute)#%d (1) {
163+
["name"]=>
164+
string(23) "DelayedTargetValidation"
165+
}
166+
[1]=>
167+
object(ReflectionAttribute)#%d (1) {
168+
["name"]=>
169+
string(9) "Attribute"
170+
}
171+
}
172+
Error: Attribute "Attribute" cannot target method (allowed targets: class)
173+
********************
174+
Method [ <user> public method $v2::set ] {
175+
@@ %s %d - %d
176+
177+
- Parameters [1] {
178+
Parameter #0 [ <required> string $value ]
179+
}
180+
- Return [ void ]
181+
}
182+
183+
array(2) {
184+
[0]=>
185+
object(ReflectionAttribute)#%d (1) {
186+
["name"]=>
187+
string(23) "DelayedTargetValidation"
188+
}
189+
[1]=>
190+
object(ReflectionAttribute)#%d (1) {
191+
["name"]=>
192+
string(9) "Attribute"
193+
}
194+
}
195+
Error: Attribute "Attribute" cannot target method (allowed targets: class)
196+
********************
197+
Method [ <user, ctor> public method __construct ] {
198+
@@ %s %d - %d
199+
200+
- Parameters [1] {
201+
Parameter #0 [ <required> string $v3 ]
202+
}
203+
}
204+
205+
array(2) {
206+
[0]=>
207+
object(ReflectionAttribute)#%d (1) {
208+
["name"]=>
209+
string(23) "DelayedTargetValidation"
210+
}
211+
[1]=>
212+
object(ReflectionAttribute)#%d (1) {
213+
["name"]=>
214+
string(9) "Attribute"
215+
}
216+
}
217+
Error: Attribute "Attribute" cannot target method (allowed targets: class)
218+
********************
219+
Parameter #0 [ <required> string $v3 ]
220+
array(2) {
221+
[0]=>
222+
object(ReflectionAttribute)#%d (1) {
223+
["name"]=>
224+
string(23) "DelayedTargetValidation"
225+
}
226+
[1]=>
227+
object(ReflectionAttribute)#%d (1) {
228+
["name"]=>
229+
string(9) "Attribute"
230+
}
231+
}
232+
Error: Attribute "Attribute" cannot target parameter (allowed targets: class)
233+
********************
234+
Property [ public string $v3 ]
235+
236+
array(2) {
237+
[0]=>
238+
object(ReflectionAttribute)#%d (1) {
239+
["name"]=>
240+
string(23) "DelayedTargetValidation"
241+
}
242+
[1]=>
243+
object(ReflectionAttribute)#%d (1) {
244+
["name"]=>
245+
string(9) "Attribute"
246+
}
247+
}
248+
Error: Attribute "Attribute" cannot target property (allowed targets: class)
249+
********************
250+
Function [ <user> function demoFn ] {
251+
@@ %s %d - %d
252+
}
253+
254+
array(2) {
255+
[0]=>
256+
object(ReflectionAttribute)#%d (1) {
257+
["name"]=>
258+
string(23) "DelayedTargetValidation"
259+
}
260+
[1]=>
261+
object(ReflectionAttribute)#%d (1) {
262+
["name"]=>
263+
string(9) "Attribute"
264+
}
265+
}
266+
Error: Attribute "Attribute" cannot target function (allowed targets: class)
267+
********************
268+
Constant [ bool EXAMPLE ] { 1 }
269+
270+
array(2) {
271+
[0]=>
272+
object(ReflectionAttribute)#%d (1) {
273+
["name"]=>
274+
string(23) "DelayedTargetValidation"
275+
}
276+
[1]=>
277+
object(ReflectionAttribute)#%d (1) {
278+
["name"]=>
279+
string(9) "Attribute"
280+
}
281+
}
282+
Error: Attribute "Attribute" cannot target constant (allowed targets: class)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
--TEST--
2+
#[\DelayedTargetValidation] prevents target errors at compile time
3+
--FILE--
4+
<?php
5+
6+
#[DelayedTargetValidation]
7+
#[NoDiscard]
8+
class Demo {
9+
10+
#[DelayedTargetValidation]
11+
#[Attribute]
12+
public const FOO = 'BAR';
13+
14+
#[DelayedTargetValidation]
15+
#[Attribute]
16+
public string $v1;
17+
18+
public string $v2 {
19+
#[DelayedTargetValidation]
20+
#[Attribute]
21+
get => $this->v2;
22+
#[DelayedTargetValidation]
23+
#[Attribute]
24+
set => $value;
25+
}
26+
27+
#[DelayedTargetValidation]
28+
#[Attribute]
29+
public function __construct(
30+
#[DelayedTargetValidation]
31+
#[Attribute]
32+
public string $v3
33+
) {
34+
$this->v1 = $v3;
35+
echo __METHOD__ . "\n";
36+
}
37+
}
38+
39+
#[DelayedTargetValidation]
40+
#[Attribute]
41+
function demoFn() {
42+
echo __FUNCTION__ . "\n";
43+
}
44+
45+
$o = new Demo( "foo" );
46+
demoFn();
47+
48+
#[DelayedTargetValidation]
49+
#[Attribute]
50+
const EXAMPLE = true;
51+
52+
?>
53+
--EXPECT--
54+
Demo::__construct
55+
demoFn
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
#[DelayedTargetValidation]
4+
#[AllowDynamicProperties]
5+
trait DemoTrait {}

0 commit comments

Comments
 (0)