Description
Symfony version(s) affected
4.4.49, 5.4.17 (those I've reproduced the issue on)
Description
Configuring a service that uses the wither pattern, added in #30212, can break the dumped PHP container with more complex configurations. The inlined service that uses the wither pattern for some reason is written into the container with the service ID of the dependant service, most of the time it is subsequently replaced by the correct implementation but in some configurations this does not happen.
As an example this YAML generates the following method:
services:
_defaults:
autoconfigure: true
App\TestServiceOptions:
public: false
calls:
- withOption: !returns_clone ['foo', 'bar']
App\TestService:
public: false
calls:
- setOptions: ['@App\TestServiceOptions']
App\TestCommand:
arguments:
- ~
- '@App\TestService'
/**
* Gets the private 'App\TestCommand' shared service.
*
* @return \App\TestCommand
*/
public static function do($container, $lazyLoad = true)
{
// include_once ...
$a = new \App\TestService();
$b = new \App\TestServiceOptions()
// Here: inlined wither service is written as App\TestCommand
$container->privates['App\TestCommand'] = $b = $b->withOption('foo', 'bar');
$a->setOptions($b);
// Invalid service is replaced here - fixing the problem.
$container->privates['App\\TestCommand'] = $instance = new \App\TestCommand(NULL, $a);
$instance->setName('test');
return $instance;
}
As I said above in some situations, the correct initialisation of $container->privates['App\\TestCommand']
is done before instead of after which then breaks other services as they get completely the wrong class.
How to reproduce
I've not managed to create a simple reproducer, but I do have one based on the Sentry Symfony bundle which is how I found this problem in the first place. https://github.com/cs278/symfony-issue-48814
git clone https://github.com/cs278/symfony-issue-48814.git
composer install --no-scripts
rm -rf var/cache/dev/
bin/console cache:clear -vvv
grep -h -R -B 10 -A 20 'SentryBeforeSend(' var/cache/dev/Container*/*.php
In this case the following service method is created:
/**
* Gets the private 'Sentry\State\HubInterface' shared service.
*
* @return \Sentry\State\HubInterface
*/
protected function getHubInterfaceService()
{
$this->privates['Sentry\\State\\HubInterface'] = $instance = \Sentry\State\HubAdapter::getInstance();
$a = new \App\SentryBeforeSend();
$this->privates['Sentry\State\HubInterface'] = $a = $a->withTag('foo', 'bar'); // Broken!
$b = new \Sentry\Options(['before_send' => $a, 'integrations' => [0 => new \Sentry\Integration\IgnoreErrorsIntegration(['ignore_exceptions' => [0 => 'Symfony\\Component\\ErrorHandler\\Error\\FatalError', 1 => 'Symfony\\Component\\Debug\\Exception\\FatalErrorException']]), 1 => new \Sentry\Integration\RequestIntegration(new \Sentry\SentryBundle\Integration\RequestFetcher(($this->services['request_stack'] ?? ($this->services['request_stack'] = new \Symfony\Component\HttpFoundation\RequestStack())), NULL))], 'prefixes' => [0 => \dirname(__DIR__, 4), 1 => '.', 2 => '/usr/share/php'], 'trace_propagation_targets' => [], 'environment' => 'dev', 'release' => 'dev-master@425c01f', 'tags' => [], 'in_app_exclude' => [0 => $this->targetDir.'', 1 => $this->targetDir.'', 2 => (\dirname(__DIR__, 4).'/vendor')], 'in_app_include' => [], 'class_serializers' => [], 'dsn' => $this->getEnv('SENTRY_DSN')]);
$c = new \Sentry\ClientBuilder($b);
$c->setSdkIdentifier('sentry.php.symfony');
$c->setSdkVersion('4.5.0');
$c->setTransportFactory(new \Sentry\SentryBundle\Transport\TransportFactory(NULL, NULL, NULL, NULL, NULL, NULL));
$c->setSerializer(new \Sentry\Serializer\Serializer($b));
$c->setRepresentationSerializer(new \Sentry\Serializer\RepresentationSerializer($b));
if ($this->has('Psr\\Log\\NullLogger')) {
$c->setLogger(($this->services['Psr\\Log\\NullLogger'] ?? $this->get('Psr\\Log\\NullLogger', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ 2)));
}
$instance->bindClient($c->getClient());
return $instance;
}
Possible Solution
I've got a PR incoming with a fix.
Additional Context
Workaround until a fix is released is to make the service that uses the returns_clone
tag public.