Skip to content

Commit bd4ece5

Browse files
[DI] Add "#[Autoconfigure]" to tell how a discovered type should be autoconfigured
1 parent a608793 commit bd4ece5

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

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

+21-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\DependencyInjection\ContainerBuilder;
2222
use Symfony\Component\DependencyInjection\Definition;
2323
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
24+
use Symfony\Contracts\Service\Attribute\Autoconfigure;
2425

2526
/**
2627
* FileLoader is the abstract class used by all built-in loaders that are file based.
@@ -96,7 +97,7 @@ 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+
$classes = $this->findClasses($namespace, $resource, (array) $exclude, $prototype->isAutoconfigured());
100101
// prepare for deep cloning
101102
$serializedPrototype = serialize($prototype);
102103

@@ -149,7 +150,7 @@ protected function setDefinition(string $id, Definition $definition)
149150
}
150151
}
151152

152-
private function findClasses(string $namespace, string $pattern, array $excludePatterns): array
153+
private function findClasses(string $namespace, string $pattern, array $excludePatterns, bool $autoconfigure): array
153154
{
154155
$parameterBag = $this->container->getParameterBag();
155156

@@ -167,6 +168,13 @@ private function findClasses(string $namespace, string $pattern, array $excludeP
167168
}
168169
}
169170

171+
if (80000 <= \PHP_VERSION_ID && $autoconfigure) {
172+
$parseDefinition = new \ReflectionMethod(YamlFileLoader::class, 'parseDefinition');
173+
$parseDefinition->setAccessible(true);
174+
$yamlLoader = $parseDefinition->getDeclaringClass()->newInstanceWithoutConstructor();
175+
$yamlLoader->isLoadingInstanceof = true;
176+
}
177+
170178
$pattern = $parameterBag->unescapeValue($parameterBag->resolveValue($pattern));
171179
$classes = [];
172180
$extRegexp = '/\\.php$/';
@@ -207,6 +215,17 @@ private function findClasses(string $namespace, string $pattern, array $excludeP
207215
if ($r->isInstantiable() || $r->isInterface()) {
208216
$classes[$class] = null;
209217
}
218+
219+
if (80000 > \PHP_VERSION_ID || !$autoconfigure) {
220+
continue;
221+
}
222+
223+
foreach ($r->getAttributes(Autoconfigure::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
224+
if (!class_exists(Autoconfigure::class)) {
225+
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));
226+
}
227+
$parseDefinition->invoke($yamlLoader, $class, [$this->container->registerForAutoconfiguration($class)] + (array) $attribute->getInstance(), $r->getFileName(), [], true);
228+
}
210229
}
211230

212231
// track only for new & removed files

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

+7-2
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'])) {
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 <fabien@symfony.com>
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\Contracts\Service\Attribute;
13+
14+
/**
15+
* A tag to tell how a base type should be autoconfigured.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
20+
class Autoconfigure
21+
{
22+
public function __construct(
23+
public ?array $tags = null,
24+
public ?array $calls = null,
25+
public ?array $bind = null,
26+
public bool|string|null $lazy = null,
27+
public ?bool $public = null,
28+
public ?bool $shared = null,
29+
public ?bool $autowire = null,
30+
public ?array $properties = null,
31+
public array|string|null $configurator = null,
32+
) {
33+
}
34+
}

0 commit comments

Comments
 (0)