Skip to content

Commit a05c57b

Browse files
committed
[TwigBundle] Improve error when autoconfiguring a class with both ExtensionInterface and Twig callable attribute
1 parent 0b4d21c commit a05c57b

File tree

2 files changed

+68
-14
lines changed

2 files changed

+68
-14
lines changed

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

Lines changed: 11 additions & 0 deletions
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()".', $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()".', $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

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,31 @@
1616
use Symfony\Bundle\TwigBundle\TwigBundle;
1717
use Symfony\Component\Config\Loader\LoaderInterface;
1818
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
use Symfony\Component\DependencyInjection\Exception\LogicException;
1920
use Symfony\Component\Filesystem\Filesystem;
2021
use Symfony\Component\HttpKernel\Kernel;
2122
use Twig\Attribute\AsTwigFilter;
2223
use Twig\Attribute\AsTwigFunction;
2324
use Twig\Attribute\AsTwigTest;
2425
use Twig\Environment;
2526
use Twig\Error\RuntimeError;
27+
use Twig\Extension\AbstractExtension;
2628
use Twig\Extension\AttributeExtension;
2729

2830
class AttributeExtensionTest extends TestCase
2931
{
30-
public function testExtensionWithAttributes()
32+
/** @beforeClass */
33+
public static function assertTwigVersion(): void
3134
{
3235
if (!class_exists(AttributeExtension::class)) {
3336
self::markTestSkipped('Twig 3.21 is required.');
3437
}
38+
}
3539

36-
$kernel = new class('test', true) extends Kernel
40+
public function testExtensionWithAttributes()
41+
{
42+
$kernel = new class extends AttributeExtensionKernel
3743
{
38-
public function registerBundles(): iterable
39-
{
40-
return [new FrameworkBundle(), new TwigBundle()];
41-
}
42-
4344
public function registerContainerConfiguration(LoaderInterface $loader): void
4445
{
4546
$loader->load(static function (ContainerBuilder $container) {
@@ -53,11 +54,6 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
5354
$container->setAlias('twig_test', 'twig')->setPublic(true);
5455
});
5556
}
56-
57-
public function getProjectDir(): string
58-
{
59-
return sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension';
60-
}
6157
};
6258

6359
$kernel->boot();
@@ -73,6 +69,26 @@ public function getProjectDir(): string
7369
$twig->getRuntime(StaticExtensionWithAttributes::class);
7470
}
7571

72+
public function testInvalidExtensionClass()
73+
{
74+
$kernel = new class extends AttributeExtensionKernel
75+
{
76+
public function registerContainerConfiguration(LoaderInterface $loader): void
77+
{
78+
$loader->load(static function (ContainerBuilder $container) {
79+
$container->register(InvalidExtensionWithAttributes::class, InvalidExtensionWithAttributes::class)
80+
->setAutoconfigured(true);
81+
});
82+
}
83+
};
84+
85+
$this->expectException(LogicException::class);
86+
$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()".');
87+
88+
$kernel->boot();
89+
}
90+
91+
7692
/**
7793
* @before
7894
* @after
@@ -85,6 +101,24 @@ protected function deleteTempDir()
85101
}
86102
}
87103

104+
abstract class AttributeExtensionKernel extends Kernel
105+
{
106+
public function __construct()
107+
{
108+
parent::__construct('test', true);
109+
}
110+
111+
public function registerBundles(): iterable
112+
{
113+
return [new FrameworkBundle(), new TwigBundle()];
114+
}
115+
116+
public function getProjectDir(): string
117+
{
118+
return sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension';
119+
}
120+
}
121+
88122
class StaticExtensionWithAttributes
89123
{
90124
#[AsTwigFilter('foo')]
@@ -112,10 +146,19 @@ public function __construct(private bool $prefix)
112146
{
113147
}
114148

115-
#[AsTwigFilter('foo')]
116-
#[AsTwigFunction('foo')]
149+
#[AsTwigFilter('prefix_foo')]
150+
#[AsTwigFunction('prefix_foo')]
117151
public function prefix(string $value): string
118152
{
119153
return $this->prefix.$value;
120154
}
121155
}
156+
157+
class InvalidExtensionWithAttributes extends AbstractExtension
158+
{
159+
#[AsTwigFilter('fun')]
160+
public function funFilter(): string
161+
{
162+
return 'fun';
163+
}
164+
}

0 commit comments

Comments
 (0)