-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
added runtime extensions #1913
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
added runtime extensions #1913
Conversation
dbe9df8
to
8a4bbbb
Compare
* | ||
* Loaders have priority over the "standard" getExtension(). | ||
*/ | ||
public function registerExtensionRuntimeLoader(Twig_RuntimeExtensionLoaderInterface $callable) |
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 variable name is weird. It is not a callable
how would this work to lazy-load extensions ? |
a4b51ce
to
76c1f04
Compare
eb3435e
to
a6324e5
Compare
Just done some renames. |
a6324e5
to
15767fa
Compare
@stof the goal is to lazy-load the runtime not the extension. An extension is just about declaring which functions/tests/tags/... are available, and they must not depend on any other dependencies. So, the dependencies are part of the runtime, and those can be lazy-loaded when using Symfony or Pimple very easily with some conventions. See the referenced Symfony PR for the implementation of the Form runtime. |
I like it :) but it is missing some tests. |
@fabpot how does it work for the definition of the callable in Twig_SimpleFilter ? Your Symfony PR would not work on Twig 2, where the callable is typehinted, as it is not callable anymore. And you are already loosing the support of named arguments in 1.x as the callable is not callable anymore. Please write tests for this feature in Twig, to see whether it works fine |
and shouldn't we have a way for extension to tell the compiler whether they use a runtime or no instead of asking loaders to try to provide one ? This would avoid the lookup in all loaders for each extension access, as most extensions won't use it. This could make things much faster. |
I know about the callable type hints we have in Twig 2.0, but I'm just going to remove them. |
@fabpot this will not solve the fact that we need the callable to access parameters through reflection |
To speed things up, I'm going to get the runtime only once per template by doing so when initializing the template and then reference a property, that should be enough. |
aa297c1
to
037abd2
Compare
I've just updated the PR which now is able to deal with named arguments. I still need to add some more tests and perhaps throw an exception when the runtime is different from the extension and the |
51fd01a
to
f8e251f
Compare
09b2785
to
600e08d
Compare
protected function compileCallable(Twig_Compiler $compiler) | ||
{ | ||
$closingParenthesis = false; | ||
if ($this->hasAttribute('callable') && $callable = $this->getAttribute('callable')) { | ||
if (is_string($callable)) { | ||
if (is_string($callable) && false === strpos($callable, '::')) { |
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 added that as some Twig tests define Twig elements as functions that do not exist. Not sure we want to support that, but that's why I've added this here and the code to correctly dump static methods below.
600e08d
to
05fae21
Compare
I prepared PR symfony/symfony#18268 for Symfony and created the https://github.com/symfonette/class-named-services library. |
Is there anything preventing to use service names (instead of class names)? All that would be very useful in many frameworks (working just like the way we can register controllers using service names). |
Also would it make sense to use container-interop's |
If I understood correctly, you're using the FQCN for lookups against this runtime loader. From the point of view of a Symfony developer, it would feel more natural and convenient if I could use service names instead of FQCN, for several reasons:
From Twig's POV, it should not really matter if the string that is used to look up a certain service is a FQCN or an arbitrary service ID or am I missing something? |
any valid PHP callable: | ||
|
||
* **functions/closures/static methods**: Simple to implement and fast (used by | ||
all Twig core extensions); but it is hard for the runtime to depend on |
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.
closures should be separated from other when you say it is fast
. Closures don't get compiled in the same way than functions or static methods. They are compiled in a way requiring to initialize extensions to access them (which adds more overhead than using a method of an extension)
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.
right
@@ -10,18 +10,29 @@ | |||
*/ | |||
abstract class Twig_Node_Expression_Call extends Twig_Node_Expression | |||
{ | |||
private $reflector; |
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.
do we really need to make it stateful ? Attributes are mutable, but you don't reset this property when mutating the callable
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.
They are mutable, but if done after the compilation, that won't change anything, and this property is only used when compilation starts. It's stateful as we need it twice. The only edge case would be a node that is re-used after a compilation, changed, and recompiled, not sure it that's a reasonable thing to do anyway.
if ($r->isStatic()) { | ||
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); | ||
} else { | ||
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); |
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.
at this point, do you have a guarantee that $callable[0]
does not contain a quote and does not end with a \
? If no, some escaping is necessary.
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.
Yes, as $callable[0]
is a class name at this point, but I will use the proper compiler method to avoid any surprise if we decide to change the class name the to something else.
@@ -136,6 +136,10 @@ Creating a filter is as simple as associating a name with a PHP callable:: | |||
// or a simple PHP function | |||
$filter = new Twig_SimpleFilter('rot13', 'str_rot13'); | |||
|
|||
// or a class static method | |||
$filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter')); |
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 looks you only need the static example one below. The above one is already added on line 144. Or did I miss something else ?
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 indeed the exact same notation but one would be a static method while the other one is not. I've added some notes to make it (I hope) more clear.
@derrabus @mnapoli One thing you might have missed is that getting an extension by name has been deprecated very recently (#2148). Extensions are now referenced by their class names only. So, re-adding a name for the runtime looks like going backward at this point. One "simple" way to get names out of class names would be to provide a map between the class name and the service name or just a default convention on the service name (a prefix + the class name in underscore form or something similar). |
5c3bc56
to
ac67a67
Compare
@fabpot what we could imagine in Symfony is forcing twig runtimes to be tagged with a specific tag, allowing to build a map of class names to service ids for runtimes. |
@stof Exactly, that's what I proposed when I wrote "provide a map between the class name and the service name", convention can be for Silex for instance. |
889637d
to
d328169
Compare
I've started to implement the Symfony counterpart of this PR, see the linked PR, but mostly symfony/symfony#20092 |
…ss than the extension they belong to
d328169
to
9cb6860
Compare
This PR was merged into the 1.x branch. Discussion ---------- added runtime extensions Commits ------- 9cb6860 allowed filters/functions/tests implementation to use a different class than the extension they belong to
This PR was merged into the 3.2-dev branch. Discussion ---------- added a Twig runtime loader | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | see twigphp/Twig#1913 | License | MIT | Doc PR | not yet This is the Symfony part of twigphp/Twig#1913. It introduces a Twig runtime loader. Commits ------- 7fc174b [TwigBundle] added a Twig runtime loader
Right but if I come back on what you said:
So there's a difference between referencing an extension (which is now done by class name), and referencing the callable for an element (functions/filters/tests). For example a problem we are experiencing in my team is that we have a Twig extension for the whole app that provides everything and anything (lots of filters, functions, etc.). We started with dependency injection in the With your change we could have a single So lazy-loading the Twig extension is one thing, but lazy-loading the implementation of the elements (function/filter/…) is another problem and is very interesting. And indeed there's no need for extension names (so #2148 is not conflicting), but there's a need for being able to use services instead of class names inside the callables ( I hope that makes sense. |
@mnapoli I think we agree on (almost) everything. The goal of this PR is indeed to lazy-load the runtimes, not the extensions which are now only about how to expand Twig (without the implementation). Using the class name is the best way to not consider how the runtimes are going to be loaded. Using a service name is exactly the same as giving a name to the extension. They already have a unique identifier, the class name. Anyway, have a look at my PRs on Symfony, everything works quite well as is. |
…m implementations (fabpot) This PR was merged into the 3.2-dev branch. Discussion ---------- Twig extensions refatoring to decouple definitions from implementations | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | see twigphp/Twig#1913 | License | MIT | Doc PR | not yet This PR tries to use the new Twig runtime loader to the Twig bridge extensions. Commits ------- b515702 fixed circular reference in Twig Form integration 4b5e412 removed on indirection c07fc58 [TwigBridge] decoupled Form extension definitions from its runtime
…m implementations (fabpot) This PR was merged into the 3.2-dev branch. Discussion ---------- Twig extensions refatoring to decouple definitions from implementations | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | see twigphp/Twig#1913 | License | MIT | Doc PR | not yet This PR tries to use the new Twig runtime loader to the Twig bridge extensions. Commits ------- b515702 fixed circular reference in Twig Form integration 4b5e412 removed on indirection c07fc58 [TwigBridge] decoupled Form extension definitions from its runtime
No description provided.