Skip to content

Commit 420c706

Browse files
Add ReflectionProperty::getMangledName()
1 parent c9249e2 commit 420c706

11 files changed

+583
-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: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
// Test numeric property name
33+
$numericObj = (object)[true];
34+
testDynamicProperty($numericObj, '1', 'Property name as number');
35+
36+
// Test property name with null byte
37+
$nullByteObj = (object)["foo\0" => true];
38+
testDynamicProperty($nullByteObj, "foo\0", 'Property name with null byte');
39+
40+
// Test invalid property name
41+
$invalidObj = (object)["::" => true];
42+
testDynamicProperty($invalidObj, '::', 'Invalid property name');
43+
44+
echo "=== Testing regular class with dynamic properties ===\n";
45+
#[AllowDynamicProperties]
46+
class TestClass {
47+
public $existing = 'existing';
48+
}
49+
50+
$obj = new TestClass();
51+
$obj->dynamic = 'dynamic value';
52+
$obj->anotherDynamic = 'another dynamic';
53+
54+
testDynamicProperty($obj, 'dynamic', 'Regular class dynamic property');
55+
testDynamicProperty($obj, 'anotherDynamic', 'Regular class another dynamic property');
56+
57+
$reflection = new ReflectionProperty($obj, 'existing');
58+
echo "Regular property:\n";
59+
echo " getName(): " . $reflection->getName() . "\n";
60+
echo " getMangledName(): " . $reflection->getMangledName() . "\n";
61+
62+
echo "\n=== Testing ReflectionProperty from class vs instance ===\n";
63+
try {
64+
$reflection = new ReflectionProperty('TestClass', 'dynamic');
65+
echo "This should not be reached\n";
66+
} catch (ReflectionException $e) {
67+
echo "Expected exception for class-based reflection: " . $e->getMessage() . "\n";
68+
}
69+
70+
try {
71+
$reflection = new ReflectionProperty($obj, 'dynamic');
72+
echo "Instance-based reflection works: " . $reflection->getMangledName() . "\n";
73+
} catch (ReflectionException $e) {
74+
echo "Unexpected exception: " . $e->getMessage() . "\n";
75+
}
76+
77+
?>
78+
--EXPECTF--
79+
=== Testing stdClass with dynamic properties ===
80+
stdClass property prop1:
81+
getName(): prop1
82+
getMangledName(): prop1
83+
Found in array cast: yes
84+
85+
stdClass property with special name:
86+
getName(): special-name
87+
getMangledName(): special-name
88+
Found in array cast: yes
89+
90+
stdClass property starting with number:
91+
getName(): 123numeric
92+
getMangledName(): 123numeric
93+
Found in array cast: yes
94+
95+
=== Testing edge cases ===
96+
Property name as number: EXCEPTION - Property stdClass::$1 does not exist
97+
98+
Property name with null byte:
99+
getName(): foo%0
100+
getMangledName(): foo%0
101+
Found in array cast: yes
102+
103+
Invalid property name:
104+
getName(): ::
105+
getMangledName(): ::
106+
Found in array cast: yes
107+
108+
=== Testing regular class with dynamic properties ===
109+
Regular class dynamic property:
110+
getName(): dynamic
111+
getMangledName(): dynamic
112+
Found in array cast: yes
113+
114+
Regular class another dynamic property:
115+
getName(): anotherDynamic
116+
getMangledName(): anotherDynamic
117+
Found in array cast: yes
118+
119+
Regular property:
120+
getName(): existing
121+
getMangledName(): existing
122+
123+
=== Testing ReflectionProperty from class vs instance ===
124+
Expected exception for class-based reflection: Property TestClass::$dynamic does not exist
125+
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)