proxy-manager-lts
proxy-manager-lts copied to clipboard
Public readonly properties
I have an issue when trying to read public readonly properties from the Proxy. I got an error:
Cannot initialize readonly property App\Entity\Customer::$firstName from scope App\Command\Command
Library version: 1.0.12
Preconditions: We have a class only with public properties.
<?php
declare(strict_types=1);
namespace App\Entity;
class Customer
{
public function __construct(
public readonly string $firstName,
public readonly string $lastName,
) {
}
}
Steps to Reproduce:
- Create a Proxy object
$instance = (new LazyLoadingGhostFactory())->createProxy(
Customer::class,
static function (
GhostObjectInterface $ghostObject,
string $method,
array $parameters,
?Closure &$initializer,
array $properties,
) {
$initializer = null;
$properties['firstName'] = 'User';
$properties['lastName'] = 'Test';
return true;
},
);
- Getting value from any public property results in an error.
Cannot initialize readonly property App\Entity\Customer::$firstName from scope App\Command\Command
$output->writeln('First Name: ' . $customer->firstName);
$output->writeln('Last Name: ' . $customer->lastName);
After investigation, I see the $class
and $scopeObject
variables equal to the App\Command\Command
.
} elseif (isset(self::$privateProperties98259[$name])) {
$callers = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
$caller = isset($callers[1]) ? $callers[1] : [];
$class = isset($caller['class']) ? $caller['class'] : '';
...
}
...
$targetObject = $realInstanceReflection->newInstanceWithoutConstructor();
$accessor = function & () use ($targetObject, $name) {
return $targetObject->$name;
};
$backtrace = debug_backtrace(true, 2);
$scopeObject = isset($backtrace[1]['object']) ? $backtrace[1]['object'] : new \ProxyManager\Stub\EmptyClassStub();
Note But if we will add a getter for the property all works without errors.
class Customer
{
public function __construct(
public readonly string $firstName,
public readonly string $lastName,
) {
}
public function getFirstName(): string
{
return $this->firstName;
}
}
$output->writeln('First Name: ' . $customer->getFirstName());
$output->writeln('Last Name: ' . $customer->lastName);
Can you provide a reproducing test case please?
This test case fails:
--TEST--
Verifies that public readonly properties can be used
--FILE--
<?php
require_once __DIR__ . '/init.php';
use ProxyManager\Proxy\GhostObjectInterface;
class Kitchen
{
public readonly string $sweets;
}
$factory = new \ProxyManager\Factory\LazyLoadingGhostFactory($configuration);
$proxy = $factory->createProxy(Kitchen::class, function (
GhostObjectInterface $ghostObject,
string $method,
array $parameters,
?Closure &$initializer,
array $properties,
) {
$properties['sweets'] = 'cookies';
});
echo $proxy->sweets;
?>
--EXPECTF--
cookies
The reason is that PublicScopeSimulator
generates an accessor that returns by reference for __get()
.
Feel free to give a fix a try. Alternatively, you might want to wait for Symfony 6.2, which will provide another implementation of ghost objects that's free from this issue already. See https://symfony.com/blog/revisiting-lazy-loading-proxies-in-php