diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index 87470c39894e4..add19cf1a0e7f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -14,6 +14,7 @@ use Psr\Container\ContainerInterface as PsrContainerInterface; use Symfony\Component\DependencyInjection\Argument\BoundArgument; use Symfony\Component\DependencyInjection\Attribute\Autowire; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -38,36 +39,63 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed return parent::processValue($value, $isRoot); } + $locatorRef = self::registerLocator($this->container, $this->currentId, $value); + + $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]); + + $value->setBindings([ + PsrContainerInterface::class => new BoundArgument($locatorRef, false), + ServiceProviderInterface::class => new BoundArgument($locatorRef, false), + ] + $value->getBindings()); + + return parent::processValue($value); + } + + /** + * @param class-string|Definition $value + */ + public static function registerLocator(ContainerBuilder $container, string $currentId, string|Definition|null $value = null): Reference + { $serviceMap = []; - $autowire = $value->isAutowired(); - foreach ($value->getTag('container.service_subscriber') as $attributes) { - if (!$attributes) { - $autowire = true; - continue; - } - ksort($attributes); - if ([] !== array_diff(array_keys($attributes), ['id', 'key'])) { - throw new InvalidArgumentException(\sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', implode('", "', array_keys($attributes)), $this->currentId)); - } - if (!\array_key_exists('id', $attributes)) { - throw new InvalidArgumentException(\sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $this->currentId)); - } - if (!\array_key_exists('key', $attributes)) { - $attributes['key'] = $attributes['id']; - } - if (isset($serviceMap[$attributes['key']])) { - continue; + if (null === $value) { + $value = $container->getDefinition($currentId); + } + + if ($value instanceof Definition) { + $autowire = $value->isAutowired(); + + foreach ($value->getTag('container.service_subscriber') as $attributes) { + if (!$attributes) { + $autowire = true; + continue; + } + ksort($attributes); + if ([] !== array_diff(array_keys($attributes), ['id', 'key'])) { + throw new InvalidArgumentException(\sprintf('The "container.service_subscriber" tag accepts only the "key" and "id" attributes, "%s" given for service "%s".', implode('", "', array_keys($attributes)), $currentId)); + } + if (!\array_key_exists('id', $attributes)) { + throw new InvalidArgumentException(\sprintf('Missing "id" attribute on "container.service_subscriber" tag with key="%s" for service "%s".', $attributes['key'], $currentId)); + } + if (!\array_key_exists('key', $attributes)) { + $attributes['key'] = $attributes['id']; + } + if (isset($serviceMap[$attributes['key']])) { + continue; + } + $serviceMap[$attributes['key']] = new Reference($attributes['id']); } - $serviceMap[$attributes['key']] = new Reference($attributes['id']); + + $class = $value->getClass(); + } else { + $class = $value; } - $class = $value->getClass(); - if (!$r = $this->container->getReflectionClass($class)) { - throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId)); + if (!$r = $container->getReflectionClass($class)) { + throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $currentId)); } if (!$r->isSubclassOf(ServiceSubscriberInterface::class)) { - throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class)); + throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $currentId, ServiceSubscriberInterface::class)); } $class = $r->name; $subscriberMap = []; @@ -87,7 +115,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed } if (!\is_string($type) || !preg_match('/(?(DEFINE)(?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?(?&cn)(?:\\\\(?&cn))*+))^\??(?&fqcn)(?:(?:\|(?&fqcn))*+|(?:&(?&fqcn))*+)$/', $type)) { - throw new InvalidArgumentException(\sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, \is_string($type) ? $type : get_debug_type($type))); + throw new InvalidArgumentException(\sprintf('"%s::getSubscribedServices()" must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $currentId, $key, \is_string($type) ? $type : get_debug_type($type))); } $optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; if ('?' === $type[0]) { @@ -100,7 +128,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed } if (!isset($serviceMap[$key])) { if (!$autowire) { - throw new InvalidArgumentException(\sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $this->currentId, $key, $class)); + throw new InvalidArgumentException(\sprintf('Service "%s" misses a "container.service_subscriber" tag with "key"/"id" attributes corresponding to entry "%s" as returned by "%s::getSubscribedServices()".', $currentId, $key, $class)); } $serviceMap[$key] = new Reference($type); } @@ -113,9 +141,9 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed } } - if (null !== $name && !$this->container->has($name) && !$this->container->has($type.' $'.$name)) { + if (null !== $name && !$container->has($name) && !$container->has($type.' $'.$name)) { $camelCaseName = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name)))); - $name = $this->container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name; + $name = $container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name; } $subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes); @@ -124,18 +152,9 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($serviceMap = array_keys($serviceMap)) { $message = \sprintf(1 < \count($serviceMap) ? 'keys "%s" do' : 'key "%s" does', str_replace('%', '%%', implode('", "', $serviceMap))); - throw new InvalidArgumentException(\sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId)); + throw new InvalidArgumentException(\sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $currentId)); } - $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId); - - $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]); - - $value->setBindings([ - PsrContainerInterface::class => new BoundArgument($locatorRef, false), - ServiceProviderInterface::class => new BoundArgument($locatorRef, false), - ] + $value->getBindings()); - - return parent::processValue($value); + return ServiceLocatorTagPass::register($container, $subscriberMap, $currentId); } }