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

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
100 changes: 100 additions & 0 deletions ext/reflection/tests/ReflectionProperty_getMangledName_dynamic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
--TEST--
Test ReflectionProperty::getMangledName() with dynamic properties
--FILE--
<?php

echo "=== Testing stdClass with dynamic properties ===\n";
$stdObj = new stdClass();
$stdObj->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
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--TEST--
Test ReflectionProperty::getMangledName() with inheritance
--FILE--
<?php

class ParentClass {
public $public = 'parent_public';
protected $protected = 'parent_protected';
private $private = 'parent_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');

?>
--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
Original file line number Diff line number Diff line change
@@ -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'
Loading