How do I change a readonly property using reflection in PHP 8.1?
Asked Answered
H

2

8

Is there any way, using reflection or otherwise, to change a readonly property that has already been set?

We sometimes do that in tests, and we don't want to avoid using readonly props just for testing purposes.

class Acme {
    public function __construct(
        private readonly int $changeMe,
    ) {}
}

$object= new Acme(1);
$reflectionClass = new ReflectionClass($object);
$reflectionProperty = $reflectionClass->getProperty('changeMe');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($object, 2);
Fatal error: Uncaught Error: Cannot modify readonly property Acme::$changeMe
Hon answered 18/3, 2022 at 17:12 Comment(1)
At a cursory glance, the RFC proposing the "readonly" keyword just says "you can't do that": wiki.php.net/rfc/readonly_properties_v2#reflection Not posting as an answer, because I haven't looked at the final implementation to see if anything was added later.Ascidium
F
5

The only way I can think of to change a readonly property is to reflect the object without calling its constructor. However, not sure if it's useful in your particular case

class Acme {
    public function __construct(
        public readonly int $changeMe,
    ) {}
}

$object = new Acme(1);
$reflection = new ReflectionClass($object);
$instance = $reflection->newInstanceWithoutConstructor();
$reflectionProperty = $reflection->getProperty('changeMe');
$reflectionProperty->setValue($instance, 33);

var_dump($reflectionProperty->getValue($instance)); // 33

https://3v4l.org/mis1l#v8.1.0

Note: we are not actually "changing" the property we are just setting it for the first time since no constructor is called.

Federico answered 19/3, 2022 at 20:46 Comment(1)
Note: (new ReflectionProperty($a, 'a'))->setValue($a, 20); doesn't work, it still results in "Cannot modify readonly property"Hysterics
E
0

@Rain is correct. But to expand on that a little, what you need to do is effectively clone the target object one property at a time, including all private properties of inherited classes, except the property you intend to alter. Then you can alter it. It would look something like this:

function setValue(object &$object, string $propertyName, mixed $value): void
{
    $objReflection = new ReflectionClass($object);

    if ($objReflection->getProperty($propertyName)->isReadOnly()) {
        $mutable = $objReflection->newInstanceWithoutConstructor();

        do {
            foreach ($objReflection->getProperties() as $property) {
                if ($property->isInitialized($object) && $property->name != $propertyName) {
                    $objReflection->getProperty($property->name)->setValue($mutable, $property->getValue($object));
                }
            }
        } while ($objReflection = $objReflection->getParentClass());

        $object = $mutable;
    }

    $objReflection->getProperty($propertyName)->setValue($object, $value);
}
Enterotomy answered 28/8, 2024 at 19:17 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.