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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 @@ -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
Expand Down
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
--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 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";
}

?>
--EXPECT--
=== 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
Original file line number Diff line number Diff line change
@@ -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');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also test with a private property on the parent class that does not get shadowed with a property of the same name in the child class? In both instance and class based creation of ReflectionProperty the constructor should fail, but its good to confirm so that if it ever changes we have a test that gets updated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


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
Loading