Skip to content

[DependencyInjection] Service subscriber without autowire #60272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Make definition optional
  • Loading branch information
Neirda24 committed Apr 25, 2025
commit 90935d4926ab4bc565b754ae8ce87fb31b8426cb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
return parent::processValue($value, $isRoot);
}

$locatorRef = self::registerLocator($value, $this->container, $this->currentId);
$locatorRef = self::registerLocator($this->container, $this->currentId, $value);

$value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]);

Expand All @@ -54,10 +54,14 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
/**
* @param class-string<ServiceSubscriberInterface>|Definition $value
*/
public static function registerLocator(string|Definition $value, ContainerBuilder $container, string $currentId): Reference
public static function registerLocator(ContainerBuilder $container, string $currentId, string|Definition|null $value = null): Reference
{
$serviceMap = [];

if (null === $value) {
$value = $container->getDefinition($currentId);
}

if ($value instanceof Definition) {
$autowire = $value->isAutowired();

Expand Down Expand Up @@ -148,7 +152,7 @@ public static function registerLocator(string|Definition $value, ContainerBuilde

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, $currentId));
throw new InvalidArgumentException(\sprintf('Service "%s" not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $currentId));
}

return ServiceLocatorTagPass::register($container, $subscriberMap, $currentId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Compiler;

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;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

/**
* Compiler pass to register tagged services that require a service locator.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterServiceSubscribersPass extends AbstractRecursivePass
{
protected bool $skipScalars = true;

protected function processValue(mixed $value, bool $isRoot = false): mixed
{
if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('container.service_subscriber')) {
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<ServiceSubscriberInterface>|Definition $value
*/
public static function registerLocator(ContainerBuilder $container, string $currentId, string|Definition|null $value = null): Reference
{
$serviceMap = [];

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']);
}

$class = $value->getClass();
} else {
$class = $value;
}

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".', $currentId, ServiceSubscriberInterface::class));
}
$class = $r->name;
$subscriberMap = [];

foreach ($class::getSubscribedServices() as $key => $type) {
$attributes = [];

if (!isset($serviceMap[$key]) && $type instanceof Autowire) {
$subscriberMap[$key] = $type;
continue;
}

if ($type instanceof SubscribedService) {
$key = $type->key ?? $key;
$attributes = $type->attributes;
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(\sprintf('When "%s::getSubscribedServices()" returns "%s", a type must be set.', $class, SubscribedService::class)));
}

if (!\is_string($type) || !preg_match('/(?(DEFINE)(?<cn>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+))(?(DEFINE)(?<fqcn>(?&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, $currentId, $key, \is_string($type) ? $type : get_debug_type($type)));
}
$optionalBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE;
if ('?' === $type[0]) {
$type = substr($type, 1);
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
}
if (\is_int($name = $key)) {
$key = $type;
$name = null;
}
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()".', $currentId, $key, $class));
}
$serviceMap[$key] = new Reference($type);
}

if ($name) {
if (false !== $i = strpos($name, '::get')) {
$name = lcfirst(substr($name, 5 + $i));
} elseif (str_contains($name, '::')) {
$name = null;
}
}

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 = $container->has($type.' $'.$camelCaseName) ? $camelCaseName : $name;
}

$subscriberMap[$key] = new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior, $name, $attributes);
unset($serviceMap[$key]);
}

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, $currentId));
}

return ServiceLocatorTagPass::register($container, $subscriberMap, $currentId);
}
}
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy