diff --git a/Attribute/Autoconfigure.php b/Attribute/Autoconfigure.php index dc2c84ca2..06513fd90 100644 --- a/Attribute/Autoconfigure.php +++ b/Attribute/Autoconfigure.php @@ -28,7 +28,7 @@ class Autoconfigure * @param bool|null $shared Whether to declare the service as shared * @param bool|null $autowire Whether to declare the service as autowired * @param array|null $properties The properties to define when creating the service - * @param array|string|null $configurator A PHP function, reference or an array containing a class/Reference and a method to call after the service is fully initialized + * @param array{string, string}|string|null $configurator A PHP function, reference or an array containing a class/reference and a method to call after the service is fully initialized * @param string|null $constructor The public static method to use to instantiate the service */ public function __construct( diff --git a/Compiler/CheckCircularReferencesPass.php b/Compiler/CheckCircularReferencesPass.php index 9b43d6e64..7adb0b4d8 100644 --- a/Compiler/CheckCircularReferencesPass.php +++ b/Compiler/CheckCircularReferencesPass.php @@ -28,6 +28,7 @@ class CheckCircularReferencesPass implements CompilerPassInterface { private array $currentPath; private array $checkedNodes; + private array $checkedLazyNodes; /** * Checks the ContainerBuilder object for circular references. @@ -57,22 +58,36 @@ private function checkOutEdges(array $edges): void $node = $edge->getDestNode(); $id = $node->getId(); - if (empty($this->checkedNodes[$id])) { - // Don't check circular references for lazy edges - if (!$node->getValue() || (!$edge->isLazy() && !$edge->isWeak())) { - $searchKey = array_search($id, $this->currentPath); - $this->currentPath[] = $id; + if (!empty($this->checkedNodes[$id])) { + continue; + } + + $isLeaf = !!$node->getValue(); + $isConcrete = !$edge->isLazy() && !$edge->isWeak(); + + // Skip already checked lazy services if they are still lazy. Will not gain any new information. + if (!empty($this->checkedLazyNodes[$id]) && (!$isLeaf || !$isConcrete)) { + continue; + } - if (false !== $searchKey) { - throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey)); - } + // Process concrete references, otherwise defer check circular references for lazy edges. + if (!$isLeaf || $isConcrete) { + $searchKey = array_search($id, $this->currentPath); + $this->currentPath[] = $id; - $this->checkOutEdges($node->getOutEdges()); + if (false !== $searchKey) { + throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey)); } + $this->checkOutEdges($node->getOutEdges()); + $this->checkedNodes[$id] = true; - array_pop($this->currentPath); + unset($this->checkedLazyNodes[$id]); + } else { + $this->checkedLazyNodes[$id] = true; } + + array_pop($this->currentPath); } } } diff --git a/Compiler/InlineServiceDefinitionsPass.php b/Compiler/InlineServiceDefinitionsPass.php index ddea146fd..de4acb258 100644 --- a/Compiler/InlineServiceDefinitionsPass.php +++ b/Compiler/InlineServiceDefinitionsPass.php @@ -223,6 +223,8 @@ private function isInlineableDefinition(string $id, Definition $definition): boo return false; } - return $this->container->getDefinition($srcId)->isShared(); + $srcDefinition = $this->container->getDefinition($srcId); + + return $srcDefinition->isShared() && !$srcDefinition->isLazy(); } } diff --git a/Compiler/ValidateEnvPlaceholdersPass.php b/Compiler/ValidateEnvPlaceholdersPass.php index 783080c09..b656cf8d4 100644 --- a/Compiler/ValidateEnvPlaceholdersPass.php +++ b/Compiler/ValidateEnvPlaceholdersPass.php @@ -46,17 +46,8 @@ public function process(ContainerBuilder $container): void $defaultBag = new ParameterBag($resolvingBag->all()); $envTypes = $resolvingBag->getProvidedTypes(); foreach ($resolvingBag->getEnvPlaceholders() + $resolvingBag->getUnusedEnvPlaceholders() as $env => $placeholders) { - $values = []; - if (false === $i = strpos($env, ':')) { - $default = $defaultBag->has("env($env)") ? $defaultBag->get("env($env)") : self::TYPE_FIXTURES['string']; - $defaultType = null !== $default ? get_debug_type($default) : 'string'; - $values[$defaultType] = $default; - } else { - $prefix = substr($env, 0, $i); - foreach ($envTypes[$prefix] ?? ['string'] as $type) { - $values[$type] = self::TYPE_FIXTURES[$type] ?? null; - } - } + $values = $this->getPlaceholderValues($env, $defaultBag, $envTypes); + foreach ($placeholders as $placeholder) { BaseNode::setPlaceholder($placeholder, $values); } @@ -97,4 +88,50 @@ public function getExtensionConfig(): array $this->extensionConfig = []; } } + + /** + * @param array> $envTypes + * + * @return array + */ + private function getPlaceholderValues(string $env, ParameterBag $defaultBag, array $envTypes): array + { + if (false === $i = strpos($env, ':')) { + [$default, $defaultType] = $this->getParameterDefaultAndDefaultType("env($env)", $defaultBag); + + return [$defaultType => $default]; + } + + $prefix = substr($env, 0, $i); + if ('default' === $prefix) { + $parts = explode(':', $env); + array_shift($parts); // Remove 'default' prefix + $parameter = array_shift($parts); // Retrieve and remove parameter + + [$defaultParameter, $defaultParameterType] = $this->getParameterDefaultAndDefaultType($parameter, $defaultBag); + + return [ + $defaultParameterType => $defaultParameter, + ...$this->getPlaceholderValues(implode(':', $parts), $defaultBag, $envTypes), + ]; + } + + $values = []; + foreach ($envTypes[$prefix] ?? ['string'] as $type) { + $values[$type] = self::TYPE_FIXTURES[$type] ?? null; + } + + return $values; + } + + /** + * @return array{0: string, 1: string} + */ + private function getParameterDefaultAndDefaultType(string $name, ParameterBag $defaultBag): array + { + $default = $defaultBag->has($name) ? $defaultBag->get($name) : self::TYPE_FIXTURES['string']; + $defaultType = null !== $default ? get_debug_type($default) : 'string'; + + return [$default, $defaultType]; + } } diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index 37793a1ee..ee7e519a0 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -338,7 +338,7 @@ class %s extends {$options['class']} EOF; foreach ($this->preload as $class) { - if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) { + if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void', 'never'], true)) { continue; } if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) { @@ -831,8 +831,7 @@ private function addService(string $id, Definition $definition): array if ($class = $definition->getClass()) { $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); $return[] = \sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); - } elseif ($definition->getFactory()) { - $factory = $definition->getFactory(); + } elseif ($factory = $definition->getFactory()) { if (\is_string($factory) && !str_starts_with($factory, '@=')) { $return[] = \sprintf('@return object An instance returned by %s()', $factory); } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { @@ -1152,9 +1151,7 @@ private function addNewInstance(Definition $definition, string $return = '', ?st $arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); } - if (null !== $definition->getFactory()) { - $callable = $definition->getFactory(); - + if ($callable = $definition->getFactory()) { if ('current' === $callable && [0] === array_keys($definition->getArguments()) && \is_array($value) && [0] === array_keys($value)) { return $return.$this->dumpValue($value[0]).$tail; } @@ -2197,6 +2194,12 @@ private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) { return false; } + + // When the source node is a proxy or ghost, it will construct its references only when the node itself is initialized. + // Since the node can be cloned before being fully initialized, we do not know how often its references are used. + if ($this->getProxyDumper()->isProxyCandidate($value)) { + return false; + } $ids[$edge->getSourceNode()->getId()] = true; } @@ -2305,7 +2308,6 @@ private function getAutoloadFile(): ?string private function getClasses(Definition $definition, string $id): array { $classes = []; - $resolve = $this->container->getParameterBag()->resolveValue(...); while ($definition instanceof Definition) { foreach ($definition->getTag($this->preloadTags[0]) as $tag) { @@ -2317,24 +2319,24 @@ private function getClasses(Definition $definition, string $id): array } if ($class = $definition->getClass()) { - $classes[] = trim($resolve($class), '\\'); + $classes[] = trim($class, '\\'); } $factory = $definition->getFactory(); + if (\is_string($factory) && !str_starts_with($factory, '@=') && str_contains($factory, '::')) { + $factory = explode('::', $factory); + } + if (!\is_array($factory)) { - $factory = [$factory]; + $definition = $factory; + continue; } - if (\is_string($factory[0])) { - $factory[0] = $resolve($factory[0]); + $definition = $factory[0] ?? null; - if (false !== $i = strrpos($factory[0], '::')) { - $factory[0] = substr($factory[0], 0, $i); - } + if (\is_string($definition)) { $classes[] = trim($factory[0], '\\'); } - - $definition = $factory[0]; } return $classes; diff --git a/Tests/Compiler/CheckCircularReferencesPassTest.php b/Tests/Compiler/CheckCircularReferencesPassTest.php index c9bcb1087..20a0a7b5a 100644 --- a/Tests/Compiler/CheckCircularReferencesPassTest.php +++ b/Tests/Compiler/CheckCircularReferencesPassTest.php @@ -13,9 +13,12 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; use Symfony\Component\DependencyInjection\Compiler\Compiler; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; use Symfony\Component\DependencyInjection\Reference; @@ -126,6 +129,21 @@ public function testProcessIgnoresLazyServices() $this->addToAssertionCount(1); } + public function testProcessDefersLazyServices() + { + $container = new ContainerBuilder(); + + $container->register('a')->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('tag', needsIndexes: true))); + $container->register('b')->addArgument(new Reference('c'))->addTag('tag'); + $container->register('c')->addArgument(new Reference('b')); + + (new ServiceLocatorTagPass())->process($container); + + $this->expectException(ServiceCircularReferenceException::class); + + $this->process($container); + } + public function testProcessIgnoresIteratorArguments() { $container = new ContainerBuilder(); diff --git a/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php b/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php index 8c5c4cc32..17ef87c3f 100644 --- a/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php +++ b/Tests/Compiler/ValidateEnvPlaceholdersPassTest.php @@ -73,6 +73,36 @@ public function testDefaultEnvWithoutPrefixIsValidatedInConfig() $this->doProcess($container); } + public function testDefaultProcessorWithScalarNode() + { + $container = new ContainerBuilder(); + $container->setParameter('parameter_int', 12134); + $container->setParameter('env(FLOATISH)', 4.2); + $container->registerExtension($ext = new EnvExtension()); + $container->prependExtensionConfig('env_extension', $expected = [ + 'scalar_node' => '%env(default:parameter_int:FLOATISH)%', + ]); + + $this->doProcess($container); + $this->assertSame($expected, $container->resolveEnvPlaceholders($ext->getConfig())); + } + + public function testDefaultProcessorAndAnotherProcessorWithScalarNode() + { + $this->expectException(InvalidTypeException::class); + $this->expectExceptionMessageMatches('/^Invalid type for path "env_extension\.scalar_node"\. Expected one of "bool", "int", "float", "string", but got one of "int", "array"\.$/'); + + $container = new ContainerBuilder(); + $container->setParameter('parameter_int', 12134); + $container->setParameter('env(JSON)', '{ "foo": "bar" }'); + $container->registerExtension($ext = new EnvExtension()); + $container->prependExtensionConfig('env_extension', [ + 'scalar_node' => '%env(default:parameter_int:json:JSON)%', + ]); + + $this->doProcess($container); + } + public function testEnvsAreValidatedInConfigWithInvalidPlaceholder() { $this->expectException(InvalidTypeException::class); diff --git a/Tests/Dumper/PhpDumperTest.php b/Tests/Dumper/PhpDumperTest.php index 5523c147e..95abb7087 100644 --- a/Tests/Dumper/PhpDumperTest.php +++ b/Tests/Dumper/PhpDumperTest.php @@ -56,6 +56,8 @@ use Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface; use Symfony\Component\DependencyInjection\Tests\Compiler\Wither; use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition; +use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainer; +use Symfony\Component\DependencyInjection\Tests\Fixtures\DependencyContainerInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooClassWithEnumAttribute; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooUnitEnum; use Symfony\Component\DependencyInjection\Tests\Fixtures\FooWithAbstractArgument; @@ -1668,6 +1670,59 @@ public function testWitherWithStaticReturnType() $this->assertInstanceOf(Foo::class, $wither->foo); } + public function testCloningLazyGhostWithDependency() + { + $container = new ContainerBuilder(); + $container->register('dependency', \stdClass::class); + $container->register(DependencyContainer::class) + ->addArgument(new Reference('dependency')) + ->setLazy(true) + ->setPublic(true); + + $container->compile(); + $dumper = new PhpDumper($container); + $dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency']); + eval('?>'.$dump); + + $container = new \Symfony_DI_PhpDumper_Service_CloningLazyGhostWithDependency(); + + $bar = $container->get(DependencyContainer::class); + $this->assertInstanceOf(DependencyContainer::class, $bar); + + $first_clone = clone $bar; + $second_clone = clone $bar; + + $this->assertSame($first_clone->dependency, $second_clone->dependency); + } + + public function testCloningProxyWithDependency() + { + $container = new ContainerBuilder(); + $container->register('dependency', \stdClass::class); + $container->register(DependencyContainer::class) + ->addArgument(new Reference('dependency')) + ->setLazy(true) + ->addTag('proxy', [ + 'interface' => DependencyContainerInterface::class, + ]) + ->setPublic(true); + + $container->compile(); + $dumper = new PhpDumper($container); + $dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_CloningProxyWithDependency']); + eval('?>'.$dump); + + $container = new \Symfony_DI_PhpDumper_Service_CloningProxyWithDependency(); + + $bar = $container->get(DependencyContainer::class); + $this->assertInstanceOf(DependencyContainerInterface::class, $bar); + + $first_clone = clone $bar; + $second_clone = clone $bar; + + $this->assertSame($first_clone->getDependency(), $second_clone->getDependency()); + } + public function testCurrentFactoryInlining() { $container = new ContainerBuilder(); diff --git a/Tests/Fixtures/DependencyContainer.php b/Tests/Fixtures/DependencyContainer.php new file mode 100644 index 000000000..5e222bdf0 --- /dev/null +++ b/Tests/Fixtures/DependencyContainer.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +class DependencyContainer implements DependencyContainerInterface +{ + public function __construct( + public mixed $dependency, + ) { + } + + public function getDependency(): mixed + { + return $this->dependency; + } +} diff --git a/Tests/Fixtures/DependencyContainerInterface.php b/Tests/Fixtures/DependencyContainerInterface.php new file mode 100644 index 000000000..ed109cad7 --- /dev/null +++ b/Tests/Fixtures/DependencyContainerInterface.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +interface DependencyContainerInterface +{ + public function getDependency(): mixed; +} diff --git a/Tests/Fixtures/config/child.expected.yml b/Tests/Fixtures/config/child.expected.yml index 44dbbd571..97380f388 100644 --- a/Tests/Fixtures/config/child.expected.yml +++ b/Tests/Fixtures/config/child.expected.yml @@ -11,7 +11,9 @@ services: - container.decorator: { id: bar, inner: b } file: file.php lazy: true - arguments: [!service { class: Class1 }] + arguments: ['@b'] + b: + class: Class1 bar: alias: foo public: true diff --git a/Tests/Fixtures/config/from_callable.expected.yml b/Tests/Fixtures/config/from_callable.expected.yml index d4dbbbadd..1ab1643af 100644 --- a/Tests/Fixtures/config/from_callable.expected.yml +++ b/Tests/Fixtures/config/from_callable.expected.yml @@ -8,5 +8,7 @@ services: class: stdClass public: true lazy: true - arguments: [[!service { class: stdClass }, do]] + arguments: [['@bar', do]] factory: [Closure, fromCallable] + bar: + class: stdClass diff --git a/Tests/Fixtures/php/callable_adapter_consumer.php b/Tests/Fixtures/php/callable_adapter_consumer.php index ccd8d2e0b..216dca434 100644 --- a/Tests/Fixtures/php/callable_adapter_consumer.php +++ b/Tests/Fixtures/php/callable_adapter_consumer.php @@ -50,6 +50,6 @@ public function getRemovedIds(): array */ protected static function getBarService($container) { - return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\CallableAdapterConsumer(new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } }); + return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\CallableAdapterConsumer(new class(fn () => (new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } }); } } diff --git a/Tests/Fixtures/php/closure_proxy.php b/Tests/Fixtures/php/closure_proxy.php index 2bef92604..eaf303c7d 100644 --- a/Tests/Fixtures/php/closure_proxy.php +++ b/Tests/Fixtures/php/closure_proxy.php @@ -55,6 +55,6 @@ protected function createProxy($class, \Closure $factory) */ protected static function getClosureProxyService($container, $lazyLoad = true) { - return $container->services['closure_proxy'] = new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } }; + return $container->services['closure_proxy'] = new class(fn () => ($container->privates['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure implements \Symfony\Component\DependencyInjection\Tests\Compiler\SingleMethodInterface { public function theMethod() { return $this->service->cloneFoo(...\func_get_args()); } }; } } diff --git a/Tests/Fixtures/php/lazy_closure.php b/Tests/Fixtures/php/lazy_closure.php index 0af28f265..2bf27779d 100644 --- a/Tests/Fixtures/php/lazy_closure.php +++ b/Tests/Fixtures/php/lazy_closure.php @@ -57,7 +57,7 @@ protected function createProxy($class, \Closure $factory) */ protected static function getClosure1Service($container, $lazyLoad = true) { - return $container->services['closure1'] = (new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function cloneFoo(?\stdClass $bar = null): \Symfony\Component\DependencyInjection\Tests\Compiler\Foo { return $this->service->cloneFoo(...\func_get_args()); } })->cloneFoo(...); + return $container->services['closure1'] = (new class(fn () => ($container->privates['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function cloneFoo(?\stdClass $bar = null): \Symfony\Component\DependencyInjection\Tests\Compiler\Foo { return $this->service->cloneFoo(...\func_get_args()); } })->cloneFoo(...); } /** @@ -67,6 +67,6 @@ protected static function getClosure1Service($container, $lazyLoad = true) */ protected static function getClosure2Service($container, $lazyLoad = true) { - return $container->services['closure2'] = (new class(fn () => new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid()) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name): void { $this->service->__invoke(...\func_get_args()); } })->__invoke(...); + return $container->services['closure2'] = (new class(fn () => ($container->privates['foo_void'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\FooVoid())) extends \Symfony\Component\DependencyInjection\Argument\LazyClosure { public function __invoke(string $name): void { $this->service->__invoke(...\func_get_args()); } })->__invoke(...); } } diff --git a/Tests/Fixtures/php/services_almost_circular_private.php b/Tests/Fixtures/php/services_almost_circular_private.php index 0a9c519c8..0c234ac39 100644 --- a/Tests/Fixtures/php/services_almost_circular_private.php +++ b/Tests/Fixtures/php/services_almost_circular_private.php @@ -373,15 +373,13 @@ protected static function getManager2Service($container) */ protected static function getManager3Service($container, $lazyLoad = true) { - $a = ($container->services['listener3'] ?? self::getListener3Service($container)); + $a = ($container->privates['connection3'] ?? self::getConnection3Service($container)); if (isset($container->services['manager3'])) { return $container->services['manager3']; } - $b = new \stdClass(); - $b->listener = [$a]; - return $container->services['manager3'] = new \stdClass($b); + return $container->services['manager3'] = new \stdClass($a); } /** @@ -481,6 +479,34 @@ protected static function getBar6Service($container) return $container->privates['bar6'] = new \stdClass($a); } + /** + * Gets the private 'connection3' shared service. + * + * @return \stdClass + */ + protected static function getConnection3Service($container) + { + $container->privates['connection3'] = $instance = new \stdClass(); + + $instance->listener = [($container->services['listener3'] ?? self::getListener3Service($container))]; + + return $instance; + } + + /** + * Gets the private 'connection4' shared service. + * + * @return \stdClass + */ + protected static function getConnection4Service($container) + { + $container->privates['connection4'] = $instance = new \stdClass(); + + $instance->listener = [($container->services['listener4'] ?? self::getListener4Service($container))]; + + return $instance; + } + /** * Gets the private 'doctrine.listener' shared service. * @@ -572,13 +598,13 @@ protected static function getMailerInline_TransportFactory_AmazonService($contai */ protected static function getManager4Service($container, $lazyLoad = true) { - $a = new \stdClass(); + $a = ($container->privates['connection4'] ?? self::getConnection4Service($container)); - $container->privates['manager4'] = $instance = new \stdClass($a); - - $a->listener = [($container->services['listener4'] ?? self::getListener4Service($container))]; + if (isset($container->privates['manager4'])) { + return $container->privates['manager4']; + } - return $instance; + return $container->privates['manager4'] = new \stdClass($a); } /** diff --git a/Tests/Fixtures/php/services_almost_circular_public.php b/Tests/Fixtures/php/services_almost_circular_public.php index 2250e8602..ae283e556 100644 --- a/Tests/Fixtures/php/services_almost_circular_public.php +++ b/Tests/Fixtures/php/services_almost_circular_public.php @@ -259,7 +259,7 @@ protected static function getDispatcher2Service($container, $lazyLoad = true) { $container->services['dispatcher2'] = $instance = new \stdClass(); - $instance->subscriber2 = new \stdClass(($container->services['manager2'] ?? self::getManager2Service($container))); + $instance->subscriber2 = ($container->privates['subscriber2'] ?? self::getSubscriber2Service($container)); return $instance; } @@ -820,4 +820,20 @@ protected static function getManager4Service($container, $lazyLoad = true) return $container->privates['manager4'] = new \stdClass($a); } + + /** + * Gets the private 'subscriber2' shared service. + * + * @return \stdClass + */ + protected static function getSubscriber2Service($container) + { + $a = ($container->services['manager2'] ?? self::getManager2Service($container)); + + if (isset($container->privates['subscriber2'])) { + return $container->privates['subscriber2']; + } + + return $container->privates['subscriber2'] = new \stdClass($a); + } } diff --git a/Tests/Fixtures/php/services_wither_lazy.php b/Tests/Fixtures/php/services_wither_lazy.php index b2940c885..58ad91e76 100644 --- a/Tests/Fixtures/php/services_wither_lazy.php +++ b/Tests/Fixtures/php/services_wither_lazy.php @@ -61,7 +61,7 @@ protected static function getWitherService($container, $lazyLoad = true) $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); - $a = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo(); + $a = ($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); $instance = $instance->withFoo1($a); $instance = $instance->withFoo2($a);