Skip to content

Commit 63f9e49

Browse files
GH-17927: Indicate virtual properties and hooks in reflection output (#19297)
1 parent 105c1e9 commit 63f9e49

File tree

7 files changed

+291
-6
lines changed

7 files changed

+291
-6
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.5.0beta1
44

5+
- Reflection:
6+
. Fixed bug GH-17927 (Reflection: have some indication of property hooks in
7+
`_property_string()`). (DanielEScherzer)
58

69
31 Jul 2025, PHP 8.5.0alpha4
710

UPGRADING

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,11 @@ PHP 8.5 UPGRADE NOTES
394394
. The output of ReflectionClass::toString() for enums has changed to
395395
better indicate that the class is an enum, and that the enum cases
396396
are enum cases rather than normal class constants.
397+
. The output of ReflectionProperty::__toString() for properties with
398+
hooks has changed to indicate what hooks the property has, whether those
399+
hooks are final, and whether the property is virtual. This also affects
400+
the output of ReflectionClass::__toString() when a class contains hooked
401+
properties.
397402

398403
- Session:
399404
. session_start is stricter in regard to the option argument.

Zend/tests/lazy_objects/skipLazyInitialization.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,15 +227,15 @@ getValue(): NULL
227227
setRawValueWithoutLazyInitialization():
228228
getValue(): string(5) "value"
229229

230-
## Property [ public $hooked = NULL ]
230+
## Property [ public $hooked = NULL { get; set; } ]
231231

232232
skipInitializerForProperty():
233233
getValue(): NULL
234234

235235
setRawValueWithoutLazyInitialization():
236236
getValue(): string(5) "value"
237237

238-
## Property [ public $virtual ]
238+
## Property [ public virtual $virtual { get; set; } ]
239239

240240
skipInitializerForProperty():
241241
ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual
@@ -324,15 +324,15 @@ getValue(): NULL
324324
setRawValueWithoutLazyInitialization():
325325
getValue(): string(5) "value"
326326

327-
## Property [ public $hooked = NULL ]
327+
## Property [ public $hooked = NULL { get; set; } ]
328328

329329
skipInitializerForProperty():
330330
getValue(): NULL
331331

332332
setRawValueWithoutLazyInitialization():
333333
getValue(): string(5) "value"
334334

335-
## Property [ public $virtual ]
335+
## Property [ public virtual $virtual { get; set; } ]
336336

337337
skipInitializerForProperty():
338338
ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual

ext/reflection/php_reflection.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,9 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha
10371037
if (prop->flags & ZEND_ACC_READONLY) {
10381038
smart_str_appends(str, "readonly ");
10391039
}
1040+
if (prop->flags & ZEND_ACC_VIRTUAL) {
1041+
smart_str_appends(str, "virtual ");
1042+
}
10401043
if (ZEND_TYPE_IS_SET(prop->type)) {
10411044
zend_string *type_str = zend_type_to_string(prop->type);
10421045
smart_str_append(str, type_str);
@@ -1054,6 +1057,26 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha
10541057
smart_str_appends(str, " = ");
10551058
format_default_value(str, default_value);
10561059
}
1060+
if (prop->hooks != NULL) {
1061+
smart_str_appends(str, " {");
1062+
const zend_function *get_hooked = prop->hooks[ZEND_PROPERTY_HOOK_GET];
1063+
if (get_hooked != NULL) {
1064+
if (get_hooked->common.fn_flags & ZEND_ACC_FINAL) {
1065+
smart_str_appends(str, " final get;");
1066+
} else {
1067+
smart_str_appends(str, " get;");
1068+
}
1069+
}
1070+
const zend_function *set_hooked = prop->hooks[ZEND_PROPERTY_HOOK_SET];
1071+
if (set_hooked != NULL) {
1072+
if (set_hooked->common.fn_flags & ZEND_ACC_FINAL) {
1073+
smart_str_appends(str, " final set;");
1074+
} else {
1075+
smart_str_appends(str, " set;");
1076+
}
1077+
}
1078+
smart_str_appends(str, " }");
1079+
}
10571080
}
10581081

10591082
smart_str_appends(str, " ]\n");
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
--TEST--
2+
Using ReflectionClass::__toString() with hooked properties (GH-17927)
3+
--FILE--
4+
<?php
5+
6+
interface IHookedDemo {
7+
public mixed $getOnly { get; }
8+
public mixed $setOnly { set; }
9+
public mixed $both { get; set; }
10+
}
11+
abstract class HookedDemo {
12+
abstract public mixed $getOnly { get; }
13+
abstract public mixed $setOnly { set; }
14+
abstract public mixed $both { get; set; }
15+
}
16+
class WithHooks {
17+
public mixed $getOnly {
18+
get => "always this string";
19+
}
20+
public mixed $setOnly {
21+
set => strtolower($value);
22+
}
23+
public mixed $both {
24+
get => $this->prop3;
25+
set => strtolower($value);
26+
}
27+
}
28+
class WithFinalHooks {
29+
public mixed $getOnly {
30+
final get => "always this string";
31+
}
32+
public mixed $setOnly {
33+
final set => strtolower($value);
34+
}
35+
public mixed $both {
36+
final get => $this->prop3;
37+
final set => strtolower($value);
38+
}
39+
}
40+
class WithMixedHooks {
41+
public mixed $getIsFinal {
42+
final get => "always this string";
43+
set => strtolower($value);
44+
}
45+
public mixed $setIsFinal {
46+
get => $this->setIsFinal;
47+
final set => strtolower($value);
48+
}
49+
}
50+
$classes = [
51+
IHookedDemo::class,
52+
HookedDemo::class,
53+
WithHooks::class,
54+
WithFinalHooks::class,
55+
WithMixedHooks::class,
56+
];
57+
foreach ( $classes as $clazz ) {
58+
echo new ReflectionClass( $clazz );
59+
}
60+
?>
61+
--EXPECTF--
62+
Interface [ <user> <iterateable> interface IHookedDemo ] {
63+
@@ %s %d-%d
64+
65+
- Constants [0] {
66+
}
67+
68+
- Static properties [0] {
69+
}
70+
71+
- Static methods [0] {
72+
}
73+
74+
- Properties [3] {
75+
Property [ abstract public virtual mixed $getOnly { get; } ]
76+
Property [ abstract public virtual mixed $setOnly { set; } ]
77+
Property [ abstract public virtual mixed $both { get; set; } ]
78+
}
79+
80+
- Methods [0] {
81+
}
82+
}
83+
Class [ <user> <iterateable> abstract class HookedDemo ] {
84+
@@ %s %d-%d
85+
86+
- Constants [0] {
87+
}
88+
89+
- Static properties [0] {
90+
}
91+
92+
- Static methods [0] {
93+
}
94+
95+
- Properties [3] {
96+
Property [ abstract public virtual mixed $getOnly { get; } ]
97+
Property [ abstract public virtual mixed $setOnly { set; } ]
98+
Property [ abstract public virtual mixed $both { get; set; } ]
99+
}
100+
101+
- Methods [0] {
102+
}
103+
}
104+
Class [ <user> <iterateable> class WithHooks ] {
105+
@@ %s %d-%d
106+
107+
- Constants [0] {
108+
}
109+
110+
- Static properties [0] {
111+
}
112+
113+
- Static methods [0] {
114+
}
115+
116+
- Properties [3] {
117+
Property [ public virtual mixed $getOnly { get; } ]
118+
Property [ public mixed $setOnly { set; } ]
119+
Property [ public mixed $both { get; set; } ]
120+
}
121+
122+
- Methods [0] {
123+
}
124+
}
125+
Class [ <user> <iterateable> class WithFinalHooks ] {
126+
@@ %s %d-%d
127+
128+
- Constants [0] {
129+
}
130+
131+
- Static properties [0] {
132+
}
133+
134+
- Static methods [0] {
135+
}
136+
137+
- Properties [3] {
138+
Property [ public virtual mixed $getOnly { final get; } ]
139+
Property [ public mixed $setOnly { final set; } ]
140+
Property [ public mixed $both { final get; final set; } ]
141+
}
142+
143+
- Methods [0] {
144+
}
145+
}
146+
Class [ <user> <iterateable> class WithMixedHooks ] {
147+
@@ %s %d-%d
148+
149+
- Constants [0] {
150+
}
151+
152+
- Static properties [0] {
153+
}
154+
155+
- Static methods [0] {
156+
}
157+
158+
- Properties [2] {
159+
Property [ public mixed $getIsFinal { final get; set; } ]
160+
Property [ public mixed $setIsFinal { get; final set; } ]
161+
}
162+
163+
- Methods [0] {
164+
}
165+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
--TEST--
2+
Using ReflectionProperty::__toString() with hooked properties (GH-17927)
3+
--FILE--
4+
<?php
5+
6+
interface IHookedDemo {
7+
public mixed $getOnly { get; }
8+
public mixed $setOnly { set; }
9+
public mixed $both { get; set; }
10+
}
11+
abstract class HookedDemo {
12+
abstract public mixed $getOnly { get; }
13+
abstract public mixed $setOnly { set; }
14+
abstract public mixed $both { get; set; }
15+
}
16+
class WithHooks {
17+
public mixed $getOnly {
18+
get => "always this string";
19+
}
20+
public mixed $setOnly {
21+
set => strtolower($value);
22+
}
23+
public mixed $both {
24+
get => $this->prop3;
25+
set => strtolower($value);
26+
}
27+
}
28+
class WithFinalHooks {
29+
public mixed $getOnly {
30+
final get => "always this string";
31+
}
32+
public mixed $setOnly {
33+
final set => strtolower($value);
34+
}
35+
public mixed $both {
36+
final get => $this->prop3;
37+
final set => strtolower($value);
38+
}
39+
}
40+
class WithMixedHooks {
41+
public mixed $getIsFinal {
42+
final get => "always this string";
43+
set => strtolower($value);
44+
}
45+
public mixed $setIsFinal {
46+
get => $this->setIsFinal;
47+
final set => strtolower($value);
48+
}
49+
}
50+
$classes = [
51+
IHookedDemo::class,
52+
HookedDemo::class,
53+
WithHooks::class,
54+
WithFinalHooks::class,
55+
WithMixedHooks::class,
56+
];
57+
foreach ( $classes as $clazz ) {
58+
echo "$clazz:\n";
59+
$ref = new ReflectionClass( $clazz );
60+
foreach ( $ref->getProperties() as $prop ) {
61+
echo $prop;
62+
}
63+
echo "\n";
64+
}
65+
?>
66+
--EXPECT--
67+
IHookedDemo:
68+
Property [ abstract public virtual mixed $getOnly { get; } ]
69+
Property [ abstract public virtual mixed $setOnly { set; } ]
70+
Property [ abstract public virtual mixed $both { get; set; } ]
71+
72+
HookedDemo:
73+
Property [ abstract public virtual mixed $getOnly { get; } ]
74+
Property [ abstract public virtual mixed $setOnly { set; } ]
75+
Property [ abstract public virtual mixed $both { get; set; } ]
76+
77+
WithHooks:
78+
Property [ public virtual mixed $getOnly { get; } ]
79+
Property [ public mixed $setOnly { set; } ]
80+
Property [ public mixed $both { get; set; } ]
81+
82+
WithFinalHooks:
83+
Property [ public virtual mixed $getOnly { final get; } ]
84+
Property [ public mixed $setOnly { final set; } ]
85+
Property [ public mixed $both { final get; final set; } ]
86+
87+
WithMixedHooks:
88+
Property [ public mixed $getIsFinal { final get; set; } ]
89+
Property [ public mixed $setIsFinal { get; final set; } ]

ext/reflection/tests/abstract_property_indicated.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ Class [ <user> <iterateable> abstract class Demo ] {
3131
}
3232

3333
- Properties [2] {
34-
Property [ abstract public $a ]
34+
Property [ abstract public virtual $a { get; } ]
3535
Property [ public $b = NULL ]
3636
}
3737

3838
- Methods [0] {
3939
}
4040
}
41-
Property [ abstract public $a ]
41+
Property [ abstract public virtual $a { get; } ]
4242
Property [ public $b = NULL ]

0 commit comments

Comments
 (0)