10000 [DependencyInjection] added Ability to define a priority method for tagged service by lyrixx · Pull Request #33628 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[DependencyInjection] added Ability to define a priority method for tagged service #33628

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

Merged
merged 1 commit into from
Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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([]);

Expand All @@ -41,6 +42,7 @@ public function __construct(string $tag, string $indexAttribute = null, string $
$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()
Expand All @@ -62,4 +64,9 @@ public function needsIndexes(): bool
{
return $this->needsIndexes;
}

public function getDefaultPriorityMethod(): ?string
{
return $this->defaultPriorityMethod;
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,62 @@ 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);

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ private function convertParameters(array $parameters, string $type, \DOMElement
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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ private function dumpValue($value)
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $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, $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]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,11 +721,11 @@ private function resolveServices($value, string $file, bool $isParameter = false
$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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;

/**
Expand Down Expand Up @@ -289,6 +290,30 @@ public function testTaggedServiceWithIndexAttributeAndDefaultMethod()
$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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public function testDumpLoad()

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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public function testInlineServices()

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ public static function getFooBar()
{
return 'bar_tab_class_with_defaultmethod';
}

public static function getPriority(): int
{
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ public static function getDefaultFooName()
{
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,10 @@ public function testParseTaggedArgumentsWithIndexBy()
$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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,10 @@ public function testTaggedArgumentsWithIndex()
$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')) {
Expand Down
0