8000 [TwigBundle] Improve error when autoconfiguring a class with both `ExtensionInterface` and Twig callable attribute by GromNaN · Pull Request #60415 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[TwigBundle] Improve error when autoconfiguring a class with both ExtensionInterface and Twig callable attribute #60415

New i 8000 ssue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 14, 2025

Conversation

GromNaN
Copy link
Member
@GromNaN GromNaN commented May 13, 2025
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 [TwigBundle] Enable #[AsTwigFilter], #[AsTwigFunction] and #[AsTwigTest] attributes to configure runtime extensions #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.

@GromNaN GromNaN force-pushed the twig-attribute-error branch from a05c57b to 83f0f07 Compare May 14, 2025 07:26
Copy link
Member
@javiereguiluz javiereguiluz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot!

Copy link
Member
@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just minor cs stuff

@GromNaN GromNaN force-pushed the twig-attribute-error branch from 83f0f07 to 2abcc87 Compare May 14, 2025 09:02
@GromNaN
Copy link
Member Author
GromNaN commented May 14, 2025

minor cs stuff

Fixed.

$class = $reflector->getDeclaringClass();
if ($class->implementsInterface(ExtensionInterface::class)) {
if ($class->isSubclassOf(AbstractExtension::class)) {
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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));
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));

if ($class->isSubclassOf(AbstractExtension::class)) {
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));
}
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));
Copy link
< 8000 /details>
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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));
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));

Copy link
Member
@chalasr chalasr May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggested it in #60415 (comment) :) what's wrong with it?

use Twig\Extension\AttributeExtension;

class AttributeExtensionTest extends TestCase
{
public function testExtensionWithAttributes()
/** @beforeClass */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add the PHPUnit attribute, to be compatible with PHPUnit 11

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, attribute added.

I've also seen the pre condition attributes, but none of them can check a composer package version or if a class exists: https://docs.phpunit.de/en/10.5/writing-tests-for-phpunit.html#skipping-tests-using-attributes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks weird that they don't have a RequiresClass attribute (and probably RequiresInterface). It would look consistent with the other attributes they have.

< 8000 a class="d-inline-block" data-test-selector="pr-timeline-events-actor-avatar" data-hovercard-type="user" data-hovercard-url="/users/GromNaN/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/GromNaN">@GromNaN GromNaN force-pushed the twig-attribute-error branch from 2abcc87 to 752b41d Compare May 14, 2025 11:51
@nicolas-grekas
Copy link
Member

Thank you @GromNaN.

@nicolas-grekas nicolas-grekas merged commit 170b631 into symfony:7.3 May 14, 2025
10 of 11 checks passed
@GromNaN GromNaN deleted the twig-attribute-error branch May 14, 2025 13:21
@PhilETaylor
Copy link
Contributor
PhilETaylor commented May 14, 2025

Just came here while researching an error on cache:warmup after moving to AsTwigFilter

Twig\Environment::addExtension(): Argument #1 ($extension) must be of type Twig\Extension\ExtensionInterface, MyJoomla\Twig\Extension\Anon given, called in /app/var/build/ContainerN7Q6vq6/App_KernelProdContainer.php on line 20408

Where MyJoomla\Twig\Extension\Anon is just

class Anon
{
    private int $anonTagIndex = 0;

    public function __construct(
        private readonly Client $client,
    ) {
    }

    #[AsTwigFilter(name: 'anon')]
    public function anon(?string $string = '', string $type = ''): string
    {
// snip
}
}

Running BETA2 in production ;-) I cant replicate it locally in dev

@GromNaN
Copy link
Member Author
GromNaN commented May 14, 2025

@PhilETaylor it looks like a cache issue. If you updated the class without rebuilding the container, this error is expected. On production, the cache is not automatically rebuilt when a file is modified. You should run APP_ENV=prod bin/console cache:clear.

@PhilETaylor
Copy link
Contributor

prod is a docker container built from scratch each deploy, the build folder should be empty on deployment. I think my issue might be a shared redis cache... ignore me, I know Im at the bleeding edge here, and not looking for support, but hoping by testing the beta's to find real issues for you : )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants
0