Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Merged
merged 1 commit into from
Sep 29, 2016
Merged

added runtime extensions #1913

merged 1 commit into from
Sep 29, 2016

Conversation

fabpot
Copy link
Contributor

@fabpot fabpot commented Nov 10, 2015

No description provided.

@fabpot fabpot force-pushed the extensions-runtime branch from dbe9df8 to 8a4bbbb Compare November 10, 2015 08:36
*
* Loaders have priority over the "standard" getExtension().
*/
public function registerExtensionRuntimeLoader(Twig_RuntimeExtensionLoaderInterface $callable)
Copy link
Member

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

@stof
Copy link
Member

stof commented Nov 10, 2015

how would this work to lazy-load extensions ?

@fabpot fabpot force-pushed the extensions-runtime branch 2 times, most recently from a4b51ce to 76c1f04 Compare November 10, 2015 08:39
@fabpot fabpot force-pushed the extensions-runtime branch 3 times, most recently from eb3435e to a6324e5 Compare November 10, 2015 08:46
@fabpot
Copy link
Contributor Author

fabpot commented Nov 10, 2015

Just done some renames.

@fabpot fabpot force-pushed the extensions-runtime branch from a6324e5 to 15767fa Compare November 10, 2015 08:49
@fabpot
Copy link
Contributor Author

fabpot commented Nov 10, 2015

@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.

@sstok
Copy link

sstok commented Nov 10, 2015

I like it :) but it is missing some tests.

@stof
Copy link
Member

stof commented Nov 10, 2015

@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

@stof
Copy link
Member

stof commented Nov 10, 2015

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.

@fabpot
Copy link
Contributor Author

fabpot commented Nov 10, 2015

I know about the callable type hints we have in Twig 2.0, but I'm just going to remove them.

@stof
Copy link
Member

stof commented Nov 10, 2015

@fabpot this will not solve the fact that we need the callable to access parameters through reflection

@fabpot
Copy link
Contributor Author

fabpot commented Nov 10, 2015

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.

@fabpot fabpot force-pushed the extensions-runtime branch 5 times, most recently from aa297c1 to 037abd2 Compare November 12, 2015 07:06
@fabpot
Copy link
Contributor Author

fabpot commented Nov 12, 2015

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 runtime_class is not defined.

@fabpot fabpot force-pushed the extensions-runtime branch 2 times, most recently from 51fd01a to f8e251f Compare September 23, 2016 20:02
@fabpot fabpot force-pushed the extensions-runtime branch 4 times, most recently from 09b2785 to 600e08d Compare September 27, 2016 18:17
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, '::')) {
Copy link
Contributor Author

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.

@hason
Copy link
Contributor

hason commented Sep 28, 2016

@fabpot

we don't have a mechanism to retrieve a service by its class name

I prepared PR symfony/symfony#18268 for Symfony and created the https://github.com/symfonette/class-named-services library.

@mnapoli
Copy link

mnapoli commented Sep 29, 2016

The only "complexity" is how we are going to make it work with Silex and Symfony as we don't have a mechanism to retrieve a service by its class name :)

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).

@mnapoli
Copy link

mnapoli commented Sep 29, 2016

Also would it make sense to use container-interop's ContainerInterface? Or if you don't want to use that standard, would PSR-11 be a good candidate for that once/if it passes? I'm trying to see if that could be a good use case for it.

@derrabus
Copy link
Contributor

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:

  • With FQCN, I need a mechanism that resolves the FQCN to a service ID.
  • Using service IDs, I can rename/move a class without having to change the loader.
  • With service IDs, I can have multiple services using the same class with different configurations.
  • Furthermore, service IDs allow me to use aliases to change the implementation depending on configuration/environment.

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
Copy link
Member

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)

Copy link
Contributor Author

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;
Copy link
Member

@stof stof Sep 29, 2016

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

Copy link
Contributor Author

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]));
Copy link
Member

@stof stof Sep 29, 2016

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.

Copy link
Contributor Author

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'));
Copy link

@harikt harikt Sep 29, 2016

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 ?

Copy link
Contributor Author

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.

@fabpot
Copy link
Contributor Author

fabpot commented Sep 29, 2016

@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).

@fabpot fabpot force-pushed the extensions-runtime branch 2 times, most recently from 5c3bc56 to ac67a67 Compare September 29, 2016 16:35
@stof
Copy link
Member

stof commented Sep 29, 2016

@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.

@fabpot
Copy link
Contributor Author

fabpot commented Sep 29, 2016

@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.

@fabpot
Copy link
Contributor Author

fabpot commented Sep 29, 2016

I've started to implement the Symfony counterpart of this PR, see the linked PR, but mostly symfony/symfony#20092

@fabpot fabpot merged commit 9cb6860 into twigphp:1.x Sep 29, 2016
fabpot added a commit that referenced this pull request Sep 29, 2016
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
fabpot added a commit to symfony/symfony that referenced this pull request Sep 29, 2016
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
@mnapoli
Copy link

mnapoli commented Sep 29, 2016

@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.

Right but if I come back on what you said:

When defining a Twig element, you can now use a class different from the extension: array('ImplementationClass', 'method') or ImplementationClass::method'. Of course, Twig has no idea on how to create an instance of ImplementationClass, so that's the goal of the runtime loader, which most of the time is going to use a dependency injection container to do its work.

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 TwigExtension class but ended up with a clogged constructor and lots of services initialized for nothing. We could split the class in multiple extensions but all the services would still always be loaded.

With your change we could have a single TwigExtension declaring all the elements, yet have the implementations in separate classes (group in topics that make sense). Each of these classes could be a proper service (use dependency injection fully). With the new "loader" those function/filters/… implementations would only be instantiated when the function/filter/… is actually used, which is more efficient.

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 (new \Twig_SimpleFilter('price', ['app.price_formatter', 'formatPrice']),).

I hope that makes sense.

@fabpot
Copy link
Contributor Author

fabpot commented Sep 29, 2016

@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.

fabpot added a commit to symfony/symfony that referenced this pull request Oct 1, 2016
…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
symfony-splitter pushed a commit to symfony/twig-bundle that referenced this pull request Oct 1, 2016
…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
@fabpot fabpot deleted the extensions-runtime branch October 9, 2016 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

7 participants