-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
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
Create attributes AsTwigFilter
, AsTwigFunction
and AsTwigTest
to ease extension development
#3916
Conversation
AsTwigFilter
, AsTwigFunction
and AsTwigTest
to improve extension development
AsTwigFilter
, AsTwigFunction
and AsTwigTest
to improve extension developmentAsTwigFilter
, AsTwigFunction
and AsTwigTest
to ease extension development
I'll rework the implementation after reading discussions on symfony/symfony#50016 |
d505321
to
e64a54b
Compare
c627a38
to
4c1adc1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for review @fabpot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not an extensive review, just things I've spotted while reading the code quickly.
This PR was merged into the 3.x branch. Discussion ---------- Add `getLastModified()` to extensions Give to extensions the ability to set a last modification date for cache invalidation. ### Runtime Currently, the cache is not invalidated when the signature of a runtime method is modified. This is an issue for templates that use named arguments, as argument names have an impact on the generated class. With this change, extensions using runtime classes can compute a modification date by including the files on which they depend. By default, the `AbstractExtension` checks if there is a file for the runtime class with the same name of the This is the convention applied in [Symfony](https://github.com/symfony/symfony/tree/7.3/src/Symfony/Bridge/Twig/Extension) and Twig Extra: `MarkdownExtension` has `MarkdownRuntime`. ### Attribute Contributing to #3916. The extension class that will get the configuration from attributes will be able to track the classes having attributes to find the last modification date of all this classes. ### ~BC break~ ~In Twig 4.0, the method `getLastModified` will be added to `ExtensionInterface`. It is extremely rare to implement this interface without extending `AbstractExtension`. So adding this method to the interface shouldn't be a problem as the base class has an implementation.~ Commits ------- d8fe3bd Add `LastModifiedExtensionInterface` and implementation in `AbstractExtension` to track modification of runtime classes
e87c1e6
to
60c96db
Compare
I refactored once again to get 1 instance of Also, I removed the ability to pass an instance of an object to The doc is up-to-date on how to use it standalone. The Symfony implementation need to be updated. |
git rebase FTW ;) |
eb7fd52
to
daa12f0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The state of my reflection is that we would want a different implementation in Symfony TwigBundle that reads the attributes during container compilation and inject a pre-compiled instance of AttributeExtension
.
This can be done in 2 ways:
- by serializing the
AttributeExtension
(I'm not sure for the performances), - or by exposing an API to convert attributes to TwigCallable. Maybe creating a public method on the attribute classes
AsTwigFilter::getTwigCallable(\ReflectionFunctionAbstract $reflection): TwigFilter
if ($extension instanceof AttributeExtension) { | ||
$class = $extension->getClass(); | ||
} else { | ||
$class = $extension::class; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for cache invalidation when an extension is added/removed.
Instead of relying on this class check, I can introduce an interface: DelegatingExtensionInterface
with the getClass
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this!
} | ||
|
||
If you want to access the current environment instance in your filter or function, | ||
add the ``Twig\Environment`` type to the first argument of the method:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this automatically add the needsEnvironment
option? What about needsContext
? Is adding array $context
enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The detection of the context is less obvious (no specific type).
And it's very rarely used: dump
is the only that uses it in all the Twig extra, Symfony and UX packages. Leaving it explicit seems to me to be more safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, just auto-detecting needsEnvironment
seems best for these reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did the detection initially: https://github.com/twigphp/Twig/blob/c5c00ed6c64ea2470f8a0dcb4c1fc18682f8fa5c/src/Extension/Extension.php
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused. needsEnvironment
is auto-detected, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needsEnvironment
is auto-detected - needsContext
is not.
* | ||
* @author Jérôme Tamarelle <[email protected]> | ||
*/ | ||
final class AttributeExtension extends AbstractExtension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be possible to extend this class to create an extension without runtime dependency. This is interesting to simplify standalone usage only, as Symfony will abstract the runtime declaration part.
class CustomExtension extends AttributeExtension
{
public function __construct()
{
// This can be set automatically for child classes
parent::__construct(self::class);
}
#[AsTwigFunction('foo')]
public function foo(): string
{
return 'foo';
}
}
Reflection for a subsequent PR.
…o ease extension development
Thank you @GromNaN. |
…on]` and `#[AsTwigTest]` attributes to configure runtime extensions (GromNaN) This PR was squashed before being merged into the 7.3 branch. Discussion ---------- [TwigBundle] Enable `#[AsTwigFilter]`, `#[AsTwigFunction]` and `#[AsTwigTest]` attributes to configure runtime extensions | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #50016 | License | MIT Integration for new PHP attributes introduced by twigphp/Twig#3916. ~We use the existing empty interface `Twig\Extension\RuntimeExtensionInterface` to identify services that are registered as Twig runtime and could define filters/functions/tests using the attributes.~ Using attribute autoconfiguration of methods. ~There is still an issue with cache invalidation when the runtime class is modified.~ Fixed by twigphp/Twig#3916 Commits ------- c8780d1 [TwigBundle] Enable `#[AsTwigFilter]`, `#[AsTwigFunction]` and `#[AsTwigTest]` attributes to configure runtime extensions
Documentation: Using PHP Attributes to define Extensions |
One drawback to writing extensions at present is that the declaration of functions/filters/tests is not directly adjacent to the methods. It's worse for runtime extensions because they need to be in 2 different classes. See
SerializerExtension
andSerializerRuntime
as an example.By using attributes for filters, functions and tests definition, we can make writing extensions more expressive, and use reflection to detect particular options (
needs_environment
,needs_context
,is_variadic
).Example if we implemented the
formatDate
filter:Twig/extra/intl-extra/IntlExtension.php
Lines 392 to 395 in aeeec9a
By using the
AsTwigFilter
attribute, it is not necessary to create thegetFilters()
method. Theneeds_environment
option is detected from method signature. The name is still required as the method naming convention (camelCase) doesn't match with Twig naming convention (snake_case).This approach does not totally replace the current definition of extensions, which is still necessary for advanced needs. It does, however, make for more pleasant reading and writing.
This makes writing lazy-loaded runtime extension the easiest way to create Twig extension in Symfony: symfony/symfony#52748
Related to symfony/symfony#50016
Is there any need to cache the parsing of method attributes? They are only read at compile time, but that can have a performance impact during development or when using dynamic templates.