Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 752b41d

Browse filesBrowse files
committed
[TwigBundle] Improve error when autoconfiguring a class with both ExtensionInterface and Twig callable attribute
1 parent 0b4d21c commit 752b41d
Copy full SHA for 752b41d

File tree

Expand file treeCollapse file tree

2 files changed

+72
-15
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+72
-15
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/AttributeExtensionPass.php
+11Lines 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()", 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/TwigBundle/Tests/Functional/AttributeExtensionTest.php
+61-15Lines changed: 61 additions & 15 deletions
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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.