Description
Symfony version(s) affected
6.2
Description
When you have multiple lazy services that use the same class but are configured differently (one without a factory, one with a factory) the lazy object generation is broken.
The reason is that for the class in question only one ProxyObject
is generated, which either uses the LazyGhostTrait
or LazyProxytTrait
, but not both. This proxy object is used for both services, but as one of those services uses a factory it expects an LazyProxy
, the service that does not use a factory expects the LazyGhost
. That means that one object cannot be instantiated.
Our specific use case is that we have several GuzzleClient
s as different services configured, as they use different middlewares and different configurations. Some of them are marked as lazy which leads to the problems described above.
How to reproduce
Having a service declaration like the following:
<service id="store_client" class="GuzzleHttp\Client" lazy="true">
<factory service="StoreClientFactory" method="create"/>
</service>
<service id="app_system.guzzle" class="GuzzleHttp\Client" lazy="true">
<argument type="collection">
<argument key="timeout">5</argument>
<argument key="connect_timeout">1</argument>
</argument>
</service>
Will lead to the following proxy class being generated:
class Client_1efc5d2 extends \GuzzleHttp\Client implements \Symfony\Component\VarExporter\LazyObjectInterface
{
use \Symfony\Component\VarExporter\LazyGhostTrait;
private const LAZY_OBJECT_PROPERTY_SCOPES = [
"\0".parent::class."\0".'config' => [parent::class, 'config', null],
'config' => [parent::class, 'config', null],
];
}
Note: This example is greatly simplified, in our case this proxy is generated, i'm not sure but i suppose that it may also happen that a proxy will be generated that uses the LazyProxyTrait
, but that will lead to the same problem down the line.
The generated service container will use the generated proxy in the following way:
/**
* Gets the private 'store_client' shared service.
*
* @return \GuzzleHttp\Client
*/
protected function getStoreClientService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->privates['store_client'] = $this->createProxy('Client_1efc5d2', fn () => \Client_1efc5d2::createLazyProxy(fn () => $this->getStoreClientService(false)));
}
return (new StoreClientFactory())->create();
}
/**
* Gets the private 'app_system.guzzle' shared service.
*
* @return \GuzzleHttp\Client
*/
protected function getAppSystem_GuzzleService($lazyLoad = true)
{
if (true === $lazyLoad) {
return $this->privates['app_system.guzzle'] = $this->createProxy('Client_1efc5d2', fn () => \Client_1efc5d2::createLazyGhost($this->getAppSystem_GuzzleService(...)));
}
return ($lazyLoad->__construct(['timeout' => 5, 'connect_timeout' => 1]) && false ?: $lazyLoad);
}
Important: You can see that in the getter for the first service (that uses a factory) it is expected that the proxy class is a LazyProxy
, but for the second service (without a factory) it expects a LazyGhost
, but the auto generated proxy uses only one of those traits. This leads to problems in the creation of on of those services, because in fact the proxy does not implement the called method.
Possible Solution
One solution might be that in cases like the one described above the auto generated proxy class for the GuzzleClient
uses both the LazyGhostTrait
and the LazyProxyTrait
. But i'm not sure if the traits are designed to be used in one class simultaneously or if the traits conflict with each other.
If the latter is the case at least two proxies should be generated for the same class in cases like the one above, one implementing the LazyGhostTrait
and the other implementing the LazyObjectTrait
.
Additional Context
No response