Skip to content

Commit

Permalink
[DependencyInjection] Apply attribute configurator to child classes
Browse files Browse the repository at this point in the history
  • Loading branch information
GromNaN authored and fabpot committed Mar 23, 2024
1 parent 759b6e1 commit 69dc71b
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 9 deletions.
Expand Up @@ -699,9 +699,9 @@ public function load(array $configs, ContainerBuilder $container): void
$taskAttributeClass,
static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void {
$tagAttributes = get_object_vars($attribute) + [
'trigger' => match ($attribute::class) {
AsPeriodicTask::class => 'every',
AsCronTask::class => 'cron',
'trigger' => match (true) {
$attribute instanceof AsPeriodicTask => 'every',
$attribute instanceof AsCronTask => 'cron',
},
];
if ($reflector instanceof \ReflectionMethod) {
Expand Down
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
* Make `ContainerBuilder::registerAttributeForAutoconfiguration()` propagate to attribute classes that extend the registered 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,20 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed

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

/**
* Find the first configurator for the given attribute name, looking up the class hierarchy.
*/
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 @@ -57,6 +57,7 @@
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3;
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService3Configurator;
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService4;
use Symfony\Component\DependencyInjection\Tests\Fixtures\TaggedService5;
use Symfony\Contracts\Service\Attribute\SubscribedService;
use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
Expand Down Expand Up @@ -993,6 +994,10 @@ static function (ChildDefinition $definition, CustomAnyAttribute $attribute, \Re
->setPublic(true)
->setAutoconfigured(true);

$container->register(TaggedService5::class)
->setPublic(true)
->setAutoconfigured(true);

$container->register('failing_factory', \stdClass::class);
$container->register('ccc', TaggedService4::class)
->setFactory([new Reference('failing_factory'), 'create'])
Expand All @@ -1018,6 +1023,12 @@ static function (ChildDefinition $definition, CustomAnyAttribute $attribute, \Re
['property' => 'name'],
['someAttribute' => 'on name', 'priority' => 0, 'property' => 'name'],
],
TaggedService5::class => [
['class' => TaggedService5::class],
['parameter' => 'param1'],
['method' => 'fooAction'],
['property' => 'name'],
],
'ccc' => [
['class' => TaggedService4::class],
['method' => 'fooAction'],
Expand Down
Expand Up @@ -12,6 +12,6 @@
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)]
final class CustomAnyAttribute
class CustomAnyAttribute
{
}
@@ -0,0 +1,17 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY | \Attribute::TARGET_PARAMETER)]
class CustomChildAttribute extends CustomAnyAttribute
{
}
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Tests\Fixtures;

use Symfony\Component\DependencyInjection\Tests\Fixtures\Attribute\CustomChildAttribute;

#[CustomChildAttribute]
final class TaggedService5
{
#[CustomChildAttribute]
public string $name;

public function __construct(
#[CustomChildAttribute]
private string $param1,
) {}

#[CustomChildAttribute]
public function fooAction(
#[CustomChildAttribute]
string $param1
) {}
}

0 comments on commit 69dc71b

Please sign in to comment.