Skip to content

Commit 4ea3307

Browse files
committed
Fix lazy objects API: add ReflectionProperty::getRawValueWithoutLazyInitialization()
1 parent cfd954f commit 4ea3307

File tree

5 files changed

+355
-17
lines changed

5 files changed

+355
-17
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
--TEST--
2+
Lazy objects: getRawValueWithoutLazyInitialization() fetches raw value without triggering initialization
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
public int $a;
8+
public $b = 2;
9+
}
10+
11+
$reflector = new ReflectionClass(C::class);
12+
$propA = $reflector->getProperty('a');
13+
$propB = $reflector->getProperty('b');
14+
15+
$obj = $reflector->newLazyGhost(function () {
16+
throw new \Exception('initializer');
17+
});
18+
19+
print "# Ghost: Lazy properties\n";
20+
21+
$isLazy = null;
22+
var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy));
23+
var_dump($isLazy);
24+
25+
$isLazy = null;
26+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
27+
var_dump($isLazy);
28+
29+
print "# Ghost: Initialized properties\n";
30+
31+
$propA->setRawValueWithoutLazyInitialization($obj, 1);
32+
33+
$isLazy = null;
34+
var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy));
35+
var_dump($isLazy);
36+
37+
$propB->skipLazyInitialization($obj);
38+
39+
$isLazy = null;
40+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
41+
var_dump($isLazy);
42+
43+
$obj = $reflector->newLazyProxy(function () {
44+
throw new \Exception('initializer');
45+
});
46+
47+
print "# Proxy: Lazy properties\n";
48+
49+
$isLazy = null;
50+
var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy));
51+
var_dump($isLazy);
52+
53+
$isLazy = null;
54+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
55+
var_dump($isLazy);
56+
57+
print "# Proxy: Initialized properties\n";
58+
59+
$propA->setRawValueWithoutLazyInitialization($obj, 1);
60+
61+
$isLazy = null;
62+
var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy));
63+
var_dump($isLazy);
64+
65+
$propB->skipLazyInitialization($obj);
66+
67+
$isLazy = null;
68+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
69+
var_dump($isLazy);
70+
71+
$obj = $reflector->newLazyProxy(function () {
72+
return new C();
73+
});
74+
$reflector->initializeLazyObject($obj);
75+
76+
print "# Initialized Proxy\n";
77+
78+
try {
79+
$propA->getRawValueWithoutLazyInitialization($obj, $isLazy);
80+
} catch (Error $e) {
81+
printf("%s: %s\n", $e::class, $e->getMessage());
82+
}
83+
84+
$isLazy = null;
85+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
86+
var_dump($isLazy);
87+
88+
?>
89+
--EXPECT--
90+
# Ghost: Lazy properties
91+
NULL
92+
bool(true)
93+
NULL
94+
bool(true)
95+
# Ghost: Initialized properties
96+
int(1)
97+
bool(false)
98+
int(2)
99+
bool(false)
100+
# Proxy: Lazy properties
101+
NULL
102+
bool(true)
103+
NULL
104+
bool(true)
105+
# Proxy: Initialized properties
106+
int(1)
107+
bool(false)
108+
int(2)
109+
bool(false)
110+
# Initialized Proxy
111+
Error: Typed property C::$a must not be accessed before initialization
112+
int(2)
113+
bool(false)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
--TEST--
2+
Lazy objects: getRawValueWithoutLazyInitialization() behaves like getRawValue() on non-lazy objects
3+
--FILE--
4+
<?php
5+
6+
#[AllowDynamicProperties]
7+
class C {
8+
public static $static = 'static';
9+
10+
public int $a;
11+
public $b;
12+
public $c {
13+
get { return 'c'; }
14+
}
15+
public $d {
16+
get { return $this->d; }
17+
set($value) { $this->d = $value; }
18+
}
19+
}
20+
21+
class D extends C {
22+
public function __get($name) {
23+
return ord($name);
24+
}
25+
}
26+
27+
$reflector = new ReflectionClass(C::class);
28+
$propA = $reflector->getProperty('a');
29+
$propB = $reflector->getProperty('b');
30+
$propC = $reflector->getProperty('c');
31+
$propD = $reflector->getProperty('d');
32+
$propStatic = $reflector->getProperty('static');
33+
34+
$obj = new C();
35+
$obj->dynamic = 1;
36+
$propDynamic = new ReflectionProperty($obj, 'dynamic');
37+
38+
$obj = new C();
39+
40+
print "# Non initialized properties\n";
41+
42+
try {
43+
$propA->getRawValueWithoutLazyInitialization($obj, $isLazy);
44+
} catch (Error $e) {
45+
printf("%s: %s\n", $e::class, $e->getMessage());
46+
}
47+
48+
$isLazy = null;
49+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
50+
var_dump($isLazy);
51+
52+
try {
53+
var_dump($propC->getRawValueWithoutLazyInitialization($obj, $isLazy));
54+
} catch (Error $e) {
55+
printf("%s: %s\n", $e::class, $e->getMessage());
56+
}
57+
58+
$isLazy = null;
59+
var_dump($propD->getRawValueWithoutLazyInitialization($obj, $isLazy));
60+
var_dump($isLazy);
61+
62+
print "# Initialized properties\n";
63+
64+
$obj->a = 1;
65+
$obj->b = new stdClass;
66+
$obj->d = 4;
67+
68+
$isLazy = null;
69+
var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy));
70+
var_dump($isLazy);
71+
72+
$isLazy = null;
73+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
74+
var_dump($isLazy);
75+
76+
try {
77+
var_dump($propC->getRawValueWithoutLazyInitialization($obj, $isLazy));
78+
} catch (Error $e) {
79+
printf("%s: %s\n", $e::class, $e->getMessage());
80+
}
81+
82+
$isLazy = null;
83+
var_dump($propD->getRawValueWithoutLazyInitialization($obj, $isLazy));
84+
var_dump($isLazy);
85+
86+
try {
87+
$propStatic->getRawValueWithoutLazyInitialization($obj, $isLazy);
88+
} catch (ReflectionException $e) {
89+
printf("%s: %s\n", $e::class, $e->getMessage());
90+
}
91+
92+
$isLazy = null;
93+
var_dump($propDynamic->getRawValueWithoutLazyInitialization($obj, $isLazy));
94+
var_dump($isLazy);
95+
96+
print "# Unset properties and __get()\n";
97+
98+
$obj = new D();
99+
unset($obj->a);
100+
unset($obj->b);
101+
102+
$isLazy = null;
103+
var_dump($propA->getRawValueWithoutLazyInitialization($obj, $isLazy));
104+
var_dump($isLazy);
105+
106+
$isLazy = null;
107+
var_dump($propB->getRawValueWithoutLazyInitialization($obj, $isLazy));
108+
var_dump($isLazy);
109+
110+
print "# Internal class\n";
111+
112+
$reflector = new ReflectionClass(Exception::class);
113+
$propMessage = $reflector->getProperty('message');
114+
115+
$obj = new Exception('hello');
116+
117+
$isLazy = null;
118+
var_dump($propMessage->getRawValueWithoutLazyInitialization($obj, $isLazy));
119+
var_dump($isLazy);
120+
121+
?>
122+
--EXPECTF--
123+
# Non initialized properties
124+
Error: Typed property C::$a must not be accessed before initialization
125+
NULL
126+
bool(false)
127+
Error: Must not read from virtual property C::$c
128+
NULL
129+
bool(false)
130+
# Initialized properties
131+
int(1)
132+
bool(false)
133+
object(stdClass)#%d (0) {
134+
}
135+
bool(false)
136+
Error: Must not read from virtual property C::$c
137+
int(4)
138+
bool(false)
139+
ReflectionException: May not use getRawValueWithoutLazyInitialization on static properties
140+
141+
Warning: Undefined property: C::$dynamic in %s on line %d
142+
NULL
143+
bool(false)
144+
# Unset properties and __get()
145+
int(97)
146+
bool(false)
147+
int(98)
148+
bool(false)
149+
# Internal class
150+
string(5) "hello"
151+
bool(false)

ext/reflection/php_reflection.c

Lines changed: 81 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6073,6 +6073,26 @@ ZEND_METHOD(ReflectionProperty, setValue)
60736073
}
60746074
/* }}} */
60756075

6076+
static void reflection_property_get_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *return_value)
6077+
{
6078+
if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) {
6079+
zval rv;
6080+
zval *member_p = zend_read_property_ex(intern->ce, object, ref->unmangled_name, 0, &rv);
6081+
6082+
if (member_p != &rv) {
6083+
RETURN_COPY_DEREF(member_p);
6084+
} else {
6085+
if (Z_ISREF_P(member_p)) {
6086+
zend_unwrap_reference(member_p);
6087+
}
6088+
RETURN_COPY_VALUE(member_p);
6089+
}
6090+
} else {
6091+
zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name);
6092+
zend_call_known_instance_method_with_0_params(func, object, return_value);
6093+
}
6094+
}
6095+
60766096
ZEND_METHOD(ReflectionProperty, getRawValue)
60776097
{
60786098
reflection_object *intern;
@@ -6095,22 +6115,7 @@ ZEND_METHOD(ReflectionProperty, getRawValue)
60956115
RETURN_THROWS();
60966116
}
60976117

6098-
if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) {
6099-
zval rv;
6100-
zval *member_p = zend_read_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, 0, &rv);
6101-
6102-
if (member_p != &rv) {
6103-
RETURN_COPY_DEREF(member_p);
6104-
} else {
6105-
if (Z_ISREF_P(member_p)) {
6106-
zend_unwrap_reference(member_p);
6107-
}
6108-
RETURN_COPY_VALUE(member_p);
6109-
}
6110-
} else {
6111-
zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name);
6112-
zend_call_known_instance_method_with_0_params(func, Z_OBJ_P(object), return_value);
6113-
}
6118+
reflection_property_get_raw_value(ref, intern, Z_OBJ_P(object), return_value);
61146119
}
61156120

61166121
static void reflection_property_set_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *value)
@@ -6271,6 +6276,66 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization)
62716276
}
62726277
}
62736278

6279+
ZEND_METHOD(ReflectionProperty, getRawValueWithoutLazyInitialization)
6280+
{
6281+
reflection_object *intern;
6282+
property_reference *ref;
6283+
zend_object *object;
6284+
zval *is_lazy;
6285+
6286+
GET_REFLECTION_OBJECT_PTR(ref);
6287+
6288+
ZEND_PARSE_PARAMETERS_START(2, 2) {
6289+
Z_PARAM_OBJ_OF_CLASS(object, intern->ce)
6290+
Z_PARAM_ZVAL(is_lazy)
6291+
} ZEND_PARSE_PARAMETERS_END();
6292+
6293+
if (zend_object_is_lazy_proxy(object)
6294+
&& zend_lazy_object_initialized(object)) {
6295+
object = zend_lazy_object_get_instance(object);
6296+
}
6297+
6298+
/* Fallback to getRawValue() if object is not lazy and has custom handlers.
6299+
* Lazy objects with custom handlers are possible if an extension overrides
6300+
* std handlers. */
6301+
if (UNEXPECTED(object->handlers != &std_object_handlers)
6302+
&& !zend_object_is_lazy(object)) {
6303+
goto get_raw_value;
6304+
}
6305+
6306+
/* Fallback to getRawValue() for dynamic or virtual properties as these can
6307+
* not be lazy and code below does not support these. */
6308+
if (!ref->prop || ref->prop->flags & ZEND_ACC_VIRTUAL) {
6309+
goto get_raw_value;
6310+
}
6311+
6312+
if (UNEXPECTED(ref->prop->flags & ZEND_ACC_STATIC)) {
6313+
ZEND_TRY_ASSIGN_REF_BOOL(is_lazy, 0);
6314+
_DO_THROW("May not use getRawValueWithoutLazyInitialization on static properties");
6315+
RETURN_THROWS();
6316+
}
6317+
6318+
zval *prop = OBJ_PROP(object, ref->prop->offset);
6319+
6320+
if (Z_PROP_FLAG_P(prop) & IS_PROP_LAZY) {
6321+
ZEND_TRY_ASSIGN_REF_BOOL(is_lazy, 1);
6322+
RETURN_NULL();
6323+
}
6324+
6325+
/* Fallback to getRawValue() for uninitialized properties */
6326+
if (UNEXPECTED(Z_TYPE_P(prop) == IS_UNDEF)) {
6327+
goto get_raw_value;
6328+
}
6329+
6330+
ZVAL_COPY(return_value, prop);
6331+
ZEND_TRY_ASSIGN_REF_BOOL(is_lazy, 0);
6332+
return;
6333+
6334+
get_raw_value:
6335+
reflection_property_get_raw_value(ref, intern, object, return_value);
6336+
ZEND_TRY_ASSIGN_REF_BOOL(is_lazy, 0);
6337+
}
6338+
62746339
/* {{{ Returns true if property was initialized */
62756340
ZEND_METHOD(ReflectionProperty, isInitialized)
62766341
{

ext/reflection/php_reflection.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,8 @@ public function setRawValueWithoutLazyInitialization(object $object, mixed $valu
502502

503503
public function skipLazyInitialization(object $object): void {}
504504

505+
public function getRawValueWithoutLazyInitialization(object $object, mixed &$isLazy): mixed {}
506+
505507
/** @tentative-return-type */
506508
public function isInitialized(?object $object = null): bool {}
507509

0 commit comments

Comments
 (0)