8000 [TwigBundle] Improve error when autoconfiguring a class with both Ext… · symfony/symfony@2abcc87 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2abcc87

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

File tree

2 files changed

+67
-15
lines changed

2 files changed

+67
-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 both 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 both 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

+56-15
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,30 @@
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
37-
{
38-
public function registerBundles(): iterable
39-
{
40-
return [new FrameworkBundle(), new TwigBundle()];
41-
}
42-
40+
public function testExtensionWithAttributes()
41+
{
42+
$kernel = new class extends AttributeExtensionKernel {
4343
public function registerContainerConfiguration(LoaderInterface $loader): void
4444
{
4545
$loader->load(static function (ContainerBuilder $container) {
@@ -53,11 +53,6 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
5353
$container->setAlias('twig_test', 'twig')->setPublic(true);
5454
});
5555
}
56-
57-
public function getProjectDir(): string
58-
{
59-
return sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension';
60-
}
6156
};
6257

6358
$kernel->boot();
@@ -73,6 +68,25 @@ public function getProjectDir(): string
7368
$twig->getRuntime(StaticExtensionWithAttributes::class);
7469
}
7570

71+
public function testInvalidExtensionClass()
72+
{
73+
$kernel = new class extends AttributeExtensionKernel {
74+
public function registerContainerConfiguration(LoaderInterface $loader): void
75+
{
76+
$loader->load(static function (ContainerBuilder $container) {
77+
$container->register(InvalidExtensionWithAttributes::class, InvalidExtensionWithAttributes::class)
78+
->setAutoconfigured(true);
79+
});
80+
}
81+
};
82+
83+
$this->expectException(LogicException::class);
84+
$this->expectExceptionMessage('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.');
85+
86+
$kernel->boot();
87+
}
88+
89+
7690
/**
7791
* @before
7892
* @after
@@ -85,6 +99,24 @@ protected function deleteTempDir()
8599
}
86100
}
87101

102+
abstract class AttributeExtensionKernel extends Kernel
103+
{
104+
public function __construct()
105+
{
106+
parent::__construct('test', true);
107+
}
108+
109+
public function registerBundles(): iterable
110+
{
111+
return [new FrameworkBundle(), new TwigBundle()];
112+
}
113+
114+
public function getProjectDir(): string
115+
{
116+
return sys_get_temp_dir().'/'.Kernel::VERSION.'/AttributeExtension';
117+
}
118+
}
119+
88120
class StaticExtensionWithAttributes
89121
{
90122
#[AsTwigFilter('foo')]
@@ -112,10 +144,19 @@ public function __construct(private bool $prefix)
112144
{
113145
}
114146

115-
#[AsTwigFilter('foo')]
116-
#[AsTwigFunction('foo')]
147+
#[AsTwigFilter('prefix_foo')]
148+
#[AsTwigFunction('prefix_foo')]
117149
public function prefix(string $value): string
118150
{
119151
return $this->prefix.$value;
120152
}
121153
}
154+
155+
class InvalidExtensionWithAttributes extends AbstractExtension
156+
{
157+
#[AsTwigFilter('fun')]
158+
public function funFilter(): string
159+
{
160+
return 'fun';
161+
}
162+
}

0 commit comments

Comments
 (0)
0