8000 minor #60415 [TwigBundle] Improve error when autoconfiguring a class … · symfony/symfony@170b631 · GitHub
[go: up one dir, main page]

Skip to content

Commit 170b631

Browse files
minor #60415 [TwigBundle] Improve error when autoconfiguring a class with both ExtensionInterface and Twig callable attribute (GromNaN)
This PR was merged into the 7.3 branch. Discussion ---------- [TwigBundle] Improve error when autoconfiguring a class with both `ExtensionInterface` and Twig callable attribute | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | no | Deprecations? | no | Issues | - | License | MIT Reported by Javier: > I'm updating Twig extensions to use the `#[AsTwig...]` attributes. I removed the `getFunctions()` method and had this: > ``` > class FooExtension extends AbstractExtension > { > #[AsTwigFunction('name_of_function')] > public function foo(): string > { > // ... > } > } > ``` > Then I saw this exception: `Unable to register extension "App\Twig\FooExtension" as it is already registered.` > My error is that I forgot to remove extends `AbstractExtension` But I think that Symfony's error exception should be more helpful. A quick example of something that could've helped me: *"When using the AsTwigFunction attribute, your Twig extension cannot extend AbstractExtension"* The ExtensionSet exception is thrown because the same an extension is registered twice: 1. It has the `twig.extension` tag autoconfigured because it implements `Twig\Extension\ExtensionInterface` 2. An `AttributeExtension` is registered for this class because it as one of the extension attributes (since #52748) Previous exception when `twig` service is instanciated: > LogicException : Unable to register extension "Symfony\Bundle\TwigBundle\Tests\Functional\InvalidExtensionWithAttributes" as it is already registered. New exception during container compilation: > Symfony\Component\DependencyInjection\Exception\LogicException : The class "Symfony\Bundle\TwigBundle\Tests\Functional\InvalidExtensionWithAttributes" cannot both extend "Twig\Extension\AbstractExtension" and use the "#[Twig\Attribute\AsTwigFilter]" attribute on method "funFilter()", choose one or the other. Checking `AbstractExtension` makes the error message more specific to the code, as most extensions extend this class and doesn't implement the interface directly. Commits ------- 752b41d [TwigBundle] Improve error when autoconfiguring a class with both ExtensionInterface and Twig callable attribute
2 parents e67b362 + 752b41d commit 170b631

File tree

2 files changed

+72
-15
lines changed

2 files changed

+72
-15
lines changed

src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/AttributeExtensionPass.php

+11
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
use Symfony\Component\DependencyInjection\ChildDefinition;
1515
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Exception\LogicException;
1718
use Twig\Attribute\AsTwigFilter;
1819
use Twig\Attribute\AsTwigFunction;
1920
use Twig\Attribute\AsTwigTest;
21+
use Twig\Extension\AbstractExtension;
2022
use Twig\Extension\AttributeExtension;
23+
use Twig\Extension\ExtensionInterface;
2124

2225
/**
2326
* Register an instance of AttributeExtension for each service using the
@@ -33,6 +36,14 @@ final class AttributeExtensionPass implements CompilerPassInterface
3336

3437
public static function autoconfigureFromAttribute(ChildDefinition $definition, AsTwigFilter|AsTwigFunction|AsTwigTest $attribute, \ReflectionMethod $reflector): void
3538
{
39+
$class = $reflector->getDeclaringClass();
40+
if ($class->implementsInterface(ExtensionInterface::class)) {
41+
if ($class->isSubclassOf(AbstractExtension::class)) {
42+
throw new LogicException(\sprintf('The class "%s" cannot extend "%s" and use the "#[%s]" attribute on method "%s()", choose one or the other.', $class->name, AbstractExtension::class, $attribute::class, $reflector->name));
43+
}
44+
throw new LogicException(\sprintf('The class "%s" cannot implement "%s" and use the "#[%s]" attribute on method "%s()", choose one or the other.', $class->name, ExtensionInterface::class, $attribute::class, $reflector->name));
45+
}
46+
3647
$definition->addTag(self::TAG);
3748

3849
// The service must be tagged as a runtime to call non-static methods

src/Symfony/Bundle/TwigBundle/Tests/Functional/AttributeExtensionTest.php

+61-15
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,39 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\Tests\Functional;
1313

14+
use PHPUnit\Framework\Attributes\After;
15+
use PHPUnit\Framework\Attributes\Before;
16+
use PHPUnit\Framework\Attributes\BeforeClass;
1417
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
1518
use Symfony\Bundle\TwigBundle\Tests\TestCase;
1619
use Symfony\Bundle\TwigBundle\TwigBundle;
1720
use Symfony\Component\Config\Loader\LoaderInterface;
1821
use Symfony\Component\DependencyInjection\ContainerBuilder;
22+
use Symfony\Component\DependencyInjection\Exception\LogicException;
1923
use Symfony\Component\Filesystem\Filesystem;
2024
use Symfony\Component\HttpKernel\Kernel;
2125
use Twig\Attribute\AsTwigFilter;
2226
use Twig\Attribute\AsTwigFunction;
2327
use Twig\Attribute\AsTwigTest;
2428
use Twig\Environment;
2529
use Twig\Error\RuntimeError;
30+
use Twig\Extension\AbstractExtension;
2631
use Twig\Extension\AttributeExtension;
2732

2833
class AttributeExtensionTest extends TestCase
2934
{
30-
public function testExtensionWithAttributes()
35+
/** @beforeClass */
36+
#[BeforeClass]
37+
public static function assertTwigVersion(): void
3138
{
3239
if (!class_exists(AttributeExtension::class)) {
3340
self::markTestSkipped('Twig 3.21 is required.');
3441
}
42+
}
3543

36-
$kernel = new class('test', true) extends Kernel
37-
{
38-
public function registerBundles(): iterable
39-
{
40-
return [new FrameworkBundle(), new TwigBundle()];
41-
}
42-
44+
public function testExtensionWithAttributes()
45+
{
46+
$kernel = new class extends AttributeExtensionKernel {
4347
public function registerContainerConfiguration(LoaderInterface $loader): void
4448
{
4549
$loader->load(static function (ContainerBuilder $container) {
@@ -53,11 +57,6 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
5357
$container->setAlias('twig_test', 'twig')->setPublic(true);
5458
});
5559
}
56-
57-
public function getProjectDir(): string
58-
{
59-
return sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension';
60-
}
6160
};
6261

6362
$kernel->boot();
@@ -73,10 +72,30 @@ public function getProjectDir(): string
7372
$twig->getRuntime(StaticExtensionWithAttributes::class);
7473
}
7574

75+
public function testInvalidExtensionClass()
76+
{
77+
$kernel = new class extends AttributeExtensionKernel {
78+
public function registerContainerConfiguration(LoaderInterface $loader): void
79+
{
80+
$loader->load(static function (ContainerBuilder $container) {
81+
$container->register(InvalidExtensionWithAttributes::class, InvalidExtensionWithAttributes::class)
82+
->setAutoconfigured(true);
83+
});
84+
}
85+
};
86+
87+
$this->expectException(LogicException::class);
88+
$this->expectExceptionMessage('The class "Symfony\Bundle\TwigBundle\Tests\Functional\InvalidExtensionWithAttributes" cannot extend "Twig\Extension\AbstractExtension" and use the "#[Twig\Attribute\AsTwigFilter]" attribute on method "funFilter()", choose one or the other.');
89+
90+
$kernel->boot();
91+
}
92+
93+
7694
/**
7795
* @before
7896
* @after
7997
*/
98+
#[Before, After]
8099
protected function deleteTempDir()
81100
{
82101
if (file_exists($dir = sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension')) {
@@ -85,6 +104,24 @@ protected function deleteTempDir()
85104
}
86105
}
87106

107+
abstract class AttributeExtensionKernel extends Kernel
108+
{
109+
public function __construct()
110+
{
111+
parent::__construct('test', true);
112+
}
113+
114+
public function registerBundles(): iterable
115+
{
116+
return [new FrameworkBundle(), new TwigBundle()];
117+
}
118+
119+
public function getProjectDir(): string
120+
{
121+
return sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension';
122+
}
123+
}
124+
88125
class StaticExtensionWithAttributes
89126
{
90127
#[AsTwigFilter('foo')]
@@ -112,10 +149,19 @@ public function __construct(private bool $prefix)
112149
{
113150
}
114151

115-
#[AsTwigFilter('foo')]
116-
#[AsTwigFunction('foo')]
152+
#[AsTwigFilter('prefix_foo')]
153+
#[AsTwigFunction('prefix_foo')]
117154
public function prefix(string $value): string
118155
{
119156
return $this->prefix.$value;
120157
}
121158
}
159+
160+
class InvalidExtensionWithAttributes extends AbstractExtension
161+
{
162+
#[AsTwigFilter('fun')]
163+
public function funFilter(): string
164+
{
165+
return 'fun';
166+
}
167+
}

0 commit comments

Comments
 (0)
0