Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 37a5d26

Browse files
[DependencyInjection] Add #[Autoconfigure] to help define autoconfiguration rules
1 parent 29b88af commit 37a5d26

File tree

22 files changed

+310
-20
lines changed

22 files changed

+310
-20
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class UnusedTagsPass implements CompilerPassInterface
3232
'container.env_var_loader',
3333
'container.env_var_processor',
3434
'container.hot_path',
35+
'container.ignore_autoconfigure_attributes',
3536
'container.no_preload',
3637
'container.preload',
3738
'container.private',
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Attribute;
13+
14+
use Symfony\Contracts\Service\Attribute\AutoTag;
15+
16+
/**
17+
* Tells when a class or an interface should autoconfigured as a command.
18+
*
19+
* @author Nicolas Grekas <[email protected]>
20+
*/
21+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
22+
class CommandAutoTag extends AutoTag
23+
{
24+
public const NAME = 'console.command';
25+
26+
public function __construct(string $name)
27+
{
28+
parent::__construct(tags: [[
29+
static::NAME => [
30+
'command' => $name,
31+
],
32+
]]);
33+
}
34+
}

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ CHANGELOG
44
5.3
55
---
66

7-
* Added `GithubActionReporter` to render annotations in a Github Action
8-
* Added `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options.
7+
* Add `GithubActionReporter` to render annotations in a Github Action
8+
* Add `InputOption::VALUE_NEGATABLE` flag to handle `--foo`/`--no-foo` options
9+
* Add `Attribute\CommandAutoTag` to help autoconfigure commands
910

1011
5.2.0
1112
-----

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3.0
5+
-----
6+
7+
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` attribute on PHP 8
8+
49
5.2.0
510
-----
611

src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function __construct()
4242
$this->beforeOptimizationPasses = [
4343
100 => [
4444
new ResolveClassPass(),
45+
new RegisterAutoconfigureAttributesPass(),
4546
new ResolveInstanceofConditionalsPass(),
4647
new RegisterEnvVarProcessorsPass(),
4748
],
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\ContainerBuilder;
15+
use Symfony\Component\DependencyInjection\Definition;
16+
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
17+
use Symfony\Contracts\Service\Attribute\Autoconfigure;
18+
19+
/**
20+
* Reads #[Autoconfigure] attributes on definitions that are autoconfigured
21+
* and don't have the "container.ignore_autoconfigure_attributes" tag.
22+
*
23+
* @author Nicolas Grekas <[email protected]>
24+
*/
25+
class RegisterAutoconfigureAttributesPass implements CompilerPassInterface
26+
{
27+
private $ignoreAttributeTag;
28+
private $registerForAutoconfiguration;
29+
30+
public function __construct(string $ignoreAttributeTag = 'container.ignore_autoconfigure_attributes')
31+
{
32+
if (80000 > \PHP_VERSION_ID) {
33+
return;
34+
}
35+
36+
$this->ignoreAttributeTag = $ignoreAttributeTag;
37+
38+
$parseDefinition = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinition');
39+
$parseDefinition->setAccessible(true);
40+
$yamlLoader = $parseDefinition->getDeclaringClass()->newInstanceWithoutConstructor();
41+
$yamlLoader->isLoadingInstanceof = true;
42+
43+
$this->registerForAutoconfiguration = static function (ContainerBuilder $container, \ReflectionClass $class, \ReflectionAttribute $attribute) use ($parseDefinition, $yamlLoader) {
44+
if (!class_exists(Autoconfigure::class)) {
45+
throw new \LogicException(sprintf('You cannot use the "%s" attribute unless Symfony Contracts 2.4+ are installed. Try running "composer require symfony/service-contracts:^2.4".', Autoconfigure::class));
46+
}
47+
48+
$parseDefinition->invoke($yamlLoader, $class->name, [$container->registerForAutoconfiguration($class->name)] + (array) $attribute->getInstance(), $class->getFileName(), [], true);
49+
};
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function process(ContainerBuilder $container)
56+
{
57+
if (80000 > \PHP_VERSION_ID) {
58+
return;
59+
}
60+
61+
foreach ($container->getDefinitions() as $id => $definition) {
62+
if ($this->accept($definition)) {
63+
$this->processClass($container, $container->getReflectionClass($definition->getClass()));
64+
}
65+
}
66+
}
67+
68+
public function accept(Definition $definition): bool
69+
{
70+
return 80000 <= \PHP_VERSION_ID && $definition->isAutoconfigured() && !$definition->hasTag($this->ignoreAttributeTag);
71+
}
72+
73+
public function processClass(ContainerBuilder $container, Definition $definition, \ReflectionClass $class)
74+
{
75+
foreach ($class->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
76+
($this->registerForAutoconfiguration)($container, $class, $attribute);
77+
}
78+
}
79+
}

src/Symfony/Component/DependencyInjection/Loader/FileLoader.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Config\Loader\Loader;
1919
use Symfony\Component\Config\Resource\GlobResource;
2020
use Symfony\Component\DependencyInjection\ChildDefinition;
21+
use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass;
2122
use Symfony\Component\DependencyInjection\ContainerBuilder;
2223
use Symfony\Component\DependencyInjection\Definition;
2324
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
@@ -96,7 +97,8 @@ public function registerClasses(Definition $prototype, string $namespace, string
9697
throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace));
9798
}
9899

99-
$classes = $this->findClasses($namespace, $resource, (array) $exclude);
100+
$autoconfigureAttributes = new RegisterAutoconfigureAttributesPass();
101+
$classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null);
100102
// prepare for deep cloning
101103
$serializedPrototype = serialize($prototype);
102104

@@ -149,7 +151,7 @@ protected function setDefinition(string $id, Definition $definition)
149151
}
150152
}
151153

152-
private function findClasses(string $namespace, string $pattern, array $excludePatterns): array
154+
private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes): array
153155
{
154156
$parameterBag = $this->container->getParameterBag();
155157

@@ -207,6 +209,10 @@ private function findClasses(string $namespace, string $pattern, array $excludeP
207209
if ($r->isInstantiable() || $r->isInterface()) {
208210
$classes[$class] = null;
209211
}
212+
213+
if ($autoconfigureAttributes && !$r->isInstantiable()) {
214+
$autoconfigureAttributes->processClass($r);
215+
}
210216
}
211217

212218
// track only for new & removed files

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ private function parseDefinition(string $id, $service, string $file, array $defa
389389
];
390390
}
391391

392+
$definition = isset($service[0]) && $service[0] instanceof Definition ? array_shift($service) : null;
393+
392394
$this->checkDefinition($id, $service, $file);
393395

394396
if (isset($service['alias'])) {
@@ -423,7 +425,9 @@ private function parseDefinition(string $id, $service, string $file, array $defa
423425
return $return ? $alias : $this->container->setAlias($id, $alias);
424426
}
425427

426-
if ($this->isLoadingInstanceof) {
428+
if (null !== $definition) {
429+
// no-op
430+
} elseif ($this->isLoadingInstanceof) {
427431
$definition = new ChildDefinition('');
428432
} elseif (isset($service['parent'])) {
429433
if ('' !== $service['parent'] && '@' === $service['parent'][0]) {
@@ -627,7 +631,8 @@ private function parseDefinition(string $id, $service, string $file, array $defa
627631

628632
if (isset($defaults['bind']) || isset($service['bind'])) {
629633
// deep clone, to avoid multiple process of the same instance in the passes
630-
$bindings = isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : [];
634+
$bindings = $definition->getBindings();
635+
$bindings += isset($defaults['bind']) ? unserialize(serialize($defaults['bind'])) : [];
631636

632637
if (isset($service['bind'])) {
633638
if (!\is_array($service['bind'])) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\EventDispatcher\Attribute;
13+
14+
use Symfony\Contracts\Service\Attribute\AutoTag;
15+
16+
/**
17+
* Tells when a class or an interface should autoconfigured as an event listener.
18+
*
19+
* @author Nicolas Grekas <[email protected]>
20+
*/
21+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
22+
class EventListenerAutoTag extends AutoTag
23+
{
24+
public const NAME = 'kernel.event_listener';
25+
26+
public function __construct(string $event = null, string $method = null, int $priority = 0)
27+
{
28+
parent::__construct(tags: [[
29+
static::NAME => [
30+
'event' => $event,
31+
'method' => $method,
32+
'priority' => $priority,
33+
],
34+
]]);
35+
}
36+
}

src/Symfony/Component/EventDispatcher/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.3
5+
---
6+
7+
* Add `Attribute\EventListenerAutoTag` to help autoconfigure event listeners
8+
49
5.1.0
510
-----
611

0 commit comments

Comments
 (0)