Set variable in parent scope in Twig
If you don't want to use the default()
filter (i.e., when you want to use the variable multiple times throughout your parent and child templates), you can actually define a block that contains your entire page in the parent template, and then nest your other blocks inside of that:
{# base.twig #}
{# Default page properties. You can override these in the `page` block of your child templates. #}
{% set page = page | default({}) | merge({
"title" : "My Default Title",
"description" : "Default description"
}) %}
{% block page %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ page.description }}">
<title>{{ page.title }}</title>
...
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
{% endblock %}
You can then override the page
variable in the page
block in your child template, by setting the value and then calling parent()
:
{# child.twig #}
{% extends "base.twig" %}
{% block page %}
{# By putting this in a special block, we ensure that it will be set AFTER the default values are set in the parent template,
but BEFORE the page itself is rendered. #}
{% set page = page | merge({
"title" : "Child Page",
"description" : "Welcome to the child page!"
}) %}
{{ parent() }}
{% endblock %}
{% block content %}
...
{% endblock %}
Note that in the parent template, we define the page
variable outside of the page
block, while in the child template we define it inside the page
block.
So, Twig will do the following:
- When it renders
child.twig
, it will start from the top ofbase.twig
, setting the default values for thepage
variable. - When it comes to the
page
block, it will see thatchild.twig
overrides that block. So, it will run thepage
block inchild.twig
instead. - Inside the
page
block inchild.twig
, it will set the new values for thepage
variable. It will then callparent()
, which tells it to go back tobase.twig
and render the parentpage
block. - It will then continue to render the page, substituting any additional blocks as defined in
child.twig
(in my example, it will render thecontent
block).
See a working example here. Do be aware that this can become more complicated when you start adding multiple layers of inheritance (e.g., grandchild templates).
base.twig
<title>{{ title|default('example.com') }} - My cool site</title>
child.twig
{% set title = 'ChildTitle' %}