Add ReflectionProperty::getMangledName() (#18980)

This commit is contained in:
Alexandre Daubois 2025-07-22 21:24:27 +02:00 committed by GitHub
parent 745e81bfd0
commit d292968f7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 583 additions and 1 deletions

1
NEWS
View file

@ -255,6 +255,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

View file

@ -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

View file

@ -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;

View file

@ -482,6 +482,8 @@ class ReflectionProperty implements Reflector
/** @tentative-return-type */
public function getName(): string {}
public function getMangledName(): string {}
/** @tentative-return-type */
public function getValue(?object $object = null): mixed {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 7a8d126a96f0115783bd20a9adfc6bdc5ee88fda */
* Stub hash: ef9e7f30a29819489e17a9c100f55696d5d164e0 */
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0)
@ -381,6 +381,8 @@ ZEND_END_ARG_INFO()
#define arginfo_class_ReflectionProperty_getName arginfo_class_ReflectionFunctionAbstract_getName
#define arginfo_class_ReflectionProperty_getMangledName arginfo_class_ReflectionFunction___toString
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionProperty_getValue, 0, 0, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, object, IS_OBJECT, 1, "null")
ZEND_END_ARG_INFO()
@ -853,6 +855,7 @@ ZEND_METHOD(ReflectionObject, __construct);
ZEND_METHOD(ReflectionProperty, __construct);
ZEND_METHOD(ReflectionProperty, __toString);
ZEND_METHOD(ReflectionProperty, getName);
ZEND_METHOD(ReflectionProperty, getMangledName);
ZEND_METHOD(ReflectionProperty, getValue);
ZEND_METHOD(ReflectionProperty, setValue);
ZEND_METHOD(ReflectionProperty, getRawValue);
@ -1155,6 +1158,7 @@ static const zend_function_entry class_ReflectionProperty_methods[] = {
ZEND_ME(ReflectionProperty, __construct, arginfo_class_ReflectionProperty___construct, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, __toString, arginfo_class_ReflectionProperty___toString, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, getName, arginfo_class_ReflectionProperty_getName, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, getMangledName, arginfo_class_ReflectionProperty_getMangledName, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, getValue, arginfo_class_ReflectionProperty_getValue, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, setValue, arginfo_class_ReflectionProperty_setValue, ZEND_ACC_PUBLIC)
ZEND_ME(ReflectionProperty, getRawValue, arginfo_class_ReflectionProperty_getRawValue, ZEND_ACC_PUBLIC)

View file

@ -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

View file

@ -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

View file

@ -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"
}

View file

@ -0,0 +1,87 @@
--TEST--
Test ReflectionProperty::getMangledName() with inheritance
--FILE--
<?php
class ParentClass {
public $public = 'parent_public';
protected $protected = 'parent_protected';
private $private = 'parent_private';
private $parentOnly = 'parent_only_private';
}
class ChildClass extends ParentClass {
private $private = 'child_private';
protected $childProp = 'child_protected';
}
function testProperty($class, $property) {
$reflection = new ReflectionProperty($class, $property);
$obj = new $class();
$array = (array) $obj;
echo "Class: $class, Property: \$$property\n";
echo "Mangled name: '" . $reflection->getMangledName() . "'\n";
echo "Key exists in array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "yes" : "no") . "\n";
echo "\n";
}
testProperty('ParentClass', 'public');
testProperty('ParentClass', 'protected');
testProperty('ParentClass', 'private');
testProperty('ChildClass', 'public');
testProperty('ChildClass', 'protected');
testProperty('ChildClass', 'childProp');
testProperty('ChildClass', 'private');
echo "Access to parent's private property not in child:\n";
try {
$reflection = new ReflectionProperty('ChildClass', 'parentOnly');
echo "ERROR: Should have failed\n";
} catch (ReflectionException $e) {
echo "Instance-based creation failed as expected: " . $e->getMessage() . "\n";
}
try {
$obj = new ChildClass();
$reflection = new ReflectionProperty($obj, 'parentOnly');
echo "ERROR: Should have failed\n";
} catch (ReflectionException $e) {
echo "Object-based creation failed as expected: " . $e->getMessage() . "\n";
}
?>
--EXPECTF--
Class: ParentClass, Property: $public
Mangled name: 'public'
Key exists in array cast: yes
Class: ParentClass, Property: $protected
Mangled name: '%0*%0protected'
Key exists in array cast: yes
Class: ParentClass, Property: $private
Mangled name: '%0ParentClass%0private'
Key exists in array cast: yes
Class: ChildClass, Property: $public
Mangled name: 'public'
Key exists in array cast: yes
Class: ChildClass, Property: $protected
Mangled name: '%0*%0protected'
Key exists in array cast: yes
Class: ChildClass, Property: $childProp
Mangled name: '%0*%0childProp'
Key exists in array cast: yes
Class: ChildClass, Property: $private
Mangled name: '%0ChildClass%0private'
Key exists in array cast: yes
Access to parent's private property not in child:
Instance-based creation failed as expected: Property ChildClass::$parentOnly does not exist
Object-based creation failed as expected: Property ChildClass::$parentOnly does not exist

View file

@ -0,0 +1,99 @@
--TEST--
Test ReflectionProperty::getMangledName() from instance vs class
--FILE--
<?php
#[AllowDynamicProperties]
class TestClass {
public $public = 'public';
protected $protected = 'protected';
private $private = 'private';
}
$obj = new TestClass();
$obj->dynamic = 'dynamic';
echo "=== Testing ReflectionProperty from CLASS ===\n";
function testFromClass($property) {
try {
$reflection = new ReflectionProperty('TestClass', $property);
echo "Property $property from class:\n";
echo " getName(): " . $reflection->getName() . "\n";
echo " getMangledName(): " . $reflection->getMangledName() . "\n";
echo "\n";
} catch (ReflectionException $e) {
echo "Property $property from class: EXCEPTION - " . $e->getMessage() . "\n\n";
}
}
testFromClass('public');
testFromClass('protected');
testFromClass('private');
testFromClass('dynamic');
echo "=== Testing ReflectionProperty from INSTANCE ===\n";
function testFromInstance($obj, $property) {
try {
$reflection = new ReflectionProperty($obj, $property);
echo "Property $property from instance:\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 "Property $property from instance: EXCEPTION - " . $e->getMessage() . "\n\n";
}
}
testFromInstance($obj, 'public');
testFromInstance($obj, 'protected');
testFromInstance($obj, 'private');
echo "=== Instance array keys ===\n";
$array = (array) $obj;
foreach (array_keys($array) as $key) {
echo "Key: '$key'\n";
}
?>
--EXPECTF--
=== Testing ReflectionProperty from CLASS ===
Property public from class:
getName(): public
getMangledName(): public
Property protected from class:
getName(): protected
getMangledName(): %0*%0protected
Property private from class:
getName(): private
getMangledName(): %0TestClass%0private
Property dynamic from class: EXCEPTION - Property TestClass::$dynamic does not exist
=== Testing ReflectionProperty from INSTANCE ===
Property public from instance:
getName(): public
getMangledName(): public
Found in array cast: yes
Property protected from instance:
getName(): protected
getMangledName(): %0*%0protected
Found in array cast: yes
Property private from instance:
getName(): private
getMangledName(): %0TestClass%0private
Found in array cast: yes
=== Instance array keys ===
Key: 'public'
Key: '%0*%0protected'
Key: '%0TestClass%0private'
Key: 'dynamic'

View file

@ -0,0 +1,123 @@
--TEST--
Test ReflectionProperty::getMangledName() with property overrides and visibility changes
--FILE--
<?php
class Parent1 {
public $prop = 'parent_public';
protected $protectedProp = 'parent_protected';
private $privateProp = 'parent_private';
}
class Child1 extends Parent1 {
// same visibility
public $prop = 'child_public';
// overridden visibility
public $protectedProp = 'child_public_from_protected';
}
class Parent2 {
protected $visibilityTest = 'parent_protected';
}
class Child2 extends Parent2 {
// overriden visibility
public $visibilityTest = 'child_public';
}
function testPropertyOverride($class, $property, $description) {
echo "$description:\n";
$reflection = new ReflectionProperty($class, $property);
echo " Class: $class\n";
echo " Property: $property\n";
echo " getName(): " . $reflection->getName() . "\n";
echo " getMangledName(): " . $reflection->getMangledName() . "\n";
echo " Visibility: " . ($reflection->isPublic() ? 'public' : ($reflection->isProtected() ? 'protected' : 'private')) . "\n";
$obj = new $class();
$array = (array) $obj;
echo " Found in array cast: " . (array_key_exists($reflection->getMangledName(), $array) ? "yes" : "no") . "\n";
echo "\n";
}
testPropertyOverride('Parent1', 'prop', 'Parent public property');
testPropertyOverride('Child1', 'prop', 'Child public property (overrides parent public)');
testPropertyOverride('Parent1', 'protectedProp', 'Parent protected property');
testPropertyOverride('Child1', 'protectedProp', 'Child public property (overrides parent protected)');
testPropertyOverride('Parent2', 'visibilityTest', 'Parent protected property');
testPropertyOverride('Child2', 'visibilityTest', 'Child public property (overrides parent protected)');
testPropertyOverride('Parent1', 'privateProp', 'Parent private property');
echo "Child1 instance array keys:\n";
$child1 = new Child1();
$array = (array) $child1;
foreach (array_keys($array) as $key) {
echo " '$key'\n";
}
?>
--EXPECTF--
Parent public property:
Class: Parent1
Property: prop
getName(): prop
getMangledName(): prop
Visibility: public
Found in array cast: yes
Child public property (overrides parent public):
Class: Child1
Property: prop
getName(): prop
getMangledName(): prop
Visibility: public
Found in array cast: yes
Parent protected property:
Class: Parent1
Property: protectedProp
getName(): protectedProp
getMangledName(): %0*%0protectedProp
Visibility: protected
Found in array cast: yes
Child public property (overrides parent protected):
Class: Child1
Property: protectedProp
getName(): protectedProp
getMangledName(): protectedProp
Visibility: public
Found in array cast: yes
Parent protected property:
Class: Parent2
Property: visibilityTest
getName(): visibilityTest
getMangledName(): %0*%0visibilityTest
Visibility: protected
Found in array cast: yes
Child public property (overrides parent protected):
Class: Child2
Property: visibilityTest
getName(): visibilityTest
getMangledName(): visibilityTest
Visibility: public
Found in array cast: yes
Parent private property:
Class: Parent1
Property: privateProp
getName(): privateProp
getMangledName(): %0Parent1%0privateProp
Visibility: private
Found in array cast: yes
Child1 instance array keys:
'prop'
'protectedProp'
'%0Parent1%0privateProp'