Skip to content

Commit 3eea873

Browse files
Add ReflectionProperty::getMangledName()
1 parent c9249e2 commit 3eea873

11 files changed

+580
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ PHP NEWS
187187
zval for uninitialized typed properties). (nielsdos)
188188
. Fixed bug GH-15766 (ReflectionClass::toString() should have better output
189189
for enums). (DanielEScherzer)
190+
. Added ReflectionProperty::getMangledName() method. (alexandre-daubois)
190191

191192
- Session:
192193
. session_start() throws a ValueError on option argument if not a hashmap

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ PHP 8.5 UPGRADE NOTES
420420
ReflectionConstant::getExtensionName() were introduced.
421421
. ReflectionConstant::getAttributes() was introduced.
422422
RFC: https://wiki.php.net/rfc/attributes-on-constants
423+
. ReflectionProperty::getMangledName() was introduced.
423424

424425
- Sqlite:
425426
. Sqlite3Stmt::busy to check if a statement had been fetched

ext/reflection/php_reflection.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5753,6 +5753,21 @@ ZEND_METHOD(ReflectionProperty, getName)
57535753
}
57545754
/* }}} */
57555755

5756+
ZEND_METHOD(ReflectionProperty, getMangledName)
5757+
{
5758+
reflection_object *intern;
5759+
property_reference *ref;
5760+
5761+
ZEND_PARSE_PARAMETERS_NONE();
5762+
5763+
GET_REFLECTION_OBJECT_PTR(ref);
5764+
if (ref->prop == NULL) {
5765+
RETURN_STR_COPY(ref->unmangled_name);
5766+
}
5767+
5768+
RETURN_STR_COPY(ref->prop->name);
5769+
}
5770+
57565771
static void _property_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) /* {{{ */
57575772
{
57585773
reflection_object *intern;

ext/reflection/php_reflection.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ public function __toString(): string {}
482482
/** @tentative-return-type */
483483
public function getName(): string {}
484484

485+
public function getMangledName(): string {}
486+
485487
/** @tentative-return-type */
486488
public function getValue(?object $object = null): mixed {}
487489

ext/reflection/php_reflection_arginfo.h

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
Test ReflectionProperty::getMangledName() method
3+
--FILE--
4+
<?php
5+
6+
class TestClass {
7+
public $publicProp = 'public';
8+
protected $protectedProp = 'protected';
9+
private $privateProp = 'private';
10+
}
11+
12+
function testMangledName($class, $property) {
13+
$reflection = new ReflectionProperty($class, $property);
14+
echo "Property: $property\n";
15+
echo "getName(): " . $reflection->getName() . "\n";
16+
echo "getMangledName(): " . $reflection->getMangledName() . "\n";
17+
18+
$obj = new $class();
19+
$array = (array) $obj;
20+
echo "In array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "found" : "not found") . "\n";
21+
echo "\n";
22+
}
23+
24+
testMangledName('TestClass', 'publicProp');
25+
testMangledName('TestClass', 'protectedProp');
26+
testMangledName('TestClass', 'privateProp');
27+
28+
?>
29+
--EXPECTF--
30+
Property: publicProp
31+
getName(): publicProp
32+
getMangledName(): publicProp
33+
In array cast: found
34+
35+
Property: protectedProp
36+
getName(): protectedProp
37+
getMangledName(): %0*%0protectedProp
38+
In array cast: found
39+
40+
Property: privateProp
41+
getName(): privateProp
42+
getMangledName(): %0TestClass%0privateProp
43+
In array cast: found
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
--TEST--
2+
Test ReflectionProperty::getMangledName() with dynamic properties
3+
--FILE--
4+
<?php
5+
6+
echo "=== Testing stdClass with dynamic properties ===\n";
7+
$stdObj = new stdClass();
8+
$stdObj->prop1 = 'value1';
9+
$stdObj->{'special-name'} = 'special value';
10+
$stdObj->{'123numeric'} = 'numeric start';
11+
12+
function testDynamicProperty($obj, $property, $description) {
13+
try {
14+
$reflection = new ReflectionProperty($obj, $property);
15+
echo "$description:\n";
16+
echo " getName(): " . $reflection->getName() . "\n";
17+
echo " getMangledName(): " . $reflection->getMangledName() . "\n";
18+
19+
$array = (array) $obj;
20+
echo " Found in array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "yes" : "no") . "\n";
21+
echo "\n";
22+
} catch (ReflectionException $e) {
23+
echo "$description: EXCEPTION - " . $e->getMessage() . "\n\n";
24+
}
25+
}
26+
27+
testDynamicProperty($stdObj, 'prop1', 'stdClass property prop1');
28+
testDynamicProperty($stdObj, 'special-name', 'stdClass property with special name');
29+
testDynamicProperty($stdObj, '123numeric', 'stdClass property starting with number');
30+
31+
echo "=== Testing edge cases ===\n";
32+
$numericObj = (object)[true];
33+
testDynamicProperty($numericObj, '1', 'Property name as number');
34+
35+
$nullByteObj = (object)["foo\0" => true];
36+
testDynamicProperty($nullByteObj, "foo\0", 'Property name with null byte');
37+
38+
$invalidObj = (object)["::" => true];
39+
testDynamicProperty($invalidObj, '::', 'Invalid property name');
40+
41+
echo "=== Testing regular class with dynamic properties ===\n";
42+
#[AllowDynamicProperties]
43+
class TestClass {
44+
public $existing = 'existing';
45+
}
46+
47+
$obj = new TestClass();
48+
$obj->dynamic = 'dynamic value';
49+
$obj->anotherDynamic = 'another dynamic';
50+
51+
testDynamicProperty($obj, 'dynamic', 'Regular class dynamic property');
52+
testDynamicProperty($obj, 'anotherDynamic', 'Regular class another dynamic property');
53+
54+
$reflection = new ReflectionProperty($obj, 'existing');
55+
echo "Regular property:\n";
56+
echo " getName(): " . $reflection->getName() . "\n";
57+
echo " getMangledName(): " . $reflection->getMangledName() . "\n";
58+
59+
echo "\n=== Testing ReflectionProperty from class vs instance ===\n";
60+
try {
61+
$reflection = new ReflectionProperty('TestClass', 'dynamic');
62+
echo "This should not be reached\n";
63+
} catch (ReflectionException $e) {
64+
echo "Expected exception for class-based reflection: " . $e->getMessage() . "\n";
65+
}
66+
67+
try {
68+
$reflection = new ReflectionProperty($obj, 'dynamic');
69+
echo "Instance-based reflection works: " . $reflection->getMangledName() . "\n";
70+
} catch (ReflectionException $e) {
71+
echo "Unexpected exception: " . $e->getMessage() . "\n";
72+
}
73+
74+
?>
75+
--EXPECTF--
76+
=== Testing stdClass with dynamic properties ===
77+
stdClass property prop1:
78+
getName(): prop1
79+
getMangledName(): prop1
80+
Found in array cast: yes
81+
82+
stdClass property with special name:
83+
getName(): special-name
84+
getMangledName(): special-name
85+
Found in array cast: yes
86+
87+
stdClass property starting with number:
88+
getName(): 123numeric
89+
getMangledName(): 123numeric
90+
Found in array cast: yes
91+
92+
=== Testing edge cases ===
93+
Property name as number: EXCEPTION - Property stdClass::$1 does not exist
94+
95+
Property name with null byte:
96+
getName(): foo%0
97+
getMangledName(): foo%0
98+
Found in array cast: yes
99+
100+
Invalid property name:
101+
getName(): ::
102+
getMangledName(): ::
103+
Found in array cast: yes
104+
105+
=== Testing regular class with dynamic properties ===
106+
Regular class dynamic property:
107+
getName(): dynamic
108+
getMangledName(): dynamic
109+
Found in array cast: yes
110+
111+
Regular class another dynamic property:
112+
getName(): anotherDynamic
113+
getMangledName(): anotherDynamic
114+
Found in array cast: yes
115+
116+
Regular property:
117+
getName(): existing
118+
getMangledName(): existing
119+
120+
=== Testing ReflectionProperty from class vs instance ===
121+
Expected exception for class-based reflection: Property TestClass::$dynamic does not exist
122+
Instance-based reflection works: dynamic
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
Test ReflectionProperty::getMangledName() with property hooks
3+
--FILE--
4+
<?php
5+
6+
echo "=== Testing virtual hooked properties ===\n";
7+
8+
class Demo {
9+
protected string $foo {
10+
get => "virtual";
11+
}
12+
13+
public string $bar {
14+
get => "hooked getter";
15+
set => throw new Exception("Cannot set bar");
16+
}
17+
18+
public string $baz = "backed";
19+
}
20+
21+
$d = new Demo();
22+
23+
function testHookedProperty($obj, $property, $description) {
24+
try {
25+
$reflection = new ReflectionProperty($obj, $property);
26+
echo "$description:\n";
27+
echo " getName(): " . $reflection->getName() . "\n";
28+
echo " getMangledName(): " . $reflection->getMangledName() . "\n";
29+
30+
$array = (array) $obj;
31+
echo " Found in array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "yes" : "no") . "\n";
32+
echo " Has hooks: " . ($reflection->hasHooks() ? "yes" : "no") . "\n";
33+
echo "\n";
34+
} catch (ReflectionException $e) {
35+
echo "$description: EXCEPTION - " . $e->getMessage() . "\n\n";
36+
}
37+
}
38+
39+
testHookedProperty($d, 'foo', 'Virtual hooked property (protected)');
40+
testHookedProperty($d, 'bar', 'Hooked property with getter/setter (public)');
41+
testHookedProperty($d, 'baz', 'Regular backed property');
42+
43+
echo "=== Object dump ===\n";
44+
var_dump($d);
45+
46+
echo "\n=== Array cast ===\n";
47+
var_dump((array)$d);
48+
49+
?>
50+
--EXPECTF--
51+
=== Testing virtual hooked properties ===
52+
Virtual hooked property (protected):
53+
getName(): foo
54+
getMangledName(): %0*%0foo
55+
Found in array cast: no
56+
Has hooks: yes
57+
58+
Hooked property with getter/setter (public):
59+
getName(): bar
60+
getMangledName(): bar
61+
Found in array cast: no
62+
Has hooks: yes
63+
64+
Regular backed property:
65+
getName(): baz
66+
getMangledName(): baz
67+
Found in array cast: yes
68+
Has hooks: no
69+
70+
=== Object dump ===
71+
object(Demo)#1 (1) {
72+
["bar"]=>
73+
uninitialized(string)
74+
["baz"]=>
75+
string(6) "backed"
76+
}
77+
78+
=== Array cast ===
79+
array(1) {
80+
["baz"]=>
81+
string(6) "backed"
82+
}

0 commit comments

Comments
 (0)