Skip to content

Commit

Permalink
Apply attribute configurator to child classes
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN committed Mar 21, 2024
1 parent 759b6e1 commit 2c896cb
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Expand Up @@ -9,6 +9,7 @@ CHANGELOG
* Have `ServiceLocator` implement `ServiceCollectionInterface`
* Add `#[Lazy]` attribute as shortcut for `#[Autowire(lazy: [bool|string])]` and `#[Autoconfigure(lazy: [bool|string])]`
* Add `#[AutowireMethodOf]` attribute to autowire a method of a service as a callable
* `ContainerBuilder::registerAttributeForAutoconfiguration()` is applied to child classes for the autoconfigured attribute, except there is another autoconfiguration for this child attribute class.

7.0
---
Expand Down
Expand Up @@ -96,7 +96,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed

if ($this->classAttributeConfigurators) {
foreach ($classReflector->getAttributes() as $attribute) {
if ($configurator = $this->classAttributeConfigurators[$attribute->getName()] ?? null) {
if ($configurator = $this->findConfigurator($this->classAttributeConfigurators, $attribute->getName())) {
$configurator($conditionals, $attribute->newInstance(), $classReflector);
}
}
Expand All @@ -112,7 +112,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
if ($constructorReflector) {
foreach ($constructorReflector->getParameters() as $parameterReflector) {
foreach ($parameterReflector->getAttributes() as $attribute) {
if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) {
if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) {
$configurator($conditionals, $attribute->newInstance(), $parameterReflector);
}
}
Expand All @@ -128,7 +128,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed

if ($this->methodAttributeConfigurators) {
foreach ($methodReflector->getAttributes() as $attribute) {
if ($configurator = $this->methodAttributeConfigurators[$attribute->getName()] ?? null) {
if ($configurator = $this->findConfigurator($this->methodAttributeConfigurators, $attribute->getName())) {
$configurator($conditionals, $attribute->newInstance(), $methodReflector);
}
}
Expand All @@ -137,7 +137,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
if ($this->parameterAttributeConfigurators) {
foreach ($methodReflector->getParameters() as $parameterReflector) {
foreach ($parameterReflector->getAttributes() as $attribute) {
if ($configurator = $this->parameterAttributeConfigurators[$attribute->getName()] ?? null) {
if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) {
$configurator($conditionals, $attribute->newInstance(), $parameterReflector);
}
}
Expand All @@ -153,7 +153,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed
}

foreach ($propertyReflector->getAttributes() as $attribute) {
if ($configurator = $this->propertyAttributeConfigurators[$attribute->getName()] ?? null) {
if ($configurator = $this->findConfigurator($this->propertyAttributeConfigurators, $attribute->getName())) {
$configurator($conditionals, $attribute->newInstance(), $propertyReflector);
}
}
Expand All @@ -167,4 +167,23 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed

return parent::processValue($value, $isRoot);
}

/**
* Looks for a configurator for the given attribute name in the given configurators array.
* If no configurator is found, the parent class is checked.
* If no configurator is found, null is returned.
* The result is cached in the configurators array passed by reference.
*/
private function findConfigurator(array &$configurators, string $attributeName): ?callable
{
if (array_key_exists($attributeName, $configurators)) {
return $configurators[$attributeName];
}

if (class_exists($attributeName) && $parent = get_parent_class($attributeName)) {
return $configurators[$attributeName] = self::findConfigurator($configurators, $parent);
}

return $configurators[$attributeName] = null;
}
}
Expand Up @@ -45,4 +45,39 @@ public function testAttributeConfiguratorCallableMissingType()
$this->expectExceptionMessage('Argument "$reflector" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in ');
(new AttributeAutoconfigurationPass())->process($container);
}

public function testCallNearestParentConfigurator()
{
$calls = [];
$container = new ContainerBuilder();
$container->registerAttributeForAutoconfiguration(AsTaggedItem::class, function (ChildDefinition $definition, AsTaggedItem $attribute, \ReflectionClass $reflector) use (&$calls) {
$calls[] = $attribute;
});
$container->register('foo', ServiceTaggedWithFooIndex::class)
->setAutoconfigured(true)
;

(new AttributeAutoconfigurationPass())->process($container);

$this->assertSame([], $container->getDefinition('foo')->getInstanceofConditionals());
$this->assertCount(1, $calls);
}
}

#[\Attribute(\Attribute::TARGET_CLASS)]
class AsTaggedItemWithFooIndex extends AsTaggedItem
{
public function __construct(
?int $priority = null,
) {
parent::__construct(
index: 'foo',
priority: $priority
);
}
}

#[AsTaggedItemWithFooIndex]
class ServiceTaggedWithFooIndex
{
}

0 comments on commit 2c896cb

Please sign in to comment.