feature #30348 [DependencyInjection] Add ability to define an index for service in an injected service locator argument (XuruDragon, nicolas-grekas)
This PR was merged into the 4.3-dev branch. Discussion ---------- [DependencyInjection] Add ability to define an index for service in an injected service locator argument | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | in progress / symfony/symfony-docs#... It's more or less the same thing then the PR #30257 but for a service locator argument Add a simple way to specify an index based on a tag attribute to simplify retrieving a specific service when injecting a service locator as argument into services, but also a way to fallback to a static method on the service class. Yaml: ```yaml services: foo_service: class: Foo tags: - {name: foo_tag, key: foo_service} foo_service_tagged: class: Bar arguments: - !tagged_locator tag: 'foo_tag' index_by: 'key' default_index_method: 'static_method' ``` XML: ```xml <?xml version="1.0" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <service id="foo" class="Foo"> <tag name="foo_tag" key="foo_service" /> </service> <service id="foo_tagged_iterator" class="Bar" public="true"> <argument type="tagged_locator" tag="foo_tag" index-by="key" default-index-method="static_method" /> </service> </services> </container> ``` PHP: ```php // config/services.php use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; $container->register(Foo::class) ->addTag('foo_tag', ['key' => 'foo_service']); $container->register(App\Handler\HandlerCollection::class) // inject all services tagged with app.handler as first argument ->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('app.handler', 'key'))); ``` Usage: ```php // src/Handler/HandlerCollection.php namespace App\Handler; use Symfony\Component\DependencyInjection\ServiceLocator; class HandlerCollection { public function __construct(ServiceLocator $serviceLocator) { $foo = $serviceLocator->get('foo_service'): } } ``` Tasks * [x] Support PHP loader/dumper * [x] Support YAML loader/dumper * [x] Support XML loader/dumper (and update XSD too) * [x] Add tests * [x] Documentation Commits -------cb3c56bc0c
Support indexing tagged locators by FQCN as fallback250a2c8332
[DI] Allow tagged_locator tag to be used as an argument
This commit is contained in:
commit
040dc7bc09
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\DependencyInjection\Argument;
|
namespace Symfony\Component\DependencyInjection\Argument;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a closure acting as a service locator.
|
* Represents a closure acting as a service locator.
|
||||||
*
|
*
|
||||||
@ -19,4 +21,24 @@ namespace Symfony\Component\DependencyInjection\Argument;
|
|||||||
class ServiceLocatorArgument implements ArgumentInterface
|
class ServiceLocatorArgument implements ArgumentInterface
|
||||||
{
|
{
|
||||||
use ReferenceSetArgumentTrait;
|
use ReferenceSetArgumentTrait;
|
||||||
|
|
||||||
|
private $taggedIteratorArgument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Reference[]|TaggedIteratorArgument $values
|
||||||
|
*/
|
||||||
|
public function __construct($values = [])
|
||||||
|
{
|
||||||
|
if ($values instanceof TaggedIteratorArgument) {
|
||||||
|
$this->taggedIteratorArgument = $values;
|
||||||
|
$this->values = [];
|
||||||
|
} else {
|
||||||
|
$this->setValues($values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTaggedIteratorArgument(): ?TaggedIteratorArgument
|
||||||
|
{
|
||||||
|
return $this->taggedIteratorArgument;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,24 +21,22 @@ class TaggedIteratorArgument extends IteratorArgument
|
|||||||
private $tag;
|
private $tag;
|
||||||
private $indexAttribute;
|
private $indexAttribute;
|
||||||
private $defaultIndexMethod;
|
private $defaultIndexMethod;
|
||||||
|
private $useFqcnAsFallback = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TaggedIteratorArgument constructor.
|
|
||||||
*
|
|
||||||
* @param string $tag The name of the tag identifying the target services
|
* @param string $tag The name of the tag identifying the target services
|
||||||
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
|
* @param string|null $indexAttribute The name of the attribute that defines the key referencing each service in the tagged collection
|
||||||
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
|
* @param string|null $defaultIndexMethod The static method that should be called to get each service's key when their tag doesn't define the previous attribute
|
||||||
|
* @param bool $useFqcnAsFallback Whether the FQCN of the service should be used as index when neither the attribute nor the method are defined
|
||||||
*/
|
*/
|
||||||
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null)
|
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $useFqcnAsFallback = false)
|
||||||
{
|
{
|
||||||
parent::__construct([]);
|
parent::__construct([]);
|
||||||
|
|
||||||
$this->tag = $tag;
|
$this->tag = $tag;
|
||||||
|
$this->indexAttribute = $indexAttribute;
|
||||||
if ($indexAttribute) {
|
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Name');
|
||||||
$this->indexAttribute = $indexAttribute;
|
$this->useFqcnAsFallback = $useFqcnAsFallback;
|
||||||
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute))).'Name');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTag()
|
public function getTag()
|
||||||
@ -55,4 +53,9 @@ class TaggedIteratorArgument extends IteratorArgument
|
|||||||
{
|
{
|
||||||
return $this->defaultIndexMethod;
|
return $this->defaultIndexMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function useFqcnAsFallback(): bool
|
||||||
|
{
|
||||||
|
return $this->useFqcnAsFallback;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ CHANGELOG
|
|||||||
* made `ContainerParametersResource` final and not implement `Serializable` anymore
|
* made `ContainerParametersResource` final and not implement `Serializable` anymore
|
||||||
* added `ReverseContainer`: a container that turns services back to their ids
|
* added `ReverseContainer`: a container that turns services back to their ids
|
||||||
* added ability to define an index for a tagged collection
|
* added ability to define an index for a tagged collection
|
||||||
|
* added ability to define an index for services in an injected service locator argument
|
||||||
|
|
||||||
4.2.0
|
4.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Symfony\Component\DependencyInjection\TypedReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait that allows a generic method to find and sort service by priority option in the tag.
|
* Trait that allows a generic method to find and sort service by priority option in the tag.
|
||||||
@ -40,34 +41,48 @@ trait PriorityTaggedServiceTrait
|
|||||||
*/
|
*/
|
||||||
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
|
private function findAndSortTaggedServices($tagName, ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$indexAttribute = $defaultIndexMethod = null;
|
$indexAttribute = $defaultIndexMethod = $useFqcnAsFallback = null;
|
||||||
|
|
||||||
if ($tagName instanceof TaggedIteratorArgument) {
|
if ($tagName instanceof TaggedIteratorArgument) {
|
||||||
$indexAttribute = $tagName->getIndexAttribute();
|
$indexAttribute = $tagName->getIndexAttribute();
|
||||||
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
|
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
|
||||||
|
$useFqcnAsFallback = $tagName->useFqcnAsFallback();
|
||||||
$tagName = $tagName->getTag();
|
$tagName = $tagName->getTag();
|
||||||
}
|
}
|
||||||
|
|
||||||
$services = [];
|
$services = [];
|
||||||
|
|
||||||
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
||||||
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||||
|
|
||||||
if (null === $indexAttribute) {
|
if (null === $indexAttribute && !$useFqcnAsFallback) {
|
||||||
$services[$priority][] = new Reference($serviceId);
|
$services[$priority][] = new Reference($serviceId);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($attributes[0][$indexAttribute])) {
|
$class = $container->getDefinition($serviceId)->getClass();
|
||||||
$services[$priority][$attributes[0][$indexAttribute]] = new Reference($serviceId);
|
$class = $container->getParameterBag()->resolveValue($class) ?: null;
|
||||||
|
|
||||||
|
if (null !== $indexAttribute && isset($attributes[0][$indexAttribute])) {
|
||||||
|
$services[$priority][$attributes[0][$indexAttribute]] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $attributes[0][$indexAttribute]);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$r = $container->getReflectionClass($class = $container->getDefinition($serviceId)->getClass())) {
|
if (!$r = $container->getReflectionClass($class)) {
|
||||||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId));
|
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$class = $r->name;
|
||||||
|
|
||||||
if (!$r->hasMethod($defaultIndexMethod)) {
|
if (!$r->hasMethod($defaultIndexMethod)) {
|
||||||
|
if ($useFqcnAsFallback) {
|
||||||
|
$services[$priority][$class] = new TypedReference($serviceId, $class);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
|
throw new InvalidArgumentException(sprintf('Method "%s::%s()" not found: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, $tagName, $serviceId, $indexAttribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +100,7 @@ trait PriorityTaggedServiceTrait
|
|||||||
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($key), $tagName, $serviceId, $indexAttribute));
|
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return a string, got %s: tag "%s" on service "%s" is missing "%s" attribute.', $class, $defaultIndexMethod, \gettype($key), $tagName, $serviceId, $indexAttribute));
|
||||||
}
|
}
|
||||||
|
|
||||||
$services[$priority][$key] = new Reference($serviceId);
|
$services[$priority][$key] = new TypedReference($serviceId, $class, ContainerBuilder::EXCEPTION_ON_INVALID_REFERENCE, $key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($services) {
|
if ($services) {
|
||||||
|
@ -27,11 +27,18 @@ use Symfony\Component\DependencyInjection\ServiceLocator;
|
|||||||
*/
|
*/
|
||||||
final class ServiceLocatorTagPass extends AbstractRecursivePass
|
final class ServiceLocatorTagPass extends AbstractRecursivePass
|
||||||
{
|
{
|
||||||
|
use PriorityTaggedServiceTrait;
|
||||||
|
|
||||||
protected function processValue($value, $isRoot = false)
|
protected function processValue($value, $isRoot = false)
|
||||||
{
|
{
|
||||||
if ($value instanceof ServiceLocatorArgument) {
|
if ($value instanceof ServiceLocatorArgument) {
|
||||||
|
if ($value->getTaggedIteratorArgument()) {
|
||||||
|
$value->setValues($this->findAndSortTaggedServices($value->getTaggedIteratorArgument(), $this->container));
|
||||||
|
}
|
||||||
|
|
||||||
return self::register($this->container, $value->getValues());
|
return self::register($this->container, $value->getValues());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
|
if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) {
|
||||||
return parent::processValue($value, $isRoot);
|
return parent::processValue($value, $isRoot);
|
||||||
}
|
}
|
||||||
|
@ -280,19 +280,19 @@ class XmlDumper extends Dumper
|
|||||||
if ($value instanceof ServiceClosureArgument) {
|
if ($value instanceof ServiceClosureArgument) {
|
||||||
$value = $value->getValues()[0];
|
$value = $value->getValues()[0];
|
||||||
}
|
}
|
||||||
if (\is_array($value)) {
|
if (\is_array($tag = $value)) {
|
||||||
$element->setAttribute('type', 'collection');
|
$element->setAttribute('type', 'collection');
|
||||||
$this->convertParameters($value, $type, $element, 'key');
|
$this->convertParameters($value, $type, $element, 'key');
|
||||||
} elseif ($value instanceof TaggedIteratorArgument) {
|
} elseif ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
|
||||||
$element->setAttribute('type', 'tagged');
|
$element->setAttribute('type', $value instanceof TaggedIteratorArgument ? 'tagged' : 'tagged_locator');
|
||||||
$element->setAttribute('tag', $value->getTag());
|
$element->setAttribute('tag', $tag->getTag());
|
||||||
|
|
||||||
if (null !== $value->getIndexAttribute()) {
|
if (null !== $tag->getIndexAttribute()) {
|
||||||
$element->setAttribute('index-by', $value->getIndexAttribute());
|
$element->setAttribute('index-by', $tag->getIndexAttribute());
|
||||||
}
|
|
||||||
|
|
||||||
if (null !== $value->getDefaultIndexMethod()) {
|
if (null !== $tag->getDefaultIndexMethod()) {
|
||||||
$element->setAttribute('default-index-method', $value->getDefaultIndexMethod());
|
$element->setAttribute('default-index-method', $tag->getDefaultIndexMethod());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} elseif ($value instanceof IteratorArgument) {
|
} elseif ($value instanceof IteratorArgument) {
|
||||||
$element->setAttribute('type', 'iterator');
|
$element->setAttribute('type', 'iterator');
|
||||||
|
@ -232,22 +232,25 @@ class YamlDumper extends Dumper
|
|||||||
$value = $value->getValues()[0];
|
$value = $value->getValues()[0];
|
||||||
}
|
}
|
||||||
if ($value instanceof ArgumentInterface) {
|
if ($value instanceof ArgumentInterface) {
|
||||||
if ($value instanceof TaggedIteratorArgument) {
|
$tag = $value;
|
||||||
if (null !== $value->getIndexAttribute()) {
|
|
||||||
$taggedValueContent = [
|
if ($value instanceof TaggedIteratorArgument || ($value instanceof ServiceLocatorArgument && $tag = $value->getTaggedIteratorArgument())) {
|
||||||
'tag' => $value->getTag(),
|
if (null === $tag->getIndexAttribute()) {
|
||||||
'index_by' => $value->getIndexAttribute(),
|
$content = $tag->getTag();
|
||||||
|
} else {
|
||||||
|
$content = [
|
||||||
|
'tag' => $tag->getTag(),
|
||||||
|
'index_by' => $tag->getIndexAttribute(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (null !== $value->getDefaultIndexMethod()) {
|
if (null !== $tag->getDefaultIndexMethod()) {
|
||||||
$taggedValueContent['default_index_method'] = $value->getDefaultIndexMethod();
|
$content['default_index_method'] = $tag->getDefaultIndexMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TaggedValue('tagged', $taggedValueContent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TaggedValue('tagged', $value->getTag());
|
return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged' : 'tagged_locator', $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof IteratorArgument) {
|
if ($value instanceof IteratorArgument) {
|
||||||
$tag = 'iterator';
|
$tag = 'iterator';
|
||||||
} elseif ($value instanceof ServiceLocatorArgument) {
|
} elseif ($value instanceof ServiceLocatorArgument) {
|
||||||
|
@ -121,6 +121,14 @@ function tagged(string $tag, string $indexAttribute = null, string $defaultIndex
|
|||||||
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod);
|
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a service locator by tag name.
|
||||||
|
*/
|
||||||
|
function tagged_locator(string $tag, string $indexAttribute, string $defaultIndexMethod = null): ServiceLocatorArgument
|
||||||
|
{
|
||||||
|
return new ServiceLocatorArgument(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, true));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an expression.
|
* Creates an expression.
|
||||||
*/
|
*/
|
||||||
|
@ -534,11 +534,19 @@ class XmlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'tagged':
|
case 'tagged':
|
||||||
|
case 'tagged_locator':
|
||||||
|
$type = $arg->getAttribute('type');
|
||||||
|
$forLocator = 'tagged_locator' === $type;
|
||||||
|
|
||||||
if (!$arg->getAttribute('tag')) {
|
if (!$arg->getAttribute('tag')) {
|
||||||
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="tagged" has no or empty "tag" attribute in "%s".', $name, $file));
|
throw new InvalidArgumentException(sprintf('Tag "<%s>" with type="%s" has no or empty "tag" attribute in "%s".', $name, $type, $file));
|
||||||
}
|
}
|
||||||
|
|
||||||
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null);
|
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator);
|
||||||
|
|
||||||
|
if ($forLocator) {
|
||||||
|
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'binary':
|
case 'binary':
|
||||||
if (false === $value = base64_decode($arg->nodeValue)) {
|
if (false === $value = base64_decode($arg->nodeValue)) {
|
||||||
|
@ -702,27 +702,37 @@ class YamlFileLoader extends FileLoader
|
|||||||
if (!\is_array($argument)) {
|
if (!\is_array($argument)) {
|
||||||
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file));
|
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps in "%s".', $file));
|
||||||
}
|
}
|
||||||
|
|
||||||
$argument = $this->resolveServices($argument, $file, $isParameter);
|
$argument = $this->resolveServices($argument, $file, $isParameter);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new ServiceLocatorArgument($argument);
|
return new ServiceLocatorArgument($argument);
|
||||||
} catch (InvalidArgumentException $e) {
|
} catch (InvalidArgumentException $e) {
|
||||||
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file));
|
throw new InvalidArgumentException(sprintf('"!service_locator" tag only accepts maps of "@service" references in "%s".', $file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ('tagged' === $value->getTag()) {
|
if (\in_array($value->getTag(), ['tagged', 'tagged_locator'], true)) {
|
||||||
|
$forLocator = 'tagged_locator' === $value->getTag();
|
||||||
|
|
||||||
if (\is_string($argument) && $argument) {
|
if (\is_string($argument) && $argument) {
|
||||||
return new TaggedIteratorArgument($argument);
|
return new TaggedIteratorArgument($argument, null, null, $forLocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
|
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
|
||||||
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method'])) {
|
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method'])) {
|
||||||
throw new InvalidArgumentException(sprintf('"!tagged" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', implode('"", "', $diff)));
|
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', $value->getTag(), implode('"", "', $diff)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null);
|
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'], $argument['default_index_method'] ?? null, $forLocator);
|
||||||
|
|
||||||
|
if ($forLocator) {
|
||||||
|
$argument = new ServiceLocatorArgument($argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $argument;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidArgumentException(sprintf('"!tagged" tags only accept a non empty string or an array with a key "tag" in "%s".', $file));
|
throw new InvalidArgumentException(sprintf('"!%s" tags only accept a non empty string or an array with a key "tag" in "%s".', $value->getTag(), $file));
|
||||||
}
|
}
|
||||||
if ('service' === $value->getTag()) {
|
if ('service' === $value->getTag()) {
|
||||||
if ($isParameter) {
|
if ($isParameter) {
|
||||||
|
@ -264,6 +264,7 @@
|
|||||||
<xsd:enumeration value="iterator" />
|
<xsd:enumeration value="iterator" />
|
||||||
<xsd:enumeration value="service_locator" />
|
<xsd:enumeration value="service_locator" />
|
||||||
<xsd:enumeration value="tagged" />
|
<xsd:enumeration value="tagged" />
|
||||||
|
<xsd:enumeration value="tagged_locator" />
|
||||||
</xsd:restriction>
|
</xsd:restriction>
|
||||||
</xsd:simpleType>
|
</xsd:simpleType>
|
||||||
|
|
||||||
|
@ -14,10 +14,12 @@ namespace Symfony\Component\DependencyInjection\Tests\Compiler;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\DependencyInjection\Alias;
|
use Symfony\Component\DependencyInjection\Alias;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||||
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
|
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
|
||||||
@ -242,15 +244,15 @@ class IntegrationTest extends TestCase
|
|||||||
public function testTaggedServiceWithIndexAttribute()
|
public function testTaggedServiceWithIndexAttribute()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register(BarTagClass::class, BarTagClass::class)
|
$container->register(BarTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar', ['foo' => 'bar'])
|
->addTag('foo_bar', ['foo' => 'bar'])
|
||||||
;
|
;
|
||||||
$container->register(FooTagClass::class, FooTagClass::class)
|
$container->register(FooTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar')
|
->addTag('foo_bar')
|
||||||
;
|
;
|
||||||
$container->register(FooBarTaggedClass::class, FooBarTaggedClass::class)
|
$container->register(FooBarTaggedClass::class)
|
||||||
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo'))
|
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo'))
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
;
|
;
|
||||||
@ -266,15 +268,15 @@ class IntegrationTest extends TestCase
|
|||||||
public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
|
public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
|
||||||
{
|
{
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register(BarTagClass::class, BarTagClass::class)
|
$container->register(BarTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar')
|
->addTag('foo_bar')
|
||||||
;
|
;
|
||||||
$container->register(FooTagClass::class, FooTagClass::class)
|
$container->register(FooTagClass::class)
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addTag('foo_bar', ['foo' => 'foo'])
|
->addTag('foo_bar', ['foo' => 'foo'])
|
||||||
;
|
;
|
||||||
$container->register(FooBarTaggedClass::class, FooBarTaggedClass::class)
|
$container->register(FooBarTaggedClass::class)
|
||||||
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar'))
|
->addArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar'))
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
;
|
;
|
||||||
@ -286,6 +288,94 @@ class IntegrationTest extends TestCase
|
|||||||
$param = iterator_to_array($s->getParam()->getIterator());
|
$param = iterator_to_array($s->getParam()->getIterator());
|
||||||
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
|
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testTaggedServiceLocatorWithIndexAttribute()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register(BarTagClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('foo_bar', ['foo' => 'bar'])
|
||||||
|
;
|
||||||
|
$container->register(FooTagClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('foo_bar')
|
||||||
|
;
|
||||||
|
$container->register(FooBarTaggedClass::class)
|
||||||
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo')))
|
||||||
|
->setPublic(true)
|
||||||
|
;
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$s = $container->get(FooBarTaggedClass::class);
|
||||||
|
|
||||||
|
/** @var ServiceLocator $serviceLocator */
|
||||||
|
$serviceLocator = $s->getParam();
|
||||||
|
$this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator)));
|
||||||
|
|
||||||
|
$same = [
|
||||||
|
'bar' => $serviceLocator->get('bar'),
|
||||||
|
'foo_tag_class' => $serviceLocator->get('foo_tag_class'),
|
||||||
|
];
|
||||||
|
$this->assertSame(['bar' => $container->get(BarTagClass::class), 'foo_tag_class' => $container->get(FooTagClass::class)], $same);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTaggedServiceLocatorWithIndexAttributeAndDefaultMethod()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register(BarTagClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('foo_bar')
|
||||||
|
;
|
||||||
|
$container->register(FooTagClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('foo_bar', ['foo' => 'foo'])
|
||||||
|
;
|
||||||
|
$container->register(FooBarTaggedClass::class)
|
||||||
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', 'foo', 'getFooBar')))
|
||||||
|
->setPublic(true)
|
||||||
|
;
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$s = $container->get(FooBarTaggedClass::class);
|
||||||
|
|
||||||
|
/** @var ServiceLocator $serviceLocator */
|
||||||
|
$serviceLocator = $s->getParam();
|
||||||
|
$this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator)));
|
||||||
|
|
||||||
|
$same = [
|
||||||
|
'bar_tab_class_with_defaultmethod' => $serviceLocator->get('bar_tab_class_with_defaultmethod'),
|
||||||
|
'foo' => $serviceLocator->get('foo'),
|
||||||
|
];
|
||||||
|
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $same);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTaggedServiceLocatorWithFqcnFallback()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register(BarTagClass::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->addTag('foo_bar')
|
||||||
|
;
|
||||||
|
$container->register(FooBarTaggedClass::class)
|
||||||
|
->addArgument(new ServiceLocatorArgument(new TaggedIteratorArgument('foo_bar', null, null, true)))
|
||||||
|
->setPublic(true)
|
||||||
|
;
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$s = $container->get(FooBarTaggedClass::class);
|
||||||
|
|
||||||
|
/** @var ServiceLocator $serviceLocator */
|
||||||
|
$serviceLocator = $s->getParam();
|
||||||
|
$this->assertTrue($s->getParam() instanceof ServiceLocator, sprintf('Wrong instance, should be an instance of ServiceLocator, %s given', \is_object($serviceLocator) ? \get_class($serviceLocator) : \gettype($serviceLocator)));
|
||||||
|
|
||||||
|
$same = [
|
||||||
|
BarTagClass::class => $serviceLocator->get(BarTagClass::class),
|
||||||
|
];
|
||||||
|
$this->assertSame([BarTagClass::class => $container->get(BarTagClass::class)], $same);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServiceSubscriberStub implements ServiceSubscriberInterface
|
class ServiceSubscriberStub implements ServiceSubscriberInterface
|
||||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
@ -201,13 +202,18 @@ class XmlDumperTest extends TestCase
|
|||||||
$this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_dump_load.xml', $dumper->dump());
|
$this->assertStringEqualsFile(self::$fixturesPath.'/xml/services_dump_load.xml', $dumper->dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTaggedArgument()
|
public function testTaggedArguments()
|
||||||
{
|
{
|
||||||
|
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar');
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register('foo', 'Foo')->addTag('foo_tag');
|
$container->register('foo', 'Foo')->addTag('foo_tag');
|
||||||
$container->register('foo_tagged_iterator', 'Bar')
|
$container->register('foo_tagged_iterator', 'Bar')
|
||||||
->setPublic(true)
|
->setPublic(true)
|
||||||
->addArgument(new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar'))
|
->addArgument($taggedIterator)
|
||||||
|
;
|
||||||
|
$container->register('foo_tagged_locator', 'Bar')
|
||||||
|
->setPublic(true)
|
||||||
|
->addArgument(new ServiceLocatorArgument($taggedIterator))
|
||||||
;
|
;
|
||||||
|
|
||||||
$dumper = new XmlDumper($container);
|
$dumper = new XmlDumper($container);
|
||||||
|
@ -13,6 +13,7 @@ namespace Symfony\Component\DependencyInjection\Tests\Dumper;
|
|||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
@ -96,11 +97,13 @@ class YamlDumperTest extends TestCase
|
|||||||
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_inline.yml', $dumper->dump());
|
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_inline.yml', $dumper->dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTaggedArgument()
|
public function testTaggedArguments()
|
||||||
{
|
{
|
||||||
|
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar');
|
||||||
$container = new ContainerBuilder();
|
$container = new ContainerBuilder();
|
||||||
$container->register('foo_service', 'Foo')->addTag('foo');
|
$container->register('foo_service', 'Foo')->addTag('foo');
|
||||||
$container->register('foo_service_tagged', 'Bar')->addArgument(new TaggedIteratorArgument('foo', 'barfoo', 'foobar'));
|
$container->register('foo_service_tagged_iterator', 'Bar')->addArgument($taggedIterator);
|
||||||
|
$container->register('foo_service_tagged_locator', 'Bar')->addArgument(new ServiceLocatorArgument($taggedIterator));
|
||||||
|
|
||||||
$dumper = new YamlDumper($container);
|
$dumper = new YamlDumper($container);
|
||||||
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_tagged_argument.yml', $dumper->dump());
|
$this->assertStringEqualsFile(self::$fixturesPath.'/yaml/services_with_tagged_argument.yml', $dumper->dump());
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
<service id="foo_tagged_iterator" class="Bar" public="true">
|
<service id="foo_tagged_iterator" class="Bar" public="true">
|
||||||
<argument type="tagged" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
|
<argument type="tagged" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
|
||||||
</service>
|
</service>
|
||||||
|
<service id="foo_tagged_locator" class="Bar" public="true">
|
||||||
|
<argument type="tagged_locator" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
|
||||||
|
</service>
|
||||||
<service id="Psr\Container\ContainerInterface" alias="service_container" public="false"/>
|
<service id="Psr\Container\ContainerInterface" alias="service_container" public="false"/>
|
||||||
<service id="Symfony\Component\DependencyInjection\ContainerInterface" alias="service_container" public="false"/>
|
<service id="Symfony\Component\DependencyInjection\ContainerInterface" alias="service_container" public="false"/>
|
||||||
</services>
|
</services>
|
||||||
|
@ -8,9 +8,12 @@ services:
|
|||||||
class: Foo
|
class: Foo
|
||||||
tags:
|
tags:
|
||||||
- { name: foo }
|
- { name: foo }
|
||||||
foo_service_tagged:
|
foo_service_tagged_iterator:
|
||||||
class: Bar
|
class: Bar
|
||||||
arguments: [!tagged { tag: foo, index_by: barfoo, default_index_method: foobar }]
|
arguments: [!tagged { tag: foo, index_by: barfoo, default_index_method: foobar }]
|
||||||
|
foo_service_tagged_locator:
|
||||||
|
class: Bar
|
||||||
|
arguments: [!tagged_locator { tag: foo, index_by: barfoo, default_index_method: foobar }]
|
||||||
Psr\Container\ContainerInterface:
|
Psr\Container\ContainerInterface:
|
||||||
alias: service_container
|
alias: service_container
|
||||||
public: false
|
public: false
|
||||||
|
@ -18,6 +18,7 @@ use Symfony\Component\Config\Resource\FileResource;
|
|||||||
use Symfony\Component\Config\Resource\GlobResource;
|
use Symfony\Component\Config\Resource\GlobResource;
|
||||||
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
|
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
|
||||||
@ -324,7 +325,13 @@ class XmlFileLoaderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertCount(1, $container->getDefinition('foo')->getTag('foo_tag'));
|
$this->assertCount(1, $container->getDefinition('foo')->getTag('foo_tag'));
|
||||||
$this->assertCount(1, $container->getDefinition('foo_tagged_iterator')->getArguments());
|
$this->assertCount(1, $container->getDefinition('foo_tagged_iterator')->getArguments());
|
||||||
$this->assertEquals(new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar'), $container->getDefinition('foo_tagged_iterator')->getArgument(0));
|
$this->assertCount(1, $container->getDefinition('foo_tagged_locator')->getArguments());
|
||||||
|
|
||||||
|
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar');
|
||||||
|
$this->assertEquals($taggedIterator, $container->getDefinition('foo_tagged_iterator')->getArgument(0));
|
||||||
|
|
||||||
|
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar', true);
|
||||||
|
$this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_tagged_locator')->getArgument(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,6 +18,7 @@ use Symfony\Component\Config\Resource\FileResource;
|
|||||||
use Symfony\Component\Config\Resource\GlobResource;
|
use Symfony\Component\Config\Resource\GlobResource;
|
||||||
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
|
||||||
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
|
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
|
||||||
@ -287,8 +288,14 @@ class YamlFileLoaderTest extends TestCase
|
|||||||
$loader->load('services_with_tagged_argument.yml');
|
$loader->load('services_with_tagged_argument.yml');
|
||||||
|
|
||||||
$this->assertCount(1, $container->getDefinition('foo_service')->getTag('foo'));
|
$this->assertCount(1, $container->getDefinition('foo_service')->getTag('foo'));
|
||||||
$this->assertCount(1, $container->getDefinition('foo_service_tagged')->getArguments());
|
$this->assertCount(1, $container->getDefinition('foo_service_tagged_iterator')->getArguments());
|
||||||
$this->assertEquals(new TaggedIteratorArgument('foo', 'barfoo', 'foobar'), $container->getDefinition('foo_service_tagged')->getArgument(0));
|
$this->assertCount(1, $container->getDefinition('foo_service_tagged_locator')->getArguments());
|
||||||
|
|
||||||
|
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar');
|
||||||
|
$this->assertEquals($taggedIterator, $container->getDefinition('foo_service_tagged_iterator')->getArgument(0));
|
||||||
|
|
||||||
|
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar', true);
|
||||||
|
$this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_service_tagged_locator')->getArgument(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNameOnlyTagsAreAllowedAsString()
|
public function testNameOnlyTagsAreAllowedAsString()
|
||||||
|
Reference in New Issue
Block a user