diff --git a/_build/redirection_map b/_build/redirection_map index 082598a5081..7a7534a765f 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -256,7 +256,8 @@ /cookbook/session/proxy_examples /session/proxy_examples /cookbook/session/sessions_directory /session/sessions_directory /cookbook/symfony1 /introduction/symfony1 -/cookbook/templating/global_variables /templating/global_variables +/cookbook/templating/global_variables /templating#templating-global-variables +/templating/global_variables /templating#templating-global-variables /cookbook/templating/index /templating /cookbook/templating/namespaced_paths /templating/namespaced_paths /cookbook/templating/PHP /templating/PHP @@ -388,6 +389,9 @@ /quick_tour/the_view /quick_tour/flex_recipes /service_container/service_locators /service_container/service_subscribers_locators /templating/overriding /bundles/override +/templating/twig_extension /templates#templates-twig-extension +/templating/hinclude /templates#templates-hinclude +/templating/PHP /templates /security/custom_provider /security/user_provider /security/multiple_user_providers /security/user_provider /security/custom_password_authenticator /security/guard_authentication diff --git a/reference/configuration/framework.rst b/reference/configuration/framework.rst index ae42b5607cd..66e9d7f9f76 100644 --- a/reference/configuration/framework.rst +++ b/reference/configuration/framework.rst @@ -736,7 +736,7 @@ is disabled. This can be either a template name or the content itself. .. seealso:: - See :doc:`/templating/hinclude` for more information about hinclude. + See :ref:`templates-hinclude` for more information about hinclude. .. _reference-fragments-path: diff --git a/reference/configuration/twig.rst b/reference/configuration/twig.rst index 29b22fb3e28..d7a6e64916b 100644 --- a/reference/configuration/twig.rst +++ b/reference/configuration/twig.rst @@ -231,7 +231,7 @@ globals **type**: ``array`` **default**: ``[]`` It defines the global variables injected automatically into all Twig templates. -Learn more about :doc:`Twig global variables `. +Learn more about :ref:`Twig global variables `. number_format ~~~~~~~~~~~~~ diff --git a/reference/dic_tags.rst b/reference/dic_tags.rst index 6123c5c04d2..c1e2c902605 100644 --- a/reference/dic_tags.rst +++ b/reference/dic_tags.rst @@ -1287,7 +1287,7 @@ the service is auto-registered and auto-tagged. But, you can also register it ma For information on how to create the actual Twig Extension class, see `Twig's documentation`_ on the topic or read the -:doc:`/templating/twig_extension` article. +:ref:`templates-twig-extension` article. twig.loader ----------- diff --git a/reference/twig_reference.rst b/reference/twig_reference.rst index 5f89688991b..85c44bd57a3 100644 --- a/reference/twig_reference.rst +++ b/reference/twig_reference.rst @@ -12,7 +12,7 @@ components with Twig templates. This article explains them all. .. tip:: If these extensions provided by Symfony are not enough, you can - :doc:`create a custom Twig extension ` to define + :ref:`create a custom Twig extension ` to define even more filters and functions. .. _reference-twig-functions: diff --git a/security/csrf.rst b/security/csrf.rst index 12a00ef185c..5e659be9750 100644 --- a/security/csrf.rst +++ b/security/csrf.rst @@ -71,7 +71,7 @@ protected forms. As an alternative, you can: * Embed the form inside an uncached :doc:`ESI fragment ` and cache the rest of the page contents; * Cache the entire page and load the form via an uncached AJAX request; -* Cache the entire page and use :doc:`hinclude.js ` to +* Cache the entire page and use :ref:`hinclude.js ` to load the CSRF token with an uncached AJAX request and replace the form field value with it. diff --git a/templates.rst b/templates.rst index 280a5f9b6df..f61b0efd6d5 100644 --- a/templates.rst +++ b/templates.rst @@ -9,6 +9,10 @@ whether you need to render HTML from a :doc:`controller ` or genera the :doc:`contents of an email `. Templates in Symfony are created with Twig: a flexible, fast, and secure template engine. +.. caution:: + + Starting from Symfony 5.0, PHP templates are no longer supported. + .. _twig-language: Twig Templating Language @@ -56,7 +60,7 @@ being rendered, like the ``upper`` filter to uppercase contents: Twig comes with a long list of `tags`_, `filters`_ and `functions`_ that are available by default. In Symfony applications you can also use these :doc:`Twig filters and functions defined by Symfony ` -and you can :doc:`create your own Twig filters and functions `. +and you can :ref:`create your own Twig filters and functions `. Twig is fast in the ``prod`` :ref:`environment ` (because templates are compiled into PHP and cached automatically), but @@ -407,7 +411,125 @@ gives you access to these variables: object representing the security token. In addition to the global ``app`` variable injected by Symfony, you can also -:doc:`inject variables automatically to all Twig templates `. +inject variables automatically to all Twig templates as explained in the next +section. + +.. index:: + single: Templating; Global variables + +.. _templating-global-variables: + +Global Variables +~~~~~~~~~~~~~~~~ + +Twig allows you to automatically inject one or more variables into all +templates. These global variables are defined in the ``twig.globals`` option +inside the main Twig configuration file: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/twig.yaml + twig: + # ... + globals: + ga_tracking: 'UA-xxxxx-x' + + .. code-block:: xml + + + + + + + + UA-xxxxx-x + + + + .. code-block:: php + + // config/packages/twig.php + use Symfony\Config\TwigConfig; + + return static function (TwigConfig $twig) { + // ... + + $twig->global('ga_tracking')->value('UA-xxxxx-x'); + }; + +Now, the variable ``ga_tracking`` is available in all Twig templates, so you +can use it without having to pass it explicitly from the controller or service +that renders the template: + +.. code-block:: html+twig + +

The Google tracking code is: {{ ga_tracking }}

+ +In addition to static values, Twig global variables can also reference services +from the :doc:`service container `. The main drawback is +that these services are not loaded lazily. In other words, as soon as Twig is +loaded, your service is instantiated, even if you never use that global +variable. + +To define a service as a global Twig variable, prefix the service ID string +with the ``@`` character, which is the usual syntax to :ref:`refer to services +in container parameters `: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/twig.yaml + twig: + # ... + globals: + # the value is the service's id + uuid: '@App\Generator\UuidGenerator' + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // config/packages/twig.php + use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + use Symfony\Config\TwigConfig; + + return static function (TwigConfig $twig) { + // ... + + $twig->global('uuid')->value(service('App\Generator\UuidGenerator')); + }; + +Now you can use the ``uuid`` variable in any Twig template to access to the +``UuidGenerator`` service: + +.. code-block:: twig + + UUID: {{ uuid.generate }} Twig Components --------------- @@ -682,7 +804,7 @@ Inspecting Twig Information The ``debug:twig`` command lists all the information available about Twig (functions, filters, global variables, etc.). It's useful to check if your -:doc:`custom Twig extensions ` are working properly +:ref:`custom Twig extensions ` are working properly and also to check the Twig features added when :ref:`installing packages `: .. code-block:: terminal @@ -906,10 +1028,103 @@ template fragments. Configure that special URL in the ``fragments`` option: the application performance if you embed lots of controllers. If possible, :doc:`cache the template fragment `. -.. seealso:: +.. index:: + single: Templating; hinclude.js + +.. _templates-hinclude: + +How to Embed Asynchronous Content with hinclude.js +-------------------------------------------------- + +Templates can also embed contents asynchronously with the ``hinclude.js`` +JavaScript library. + +First, include the `hinclude.js`_ library in your page +:ref:`linking to it ` from the template or adding it +to your application JavaScript :doc:`using Webpack Encore `. + +As the embedded content comes from another page (or controller for that matter), +Symfony uses a version of the standard ``render()`` function to configure +``hinclude`` tags in templates: + +.. code-block:: twig + + {{ render_hinclude(controller('...')) }} + {{ render_hinclude(url('...')) }} - Templates can also :doc:`embed contents asynchronously ` - with the ``hinclude.js`` JavaScript library. +.. note:: + + When using the ``controller()`` function, you must also configure the + :ref:`fragments path option `. + +When JavaScript is disabled or it takes a long time to load you can display a +default content rendering some template: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/framework.yaml + framework: + # ... + fragments: + hinclude_default_template: hinclude.html.twig + + .. code-block:: xml + + + + + + + + + + + + .. code-block:: php + + // config/packages/framework.php + use Symfony\Config\FrameworkConfig; + + return static function (FrameworkConfig $framework) { + // ... + $framework->fragments() + ->hincludeDefaultTemplate('hinclude.html.twig') + ; + }; + +You can define default templates per ``render()`` function (which will override +any global default template that is defined): + +.. code-block:: twig + + {{ render_hinclude(controller('...'), { + default: 'default/content.html.twig' + }) }} + +Or you can also specify a string to display as the default content: + +.. code-block:: twig + + {{ render_hinclude(controller('...'), {default: 'Loading...'}) }} + +Use the ``attributes`` option to define the value of hinclude.js options: + +.. code-block:: twig + + {# by default, cross-site requests don't use credentials such as cookies, authorization + headers or TLS client certificates; set this option to 'true' to use them #} + {{ render_hinclude(controller('...'), {attributes: {'data-with-credentials': 'true'}}) }} + + {# by default, the JavaScript code included in the loaded contents is not run; + set this option to 'true' to run that JavaScript code #} + {{ render_hinclude(controller('...'), {attributes: {evaljs: 'true'}}) }} Template Inheritance and Layouts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1207,14 +1422,175 @@ you can refer to it as ``@AcmeFoo/user/profile.html.twig``. You can also :ref:`override bundle templates ` in case you want to change some parts of the original bundle templates. -Learn more ----------- +.. index:: + single: Twig extensions + +.. _templates-twig-extension: + +Writing a Twig Extension +------------------------ + +`Twig Extensions`_ allow the creation of custom functions, filters, and more to use +in your Twig templates. Before writing your own Twig extension, check if +the filter/function that you need is already implemented in: + +* The `default Twig filters and functions`_; +* The :doc:`Twig filters and functions added by Symfony `; +* The `official Twig extensions`_ related to strings, HTML, Markdown, internationalization, etc. + +Create the Extension Class +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Suppose you want to create a new filter called ``price`` that formats a number +as currency: + +.. code-block:: twig + + {{ product.price|price }} + + {# pass in the 3 optional arguments #} + {{ product.price|price(2, ',', '.') }} + +Create a class that extends ``AbstractExtension`` and fill in the logic:: + + // src/Twig/AppExtension.php + namespace App\Twig; + + use Twig\Extension\AbstractExtension; + use Twig\TwigFilter; + + class AppExtension extends AbstractExtension + { + public function getFilters() + { + return [ + new TwigFilter('price', [$this, 'formatPrice']), + ]; + } + + public function formatPrice($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') + { + $price = number_format($number, $decimals, $decPoint, $thousandsSep); + $price = '$'.$price; + + return $price; + } + } + +If you want to create a function instead of a filter, define the +``getFunctions()`` method:: + + // src/Twig/AppExtension.php + namespace App\Twig; + + use Twig\Extension\AbstractExtension; + use Twig\TwigFunction; + + class AppExtension extends AbstractExtension + { + public function getFunctions() + { + return [ + new TwigFunction('area', [$this, 'calculateArea']), + ]; + } + + public function calculateArea(int $width, int $length) + { + return $width * $length; + } + } + +.. tip:: + + Along with custom filters and functions, you can also register + `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.yaml configuration `, +you're done! Symfony will automatically know about your new service and add the tag. + +You can now start using your filter in any Twig template. Optionally, execute +this command to confirm that your new filter was successfully registered: + +.. code-block:: terminal -.. toctree:: - :maxdepth: 1 - :glob: + # display all information about Twig + $ php bin/console debug:twig + + # display only the information about a specific filter + $ php bin/console debug:twig --filter=price + +.. _lazy-loaded-twig-extensions: + +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. + +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 decoupling the extension definition from its +implementation. Following the same example as before, the first change would be +to remove the ``formatPrice()`` method from the extension and update the PHP +callable defined in ``getFilters()``:: + + // src/Twig/AppExtension.php + namespace App\Twig; + + use App\Twig\AppRuntime; + use Twig\Extension\AbstractExtension; + use Twig\TwigFilter; + + class AppExtension extends AbstractExtension + { + public function getFilters() + { + return [ + // the logic of this filter is now implemented in a different class + new TwigFilter('price', [AppRuntime::class, 'formatPrice']), + ]; + } + } + +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 ``formatPrice()`` method:: + + // src/Twig/AppRuntime.php + namespace App\Twig; + + use Twig\Extension\RuntimeExtensionInterface; + + class AppRuntime implements RuntimeExtensionInterface + { + 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 formatPrice($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') + { + $price = number_format($number, $decimals, $decPoint, $thousandsSep); + $price = '$'.$price; + + return $price; + } + } - /templating/* +If you're using the default ``services.yaml`` configuration, this will already +work! Otherwise, :ref:`create a service ` +for this class and :doc:`tag your service ` with ``twig.runtime``. .. _`Twig`: https://twig.symfony.com .. _`tags`: https://twig.symfony.com/doc/3.x/tags/index.html @@ -1231,3 +1607,8 @@ Learn more .. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions .. _`UX Twig Component`: https://symfony.com/bundles/ux-twig-component/current/index.html .. _`UX Live Component`: https://symfony.com/bundles/ux-live-component/current/index.html +.. _`Twig Extensions`: https://twig.symfony.com/doc/3.x/advanced.html#creating-an-extension +.. _`default Twig filters and functions`: https://twig.symfony.com/doc/3.x/#reference +.. _`official Twig extensions`: https://github.com/twigphp?q=extra +.. _`global variables`: https://twig.symfony.com/doc/3.x/advanced.html#id1 +.. _`hinclude.js`: http://mnot.github.io/hinclude/ diff --git a/templating/PHP.rst b/templating/PHP.rst deleted file mode 100644 index 9928984f8d8..00000000000 --- a/templating/PHP.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. index:: - single: PHP Templates - -How to Use PHP instead of Twig for Templates -============================================ - -.. caution:: - - Starting from Symfony 5.0, PHP templates are no longer supported. Use - :doc:`Twig ` instead to create your templates. diff --git a/templating/global_variables.rst b/templating/global_variables.rst deleted file mode 100644 index f38d06aca02..00000000000 --- a/templating/global_variables.rst +++ /dev/null @@ -1,116 +0,0 @@ -.. index:: - single: Templating; Global variables - -How to Inject Variables Automatically into all Templates -======================================================== - -Twig allows you to automatically inject one or more variables into all templates. -These global variables are defined in the ``twig.globals`` option inside the -main Twig configuration file: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/twig.yaml - twig: - # ... - globals: - ga_tracking: 'UA-xxxxx-x' - - .. code-block:: xml - - - - - - - - UA-xxxxx-x - - - - .. code-block:: php - - // config/packages/twig.php - use Symfony\Config\TwigConfig; - - return static function (TwigConfig $twig) { - // ... - - $twig->global('ga_tracking')->value('UA-xxxxx-x'); - }; - -Now, the variable ``ga_tracking`` is available in all Twig templates, so you -can use it without having to pass it explicitly from the controller or service -that renders the template: - -.. code-block:: html+twig - -

The Google tracking code is: {{ ga_tracking }}

- -Referencing Services --------------------- - -In addition to static values, Twig global variables can also reference services -from the :doc:`service container `. The main drawback is -that these services are not loaded lazily. In other words, as soon as Twig is -loaded, your service is instantiated, even if you never use that global variable. - -To define a service as a global Twig variable, prefix the service ID string with -the ``@`` character, which is the usual syntax to -:ref:`refer to services in container parameters `: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/twig.yaml - twig: - # ... - globals: - # the value is the service's id - uuid: '@App\Generator\UuidGenerator' - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // config/packages/twig.php - use function Symfony\Component\DependencyInjection\Loader\Configurator\service; - use Symfony\Config\TwigConfig; - - return static function (TwigConfig $twig) { - // ... - - $twig->global('uuid')->value(service('App\Generator\UuidGenerator')); - }; - -Now you can use the ``uuid`` variable in any Twig template to access to the -``UuidGenerator`` service: - -.. code-block:: twig - - UUID: {{ uuid.generate }} diff --git a/templating/hinclude.rst b/templating/hinclude.rst deleted file mode 100644 index 3a117148983..00000000000 --- a/templating/hinclude.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. index:: - single: Templating; hinclude.js - -How to Embed Asynchronous Content with hinclude.js -================================================== - -:ref:`Embedding controllers in templates ` is one -of the ways to reuse contents across multiple templates. To further improve -performance you can use the `hinclude.js`_ JavaScript library to embed -controllers asynchronously. - -First, include the `hinclude.js`_ library in your page -:ref:`linking to it ` from the template or adding it -to your application JavaScript :doc:`using Webpack Encore `. - -As the embedded content comes from another page (or controller for that matter), -Symfony uses a version of the standard ``render()`` function to configure -``hinclude`` tags in templates: - -.. code-block:: twig - - {{ render_hinclude(controller('...')) }} - {{ render_hinclude(url('...')) }} - -.. note:: - - When using the ``controller()`` function, you must also configure the - :ref:`fragments path option `. - -When JavaScript is disabled or it takes a long time to load you can display a -default content rendering some template: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/framework.yaml - framework: - # ... - fragments: - hinclude_default_template: hinclude.html.twig - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // config/packages/framework.php - use Symfony\Config\FrameworkConfig; - - return static function (FrameworkConfig $framework) { - // ... - $framework->fragments() - ->hincludeDefaultTemplate('hinclude.html.twig') - ; - }; - -You can define default templates per ``render()`` function (which will override -any global default template that is defined): - -.. code-block:: twig - - {{ render_hinclude(controller('...'), { - default: 'default/content.html.twig' - }) }} - -Or you can also specify a string to display as the default content: - -.. code-block:: twig - - {{ render_hinclude(controller('...'), {default: 'Loading...'}) }} - -Use the ``attributes`` option to define the value of hinclude.js options: - -.. code-block:: twig - - {# by default, cross-site requests don't use credentials such as cookies, authorization - headers or TLS client certificates; set this option to 'true' to use them #} - {{ render_hinclude(controller('...'), {attributes: {'data-with-credentials': 'true'}}) }} - - {# by default, the JavaScript code included in the loaded contents is not run; - set this option to 'true' to run that JavaScript code #} - {{ render_hinclude(controller('...'), {attributes: {evaljs: 'true'}}) }} - -.. _`hinclude.js`: http://mnot.github.io/hinclude/ diff --git a/templating/twig_extension.rst b/templating/twig_extension.rst deleted file mode 100644 index 3654fab4f6c..00000000000 --- a/templating/twig_extension.rst +++ /dev/null @@ -1,172 +0,0 @@ -.. index:: - single: Twig extensions - -How to Write a custom Twig Extension -==================================== - -`Twig Extensions`_ allow the creation of custom functions, filters, and more to use -in your Twig templates. Before writing your own Twig extension, check if -the filter/function that you need is already implemented in: - -* The `default Twig filters and functions`_; -* The :doc:`Twig filters and functions added by Symfony `; -* The `official Twig extensions`_ related to strings, HTML, Markdown, internationalization, etc. - -Create the Extension Class --------------------------- - -Suppose you want to create a new filter called ``price`` that formats a number -as currency: - -.. code-block:: twig - - {{ product.price|price }} - - {# pass in the 3 optional arguments #} - {{ product.price|price(2, ',', '.') }} - -Create a class that extends ``AbstractExtension`` and fill in the logic:: - - // src/Twig/AppExtension.php - namespace App\Twig; - - use Twig\Extension\AbstractExtension; - use Twig\TwigFilter; - - class AppExtension extends AbstractExtension - { - public function getFilters() - { - return [ - new TwigFilter('price', [$this, 'formatPrice']), - ]; - } - - public function formatPrice($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') - { - $price = number_format($number, $decimals, $decPoint, $thousandsSep); - $price = '$'.$price; - - return $price; - } - } - -If you want to create a function instead of a filter, define the -``getFunctions()`` method:: - - // src/Twig/AppExtension.php - namespace App\Twig; - - use Twig\Extension\AbstractExtension; - use Twig\TwigFunction; - - class AppExtension extends AbstractExtension - { - public function getFunctions() - { - return [ - new TwigFunction('area', [$this, 'calculateArea']), - ]; - } - - public function calculateArea(int $width, int $length) - { - return $width * $length; - } - } - -.. tip:: - - Along with custom filters and functions, you can also register - `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.yaml configuration `, -you're done! Symfony will automatically know about your new service and add the tag. - -You can now start using your filter in any Twig template. Optionally, execute -this command to confirm that your new filter was successfully registered: - -.. code-block:: terminal - - # display all information about Twig - $ php bin/console debug:twig - - # display only the information about a specific filter - $ php bin/console debug:twig --filter=price - -.. _lazy-loaded-twig-extensions: - -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. - -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 decoupling the extension definition from its -implementation. Following the same example as before, the first change would be -to remove the ``formatPrice()`` method from the extension and update the PHP -callable defined in ``getFilters()``:: - - // src/Twig/AppExtension.php - namespace App\Twig; - - use App\Twig\AppRuntime; - use Twig\Extension\AbstractExtension; - use Twig\TwigFilter; - - class AppExtension extends AbstractExtension - { - public function getFilters() - { - return [ - // the logic of this filter is now implemented in a different class - new TwigFilter('price', [AppRuntime::class, 'formatPrice']), - ]; - } - } - -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 ``formatPrice()`` method:: - - // src/Twig/AppRuntime.php - namespace App\Twig; - - use Twig\Extension\RuntimeExtensionInterface; - - class AppRuntime implements RuntimeExtensionInterface - { - 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 formatPrice($number, $decimals = 0, $decPoint = '.', $thousandsSep = ',') - { - $price = number_format($number, $decimals, $decPoint, $thousandsSep); - $price = '$'.$price; - - return $price; - } - } - -If you're using the default ``services.yaml`` configuration, this will already -work! Otherwise, :ref:`create a service ` -for this class and :doc:`tag your service ` with ``twig.runtime``. - -.. _`Twig Extensions`: https://twig.symfony.com/doc/3.x/advanced.html#creating-an-extension -.. _`default Twig filters and functions`: https://twig.symfony.com/doc/3.x/#reference -.. _`official Twig extensions`: https://github.com/twigphp?q=extra -.. _`global variables`: https://twig.symfony.com/doc/3.x/advanced.html#id1