Skip to content

Add ReflectionProperty::getMangledName() #18980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ PHP NEWS
zval for uninitialized typed properties). (nielsdos)
. Fixed bug GH-15766 (ReflectionClass::toString() should have better output
for enums). (DanielEScherzer)
. Added ReflectionProperty::getMangledName() method. (alexandre-daubois)

- Session:
. session_start() throws a ValueError on option argument if not a hashmap
Expand Down
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ PHP 8.5 UPGRADE NOTES
ReflectionConstant::getExtensionName() were introduced.
. ReflectionConstant::getAttributes() was introduced.
RFC: https://wiki.php.net/rfc/attributes-on-constants
. ReflectionProperty::getMangledName() was introduced.

- Sqlite:
. Sqlite3Stmt::busy to check if a statement had been fetched
Expand Down
15 changes: 15 additions & 0 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -5753,6 +5753,21 @@ ZEND_METHOD(ReflectionProperty, getName)
}
/* }}} */

ZEND_METHOD(ReflectionProperty, getMangledName)
{
reflection_object *intern;
property_reference *ref;

ZEND_PARSE_PARAMETERS_NONE();

GET_REFLECTION_OBJECT_PTR(ref);
if (ref->prop == NULL) {
RETURN_STR_COPY(ref->unmangled_name);
}

RETURN_STR_COPY(ref->prop->name);
}

static void _property_check_flag(INTERNAL_FUNCTION_PARAMETERS, int mask) /* {{{ */
{
reflection_object *intern;
Expand Down
2 changes: 2 additions & 0 deletions ext/reflection/php_reflection.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,8 @@ public function __toString(): string {}
/** @tentative-return-type */
public function getName(): string {}

public function getMangledName(): string {}

/** @tentative-return-type */
public function getValue(?object $object = null): mixed {}

Expand Down
6 changes: 5 additions & 1 deletion ext/reflection/php_reflection_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
Test ReflectionProperty::getMangledName() method
--FILE--
<?php

class TestClass {
public $publicProp = 'public';
protected $protectedProp = 'protected';
private $privateProp = 'private';
}

function testMangledName($class, $property) {
$reflection = new ReflectionProperty($class, $property);
echo "Property: $property\n";
echo "getName(): " . $reflection->getName() . "\n";
echo "getMangledName(): " . $reflection->getMangledName() . "\n";

$obj = new $class();
$array = (array) $obj;
echo "In array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "found" : "not found") . "\n";
echo "\n";
}

testMangledName('TestClass', 'publicProp');
testMangledName('TestClass', 'protectedProp');
testMangledName('TestClass', 'privateProp');

?>
--EXPECTF--
Property: publicProp
getName(): publicProp
getMangledName(): publicProp
In array cast: found

Property: protectedProp
getName(): protectedProp
getMangledName(): %0*%0protectedProp
In array cast: found

Property: privateProp
getName(): privateProp
getMangledName(): %0TestClass%0privateProp
In array cast: found
125 changes: 125 additions & 0 deletions ext/reflection/tests/ReflectionProperty_getMangledName_dynamic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
--TEST--
Test ReflectionProperty::getMangledName() with dynamic properties
--FILE--
<?php

echo "=== Testing stdClass with dynamic properties ===\n";
$stdObj = new stdClass();
$stdObj->prop1 = 'value1';
$stdObj->{'special-name'} = 'special value';
$stdObj->{'123numeric'} = 'numeric start';

function testDynamicProperty($obj, $property, $description) {
try {
$reflection = new ReflectionProperty($obj, $property);
echo "$description:\n";
echo " getName(): " . $reflection->getName() . "\n";
echo " getMangledName(): " . $reflection->getMangledName() . "\n";

$array = (array) $obj;
echo " Found in array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "yes" : "no") . "\n";
echo "\n";
} catch (ReflectionException $e) {
echo "$description: EXCEPTION - " . $e->getMessage() . "\n\n";
}
}

testDynamicProperty($stdObj, 'prop1', 'stdClass property prop1');
testDynamicProperty($stdObj, 'special-name', 'stdClass property with special name');
testDynamicProperty($stdObj, '123numeric', 'stdClass property starting with number');

echo "=== Testing edge cases ===\n";
$numericObj = (object)[true];
testDynamicProperty($numericObj, '0', 'Property name as number');

$nullByteObj = (object)["foo\0" => true];
testDynamicProperty($nullByteObj, "foo\0", 'Property name with null byte');

$invalidObj = (object)["::" => true];
testDynamicProperty($invalidObj, '::', 'Invalid property name');

echo "=== Testing regular class with dynamic properties ===\n";
#[AllowDynamicProperties]
class TestClass {
public $existing = 'existing';
}

$obj = new TestClass();
$obj->dynamic = 'dynamic value';
$obj->anotherDynamic = 'another dynamic';

testDynamicProperty($obj, 'dynamic', 'Regular class dynamic property');
testDynamicProperty($obj, 'anotherDynamic', 'Regular class another dynamic property');

$reflection = new ReflectionProperty($obj, 'existing');
echo "Regular property:\n";
echo " getName(): " . $reflection->getName() . "\n";
echo " getMangledName(): " . $reflection->getMangledName() . "\n";

echo "\n=== Testing ReflectionProperty from class vs instance ===\n";
try {
$reflection = new ReflectionProperty('TestClass', 'dynamic');
echo "This should not be reached\n";
} catch (ReflectionException $e) {
echo "Expected exception for class-based reflection: " . $e->getMessage() . "\n";
}

try {
$reflection = new ReflectionProperty($obj, 'dynamic');
echo "Instance-based reflection works: " . $reflection->getMangledName() . "\n";
} catch (ReflectionException $e) {
echo "Unexpected exception: " . $e->getMessage() . "\n";
}

?>
--EXPECTF--
=== Testing stdClass with dynamic properties ===
stdClass property prop1:
getName(): prop1
getMangledName(): prop1
Found in array cast: yes

stdClass property with special name:
getName(): special-name
getMangledName(): special-name
Found in array cast: yes

stdClass property starting with number:
getName(): 123numeric
getMangledName(): 123numeric
Found in array cast: yes

=== Testing edge cases ===
Property name as number:
getName(): 0
getMangledName(): 0
Found in array cast: yes

Property name with null byte:
getName(): foo%0
getMangledName(): foo%0
Found in array cast: yes

Invalid property name:
getName(): ::
getMangledName(): ::
Found in array cast: yes

=== Testing regular class with dynamic properties ===
Regular class dynamic property:
getName(): dynamic
getMangledName(): dynamic
Found in array cast: yes

Regular class another dynamic property:
getName(): anotherDynamic
getMangledName(): anotherDynamic
Found in array cast: yes

Regular property:
getName(): existing
getMangledName(): existing

=== Testing ReflectionProperty from class vs instance ===
Expected exception for class-based reflection: Property TestClass::$dynamic does not exist
Instance-based reflection works: dynamic
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
--TEST--
Test ReflectionProperty::getMangledName() with property hooks
--FILE--
<?php

echo "=== Testing virtual hooked properties ===\n";

class Demo {
protected string $foo {
get => "virtual";
}

public string $bar {
get => "hooked getter";
set => throw new Exception("Cannot set bar");
}

public string $baz = "backed";
}

$d = new Demo();

function testHookedProperty($obj, $property, $description) {
try {
$reflection = new ReflectionProperty($obj, $property);
echo "$description:\n";
echo " getName(): " . $reflection->getName() . "\n";
echo " getMangledName(): " . $reflection->getMangledName() . "\n";

$array = (array) $obj;
echo " Found in array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "yes" : "no") . "\n";
echo " Has hooks: " . ($reflection->hasHooks() ? "yes" : "no") . "\n";
echo "\n";
} catch (ReflectionException $e) {
echo "$description: EXCEPTION - " . $e->getMessage() . "\n\n";
}
}

testHookedProperty($d, 'foo', 'Virtual hooked property (protected)');
testHookedProperty($d, 'bar', 'Hooked property with getter/setter (public)');
testHookedProperty($d, 'baz', 'Regular backed property');

echo "=== Object dump ===\n";
var_dump($d);

echo "\n=== Array cast ===\n";
var_dump((array)$d);

?>
--EXPECTF--
=== Testing virtual hooked properties ===
Virtual hooked property (protected):
getName(): foo
getMangledName(): %0*%0foo
Found in array cast: no
Has hooks: yes

Hooked property with getter/setter (public):
getName(): bar
getMangledName(): bar
Found in array cast: no
Has hooks: yes

Regular backed property:
getName(): baz
getMangledName(): baz
Found in array cast: yes
Has hooks: no

=== Object dump ===
object(Demo)#1 (1) {
["bar"]=>
uninitialized(string)
["baz"]=>
string(6) "backed"
}

=== Array cast ===
array(1) {
["baz"]=>
string(6) "backed"
}
Loading