diff --git a/NEWS b/NEWS index f3c92852e4db5..cbe525f13cf2e 100644 --- a/NEWS +++ b/NEWS @@ -187,6 +187,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 diff --git a/UPGRADING b/UPGRADING index 5491d85f529d8..f521926e140fe 100644 --- a/UPGRADING +++ b/UPGRADING @@ -420,6 +420,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 diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8ef0269481cf7..9e2ce7a8f2772 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -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; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 63518b446ad86..67fb849db5845 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -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 {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index d50dc04ae3d15..0e5238420245a 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -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) diff --git a/ext/reflection/tests/ReflectionProperty_getMangledName_basic.phpt b/ext/reflection/tests/ReflectionProperty_getMangledName_basic.phpt new file mode 100644 index 0000000000000..473ac2c84b816 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_getMangledName_basic.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test ReflectionProperty::getMangledName() method +--FILE-- +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 diff --git a/ext/reflection/tests/ReflectionProperty_getMangledName_dynamic.phpt b/ext/reflection/tests/ReflectionProperty_getMangledName_dynamic.phpt new file mode 100644 index 0000000000000..0c7abf4ae41df --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_getMangledName_dynamic.phpt @@ -0,0 +1,100 @@ +--TEST-- +Test ReflectionProperty::getMangledName() with dynamic properties +--FILE-- +prop1 = 'value1'; +$stdObj->prop2 = 'value2'; +$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 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 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 diff --git a/ext/reflection/tests/ReflectionProperty_getMangledName_inheritance.phpt b/ext/reflection/tests/ReflectionProperty_getMangledName_inheritance.phpt new file mode 100644 index 0000000000000..4424e88759bb3 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_getMangledName_inheritance.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test ReflectionProperty::getMangledName() with inheritance +--FILE-- +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'); + +?> +--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 diff --git a/ext/reflection/tests/ReflectionProperty_getMangledName_instance.phpt b/ext/reflection/tests/ReflectionProperty_getMangledName_instance.phpt new file mode 100644 index 0000000000000..7c24ce85f1734 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_getMangledName_instance.phpt @@ -0,0 +1,99 @@ +--TEST-- +Test ReflectionProperty::getMangledName() from instance vs class +--FILE-- +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' diff --git a/ext/reflection/tests/ReflectionProperty_getMangledName_override.phpt b/ext/reflection/tests/ReflectionProperty_getMangledName_override.phpt new file mode 100644 index 0000000000000..3f6a3bfdcc5e8 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_getMangledName_override.phpt @@ -0,0 +1,123 @@ +--TEST-- +Test ReflectionProperty::getMangledName() with property overrides and visibility changes +--FILE-- +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'