From e4e32eaed7b2a98ac3961f21ea3b4c00275062c7 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 5 Feb 2018 10:22:45 +0100 Subject: [PATCH 1/3] Documented how to create lazy-loaded Twig extensions --- templating/twig_extension.rst | 99 ++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/templating/twig_extension.rst b/templating/twig_extension.rst index 4a684718d51..7ced8c56030 100644 --- a/templating/twig_extension.rst +++ b/templating/twig_extension.rst @@ -58,7 +58,7 @@ Create a class that extends ``\Twig_Extension`` and fill in the logic:: `global variables`_. Register an Extension as a Service ----------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Next, register your class as a service and tag it with ``twig.extension``. If you're using the :ref:`default services.yml configuration `, @@ -66,6 +66,103 @@ you're done! Symfony will automatically know about your new service and add the You can now start using your filter in any Twig template. +Creating Lazy-Loaded Twig Extensions +------------------------------------ + +Including the code of the custom filters/functions in the Twig extension class +is the simplest way to create extensions. However, Twig must initialize all +extensions before rendering any template, even if the template doesn't use an +extension. This means that performance can be slow if you define extensions with +lots of complex dependencies (e.g. those making database connections). + +That's why Twig allows to decouple the extension definition from its +implementation. Following the same example as before, the first change would be +to remove the ``priceFilter()`` method from the extension and update the PHP +callable defined in ``getFilters()``:: + + // src/AppBundle/Twig/AppExtension.php + namespace AppBundle\Twig; + + use AppBundle\Twig\AppRuntime; + + class AppExtension extends \Twig_Extension + { + public function getFilters() + { + return array( + // the logic of this filter is now implemented in a different class + new \Twig_SimpleFilter('price', array(AppRuntime::class, 'priceFilter')), + ); + } + } + +Then, create the new ``AppRuntime`` class (it's not required but these classes +are suffixed with ``Runtime`` by convention) and include the logic of the +previous ``priceFilter()`` method:: + + // src/AppBundle/Twig/AppRuntime.php + namespace AppBundle\Twig; + + class AppRuntime + { + // for complex Twig extensions you may need to define a constructor + // in this class to inject external services + + public function priceFilter($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') + { + $price = number_format($number, $decimals, $decPoint, $thousandsSep); + $price = '$'.$price; + + return $price; + } + } + +Register the Lazy-Loaded Extension as a Service +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Finally, register your new class as a service and tag it with ``twig.runtime`` +(and optionally inject any service needed by the Twig extension runtime): + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/services.yml + services: + app.twig_runtime: + class: AppBundle\Twig\AppRuntime + public: false + tags: + - { name: twig.runtime } + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // app/config/services.php + use AppBundle\Twig\AppExtension; + + $container + ->register('app.twig_runtime', AppRuntime::class) + ->setPublic(false) + ->addTag('twig.runtime'); + .. _`Twig extensions documentation`: http://twig.sensiolabs.org/doc/advanced.html#creating-an-extension .. _`global variables`: http://twig.sensiolabs.org/doc/advanced.html#id1 .. _`functions`: http://twig.sensiolabs.org/doc/advanced.html#id2 From 11dcc29c1c27251a50f17cf5769d6a065ca54dcb Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 5 Feb 2018 10:38:14 +0100 Subject: [PATCH 2/3] Added the missing versionadded directive --- templating/twig_extension.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templating/twig_extension.rst b/templating/twig_extension.rst index 7ced8c56030..5fef36fdb98 100644 --- a/templating/twig_extension.rst +++ b/templating/twig_extension.rst @@ -69,6 +69,9 @@ You can now start using your filter in any Twig template. Creating Lazy-Loaded Twig Extensions ------------------------------------ +.. versionadded:: 1.26 + Support for lazy-loaded extensions was introduced in Twig 1.26. + Including the code of the custom filters/functions in the Twig extension class is the simplest way to create extensions. However, Twig must initialize all extensions before rendering any template, even if the template doesn't use an From d123ce21786de8b9e4e87b94a23f53e590917573 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 22 Feb 2018 09:13:34 +0100 Subject: [PATCH 3/3] Rewords --- templating/twig_extension.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/templating/twig_extension.rst b/templating/twig_extension.rst index 5fef36fdb98..4b2772d4ffb 100644 --- a/templating/twig_extension.rst +++ b/templating/twig_extension.rst @@ -75,8 +75,12 @@ Creating Lazy-Loaded Twig Extensions Including the code of the custom filters/functions in the Twig extension class is the simplest way to create extensions. However, Twig must initialize all extensions before rendering any template, even if the template doesn't use an -extension. This means that performance can be slow if you define extensions with -lots of complex dependencies (e.g. those making database connections). +extension. + +If extensions don't define dependencies (i.e. if you don't inject services in +them) performance is not affected. However, if extensions define lots of complex +dependencies (e.g. those making database connections), the performance loss can +be significant. That's why Twig allows to decouple the extension definition from its implementation. Following the same example as before, the first change would be @@ -108,8 +112,11 @@ previous ``priceFilter()`` method:: class AppRuntime { - // for complex Twig extensions you may need to define a constructor - // in this class to inject external services + public function __construct() + { + // this simple example doesn't define any dependency, but in your own + // extensions, you'll need to inject services using this constructor + } public function priceFilter($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') {