Skip to content

Commit

Permalink
minor #51916 [DependencyInjection] Simplify using DI attributes with …
Browse files Browse the repository at this point in the history
…`ServiceLocator/Iterator`'s (kbond)

This PR was merged into the 6.4 branch.

Discussion
----------

[DependencyInjection] Simplify using DI attributes with `ServiceLocator/Iterator`'s

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | n/a
| License       | MIT

Instead of:
```php
#[AutowireLocator([
    'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')),
])]
```

This PR allows:
```php
#[AutowireLocator([
    'subscribed' => new Autowire('%some.parameter%'),
])]
```

Commits
-------

33169fe [DI] Simplify using DI attributes with `ServiceLocator/Iterator`'s
  • Loading branch information
nicolas-grekas committed Oct 12, 2023
2 parents 5f77c3f + 33169fe commit 253968e
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,67 +11,25 @@

namespace Symfony\Component\DependencyInjection\Attribute;

use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

/**
* Autowires an iterator of services based on a tag name or an explicit list of key => service-type pairs.
* Autowires an iterator of services based on a tag name.
*/
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class AutowireIterator extends Autowire
{
/**
* @see ServiceSubscriberInterface::getSubscribedServices()
*
* @param string|array<string|SubscribedService> $services A tag name or an explicit list of services
* @param string|string[] $exclude A service or a list of services to exclude
* @param string|string[] $exclude A service or a list of services to exclude
*/
public function __construct(
string|array $services,
string $tag,
string $indexAttribute = null,
string $defaultIndexMethod = null,
string $defaultPriorityMethod = null,
string|array $exclude = [],
bool $excludeSelf = true,
) {
if (\is_string($services)) {
parent::__construct(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf));

return;
}

$references = [];

foreach ($services as $key => $type) {
$attributes = [];

if ($type instanceof SubscribedService) {
$key = $type->key ?? $key;
$attributes = $type->attributes;
$type = ($type->nullable ? '?' : '').($type->type ?? throw new InvalidArgumentException(sprintf('When "%s" is used, a type must be set.', 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" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key));
}
$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;
}

$references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes);
}

parent::__construct(new IteratorArgument($references));
parent::__construct(new TaggedIteratorArgument($tag, $indexAttribute, $defaultIndexMethod, false, $defaultPriorityMethod, (array) $exclude, $excludeSelf));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@

namespace Symfony\Component\DependencyInjection\Attribute;

use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

Expand All @@ -37,14 +39,44 @@ public function __construct(
string|array $exclude = [],
bool $excludeSelf = true,
) {
$iterator = (new AutowireIterator($services, $indexAttribute, $defaultIndexMethod, $defaultPriorityMethod, (array) $exclude, $excludeSelf))->value;
if (\is_string($services)) {
parent::__construct(new ServiceLocatorArgument(new TaggedIteratorArgument($services, $indexAttribute, $defaultIndexMethod, true, $defaultPriorityMethod, (array) $exclude, $excludeSelf)));

if ($iterator instanceof TaggedIteratorArgument) {
$iterator = new TaggedIteratorArgument($iterator->getTag(), $iterator->getIndexAttribute(), $iterator->getDefaultIndexMethod(), true, $iterator->getDefaultPriorityMethod(), $iterator->getExclude(), $iterator->excludeSelf());
} elseif ($iterator instanceof IteratorArgument) {
$iterator = $iterator->getValues();
return;
}

parent::__construct(new ServiceLocatorArgument($iterator));
$references = [];

foreach ($services as $key => $type) {
$attributes = [];

if ($type instanceof Autowire) {
$references[$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" is used, a type must be set.', 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" is not a PHP type for key "%s".', \is_string($type) ? $type : get_debug_type($type), $key));
}
$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;
}

$references[$key] = new TypedReference($type, $type, $optionalBehavior, $name, $attributes);
}

parent::__construct(new ServiceLocatorArgument($references));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
Expand Down Expand Up @@ -78,6 +79,11 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Psr\Container\ContainerInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
Expand All @@ -33,7 +32,6 @@
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredInterface2;
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService1;
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutoconfiguredService2;
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireIteratorConsumer;
use Symfony\Component\DependencyInjection\Tests\Fixtures\AutowireLocatorConsumer;
use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
Expand Down Expand Up @@ -415,35 +413,7 @@ public function testLocatorConfiguredViaAttribute()
self::assertSame($container->get(FooTagClass::class), $s->locator->get('with_key'));
self::assertFalse($s->locator->has('nullable'));
self::assertSame('foo', $s->locator->get('subscribed'));
}

public function testIteratorConfiguredViaAttribute()
{
$container = new ContainerBuilder();
$container->setParameter('some.parameter', 'foo');
$container->register(BarTagClass::class)
->setPublic(true)
;
$container->register(FooTagClass::class)
->setPublic(true)
;
$container->register(AutowireIteratorConsumer::class)
->setAutowired(true)
->setPublic(true)
;

$container->compile();

/** @var AutowireIteratorConsumer $s */
$s = $container->get(AutowireIteratorConsumer::class);

self::assertInstanceOf(RewindableGenerator::class, $s->iterator);

$values = iterator_to_array($s->iterator);
self::assertCount(3, $values);
self::assertSame($container->get(BarTagClass::class), $values[BarTagClass::class]);
self::assertSame($container->get(FooTagClass::class), $values['with_key']);
self::assertSame('foo', $values['subscribed']);
self::assertSame('foo', $s->locator->get('subscribed1'));
}

public function testTaggedServiceWithIndexAttributeAndDefaultMethodConfiguredViaAttribute()
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function __construct(
'with_key' => FooTagClass::class,
'nullable' => '?invalid',
'subscribed' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%')),
'subscribed1' => new Autowire('%some.parameter%'),
])]
public readonly ContainerInterface $locator,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ public function testTaggedIteratorAndTaggedLocatorAttributes()
/** @var ServiceLocator $locator */
$locator = $container->get($locatorId)->get('foo::fooAction');

$this->assertCount(7, $locator->getProvidedServices());
$this->assertCount(6, $locator->getProvidedServices());

$this->assertTrue($locator->has('iterator1'));
$this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator1'));
Expand Down Expand Up @@ -557,11 +557,6 @@ public function testTaggedIteratorAndTaggedLocatorAttributes()
$this->assertCount(1, $argLocator);
$this->assertTrue($argLocator->has('foo'));
$this->assertSame('bar', $argLocator->get('foo'));

$this->assertTrue($locator->has('iterator3'));
$this->assertInstanceOf(RewindableGenerator::class, $argIterator = $locator->get('iterator3'));
$this->assertCount(1, $argIterator);
$this->assertSame('bar', iterator_to_array($argIterator)['foo']);
}
}

Expand Down Expand Up @@ -705,8 +700,7 @@ public function fooAction(
#[TaggedLocator('foobar')] ServiceLocator $locator1,
#[AutowireLocator('foobar')] ServiceLocator $locator2,
#[AutowireLocator(['bar', 'baz'])] ContainerInterface $container1,
#[AutowireLocator(['foo' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%'))])] ContainerInterface $container2,
#[AutowireIterator(['foo' => new SubscribedService(type: 'string', attributes: new Autowire('%some.parameter%'))])] iterable $iterator3,
#[AutowireLocator(['foo' => new Autowire('%some.parameter%')])] ContainerInterface $container2,
) {
}
}
1 change: 0 additions & 1 deletion src/Symfony/Component/HttpKernel/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"symfony/http-client-contracts": "<2.5",
"symfony/mailer": "<5.4",
"symfony/messenger": "<5.4",
"symfony/service-contracts": "<3.2",
"symfony/translation": "<5.4",
"symfony/translation-contracts": "<2.5",
"symfony/twig-bridge": "<5.4",
Expand Down

0 comments on commit 253968e

Please sign in to comment.