feature #33628 [DependencyInjection] added Ability to define a priority method for tagged service (lyrixx)
This PR was merged into the 4.4 branch.
Discussion
----------
[DependencyInjection] added Ability to define a priority method for tagged service
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | Fix #32976
| License | MIT
| Doc PR |
Commits
-------
c1917c2999
[DependencyInjection] added Ability to define a priority method for tagged service
This commit is contained in:
commit
67fe198a8e
@ -24,12 +24,13 @@ class TaggedIteratorArgument extends IteratorArgument
|
||||
private $needsIndexes = false;
|
||||
|
||||
/**
|
||||
* @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 $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 $needsIndexes Whether indexes are required and should be generated when computing the map
|
||||
* @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 $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 $needsIndexes Whether indexes are required and should be generated when computing the map
|
||||
* @param string|null $defaultPriorityMethod The static method that should be called to get each service's priority when their tag doesn't define the "priority" attribute
|
||||
*/
|
||||
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false)
|
||||
public function __construct(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, bool $needsIndexes = false, string $defaultPriorityMethod = null)
|
||||
{
|
||||
parent::__construct([]);
|
||||
|
||||
@ -41,6 +42,7 @@ class TaggedIteratorArgument extends IteratorArgument
|
||||
$this->indexAttribute = $indexAttribute;
|
||||
$this->defaultIndexMethod = $defaultIndexMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Name');
|
||||
$this->needsIndexes = $needsIndexes;
|
||||
$this->defaultPriorityMethod = $defaultPriorityMethod ?: ('getDefault'.str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $indexAttribute ?? ''))).'Priority');
|
||||
}
|
||||
|
||||
public function getTag()
|
||||
@ -62,4 +64,9 @@ class TaggedIteratorArgument extends IteratorArgument
|
||||
{
|
||||
return $this->needsIndexes;
|
||||
}
|
||||
|
||||
public function getDefaultPriorityMethod(): ?string
|
||||
{
|
||||
return $this->defaultPriorityMethod;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ CHANGELOG
|
||||
* deprecated passing an instance of `Symfony\Component\DependencyInjection\Parameter` as class name to `Symfony\Component\DependencyInjection\Definition`
|
||||
* added support for binding iterable and tagged services
|
||||
* made singly-implemented interfaces detection be scoped by file
|
||||
* added ability to define a static priority method for tagged service
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
@ -40,19 +40,43 @@ trait PriorityTaggedServiceTrait
|
||||
*/
|
||||
private function findAndSortTaggedServices($tagName, ContainerBuilder $container): array
|
||||
{
|
||||
$indexAttribute = $defaultIndexMethod = $needsIndexes = null;
|
||||
$indexAttribute = $defaultIndexMethod = $needsIndexes = $defaultPriorityMethod = null;
|
||||
|
||||
if ($tagName instanceof TaggedIteratorArgument) {
|
||||
$indexAttribute = $tagName->getIndexAttribute();
|
||||
$defaultIndexMethod = $tagName->getDefaultIndexMethod();
|
||||
$needsIndexes = $tagName->needsIndexes();
|
||||
$defaultPriorityMethod = $tagName->getDefaultPriorityMethod();
|
||||
$tagName = $tagName->getTag();
|
||||
}
|
||||
|
||||
$services = [];
|
||||
|
||||
foreach ($container->findTaggedServiceIds($tagName, true) as $serviceId => $attributes) {
|
||||
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||
$class = $r = null;
|
||||
$priority = 0;
|
||||
if (isset($attributes[0]['priority'])) {
|
||||
$priority = $attributes[0]['priority'];
|
||||
} elseif ($defaultPriorityMethod) {
|
||||
$class = $container->getDefinition($serviceId)->getClass();
|
||||
$class = $container->getParameterBag()->resolveValue($class) ?: null;
|
||||
|
||||
if (($r = $container->getReflectionClass($class)) && $r->hasMethod($defaultPriorityMethod)) {
|
||||
if (!($rm = $r->getMethod($defaultPriorityMethod))->isStatic()) {
|
||||
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be static: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
|
||||
}
|
||||
|
||||
if (!$rm->isPublic()) {
|
||||
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should be public: tag "%s" on service "%s".', $class, $defaultPriorityMethod, $tagName, $serviceId));
|
||||
}
|
||||
|
||||
$priority = $rm->invoke(null);
|
||||
|
||||
if (!\is_int($priority)) {
|
||||
throw new InvalidArgumentException(sprintf('Method "%s::%s()" should return an integer, got %s: tag "%s" on service "%s".', $class, $defaultPriorityMethod, \gettype($priority), $tagName, $serviceId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $indexAttribute && !$needsIndexes) {
|
||||
$services[$priority][] = new Reference($serviceId);
|
||||
@ -60,8 +84,10 @@ trait PriorityTaggedServiceTrait
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = $container->getDefinition($serviceId)->getClass();
|
||||
$class = $container->getParameterBag()->resolveValue($class) ?: null;
|
||||
if (!$class) {
|
||||
$class = $container->getDefinition($serviceId)->getClass();
|
||||
$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]);
|
||||
@ -69,7 +95,7 @@ trait PriorityTaggedServiceTrait
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$r = $container->getReflectionClass($class)) {
|
||||
if (!$r && !$r = $container->getReflectionClass($class)) {
|
||||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $serviceId));
|
||||
}
|
||||
|
||||
|
@ -274,6 +274,9 @@ class XmlDumper extends Dumper
|
||||
if (null !== $tag->getDefaultIndexMethod()) {
|
||||
$element->setAttribute('default-index-method', $tag->getDefaultIndexMethod());
|
||||
}
|
||||
if (null !== $tag->getDefaultPriorityMethod()) {
|
||||
$element->setAttribute('default-priority-method', $tag->getDefaultPriorityMethod());
|
||||
}
|
||||
}
|
||||
} elseif ($value instanceof IteratorArgument) {
|
||||
$element->setAttribute('type', 'iterator');
|
||||
|
@ -244,6 +244,9 @@ class YamlDumper extends Dumper
|
||||
if (null !== $tag->getDefaultIndexMethod()) {
|
||||
$content['default_index_method'] = $tag->getDefaultIndexMethod();
|
||||
}
|
||||
if (null !== $tag->getDefaultPriorityMethod()) {
|
||||
$content['default_priority_method'] = $tag->getDefaultPriorityMethod();
|
||||
}
|
||||
}
|
||||
|
||||
return new TaggedValue($value instanceof TaggedIteratorArgument ? 'tagged_iterator' : 'tagged_locator', $content);
|
||||
|
@ -128,9 +128,9 @@ function tagged(string $tag, string $indexAttribute = null, string $defaultIndex
|
||||
/**
|
||||
* Creates a lazy iterator by tag name.
|
||||
*/
|
||||
function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null): TaggedIteratorArgument
|
||||
function tagged_iterator(string $tag, string $indexAttribute = null, string $defaultIndexMethod = null, string $defaultPriorityMethod = null): TaggedIteratorArgument
|
||||
{
|
||||
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod);
|
||||
return new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -513,7 +513,7 @@ class XmlFileLoader extends FileLoader
|
||||
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, $forLocator);
|
||||
$arguments[$key] = new TaggedIteratorArgument($arg->getAttribute('tag'), $arg->getAttribute('index-by') ?: null, $arg->getAttribute('default-index-method') ?: null, $forLocator, $arg->getAttribute('default-priority-method') ?: null);
|
||||
|
||||
if ($forLocator) {
|
||||
$arguments[$key] = new ServiceLocatorArgument($arguments[$key]);
|
||||
|
@ -721,11 +721,11 @@ class YamlFileLoader extends FileLoader
|
||||
$forLocator = 'tagged_locator' === $value->getTag();
|
||||
|
||||
if (\is_array($argument) && isset($argument['tag']) && $argument['tag']) {
|
||||
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method'])) {
|
||||
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by" and "default_index_method".', $value->getTag(), implode('"", "', $diff)));
|
||||
if ($diff = array_diff(array_keys($argument), ['tag', 'index_by', 'default_index_method', 'default_priority_method'])) {
|
||||
throw new InvalidArgumentException(sprintf('"!%s" tag contains unsupported key "%s"; supported ones are "tag", "index_by", "default_index_method", and "default_priority_method".', $value->getTag(), implode('"", "', $diff)));
|
||||
}
|
||||
|
||||
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator);
|
||||
$argument = new TaggedIteratorArgument($argument['tag'], $argument['index_by'] ?? null, $argument['default_index_method'] ?? null, $forLocator, $argument['default_priority_method'] ?? null);
|
||||
} elseif (\is_string($argument) && $argument) {
|
||||
$argument = new TaggedIteratorArgument($argument, null, null, $forLocator);
|
||||
} else {
|
||||
|
@ -236,6 +236,7 @@
|
||||
<xsd:attribute name="tag" type="xsd:string" />
|
||||
<xsd:attribute name="index-by" type="xsd:string" />
|
||||
<xsd:attribute name="default-index-method" type="xsd:string" />
|
||||
<xsd:attribute name="default-priority-method" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="call">
|
||||
|
@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
|
||||
|
||||
/**
|
||||
@ -289,6 +290,30 @@ class IntegrationTest extends TestCase
|
||||
$this->assertSame(['bar_tab_class_with_defaultmethod' => $container->get(BarTagClass::class), 'foo' => $container->get(FooTagClass::class)], $param);
|
||||
}
|
||||
|
||||
public function testTaggedServiceWithDefaultPriorityMethod()
|
||||
{
|
||||
$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(FooBarTaggedForDefaultPriorityClass::class)
|
||||
->addArgument(new TaggedIteratorArgument('foo_bar', null, null, false, 'getPriority'))
|
||||
->setPublic(true)
|
||||
;
|
||||
|
||||
$container->compile();
|
||||
|
||||
$s = $container->get(FooBarTaggedForDefaultPriorityClass::class);
|
||||
|
||||
$param = iterator_to_array($s->getParam()->getIterator());
|
||||
$this->assertSame([$container->get(FooTagClass::class), $container->get(BarTagClass::class)], $param);
|
||||
}
|
||||
|
||||
public function testTaggedServiceLocatorWithIndexAttribute()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -204,7 +204,7 @@ class XmlDumperTest extends TestCase
|
||||
|
||||
public function testTaggedArguments()
|
||||
{
|
||||
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar');
|
||||
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar', false, 'getPriority');
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo', 'Foo')->addTag('foo_tag');
|
||||
$container->register('foo_tagged_iterator', 'Bar')
|
||||
|
@ -99,7 +99,7 @@ class YamlDumperTest extends TestCase
|
||||
|
||||
public function testTaggedArguments()
|
||||
{
|
||||
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar');
|
||||
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar', false, 'getPriority');
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('foo_service', 'Foo')->addTag('foo');
|
||||
$container->register('foo_service_tagged_iterator', 'Bar')->addArgument($taggedIterator);
|
||||
|
@ -13,4 +13,9 @@ class BarTagClass
|
||||
{
|
||||
return 'bar_tab_class_with_defaultmethod';
|
||||
}
|
||||
|
||||
public static function getPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
|
||||
|
||||
class FooBarTaggedForDefaultPriorityClass
|
||||
{
|
||||
private $param;
|
||||
|
||||
public function __construct($param = [])
|
||||
{
|
||||
$this->param = $param;
|
||||
}
|
||||
|
||||
public function getParam()
|
||||
{
|
||||
return $this->param;
|
||||
}
|
||||
}
|
@ -8,4 +8,11 @@ class FooTagClass
|
||||
{
|
||||
return 'foo_tag_class';
|
||||
}
|
||||
|
||||
public static function getPriority(): int
|
||||
{
|
||||
// Should be more than BarTagClass. More because this class is after
|
||||
// BarTagClass (order by name). So we want to ensure it will be before it
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@
|
||||
<tag name="foo_tag"/>
|
||||
</service>
|
||||
<service id="foo_tagged_iterator" class="Bar" public="true">
|
||||
<argument type="tagged_iterator" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
|
||||
<argument type="tagged_iterator" tag="foo_tag" index-by="barfoo" default-index-method="foobar" default-priority-method="getPriority"/>
|
||||
</service>
|
||||
<service id="foo_tagged_locator" class="Bar" public="true">
|
||||
<argument type="tagged_locator" tag="foo_tag" index-by="barfoo" default-index-method="foobar"/>
|
||||
<argument type="tagged_locator" tag="foo_tag" index-by="barfoo" default-index-method="foobar" default-priority-method="getPriority"/>
|
||||
</service>
|
||||
<service id="Psr\Container\ContainerInterface" alias="service_container" public="false"/>
|
||||
<service id="Symfony\Component\DependencyInjection\ContainerInterface" alias="service_container" public="false"/>
|
||||
|
@ -10,10 +10,10 @@ services:
|
||||
- { name: foo }
|
||||
foo_service_tagged_iterator:
|
||||
class: Bar
|
||||
arguments: [!tagged_iterator { tag: foo, index_by: barfoo, default_index_method: foobar }]
|
||||
arguments: [!tagged_iterator { tag: foo, index_by: barfoo, default_index_method: foobar, default_priority_method: getPriority }]
|
||||
foo_service_tagged_locator:
|
||||
class: Bar
|
||||
arguments: [!tagged_locator { tag: foo, index_by: barfoo, default_index_method: foobar }]
|
||||
arguments: [!tagged_locator { tag: foo, index_by: barfoo, default_index_method: foobar, default_priority_method: getPriority }]
|
||||
bar_service_tagged_locator:
|
||||
class: Bar
|
||||
arguments: [!tagged_locator foo]
|
||||
|
@ -327,10 +327,10 @@ class XmlFileLoaderTest extends TestCase
|
||||
$this->assertCount(1, $container->getDefinition('foo_tagged_iterator')->getArguments());
|
||||
$this->assertCount(1, $container->getDefinition('foo_tagged_locator')->getArguments());
|
||||
|
||||
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar');
|
||||
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar', false, 'getPriority');
|
||||
$this->assertEquals($taggedIterator, $container->getDefinition('foo_tagged_iterator')->getArgument(0));
|
||||
|
||||
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar', true);
|
||||
$taggedIterator = new TaggedIteratorArgument('foo_tag', 'barfoo', 'foobar', true, 'getPriority');
|
||||
$this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_tagged_locator')->getArgument(0));
|
||||
}
|
||||
|
||||
|
@ -306,10 +306,10 @@ class YamlFileLoaderTest extends TestCase
|
||||
$this->assertCount(1, $container->getDefinition('foo_service_tagged_iterator')->getArguments());
|
||||
$this->assertCount(1, $container->getDefinition('foo_service_tagged_locator')->getArguments());
|
||||
|
||||
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar');
|
||||
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar', false, 'getPriority');
|
||||
$this->assertEquals($taggedIterator, $container->getDefinition('foo_service_tagged_iterator')->getArgument(0));
|
||||
|
||||
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar', true);
|
||||
$taggedIterator = new TaggedIteratorArgument('foo', 'barfoo', 'foobar', true, 'getPriority');
|
||||
$this->assertEquals(new ServiceLocatorArgument($taggedIterator), $container->getDefinition('foo_service_tagged_locator')->getArgument(0));
|
||||
|
||||
if (is_subclass_of('Symfony\Component\Yaml\Exception\ExceptionInterface', 'Throwable')) {
|
||||
|
Reference in New Issue
Block a user