Skip to main content

Template Inheritance

Twig provides powerful mechanisms for organizing and reusing templates.

Master templates and overrides​

Master templates ship with MMO-DEV WEB and are read-only on a production site. To customize a site or cabinet template, open it in the control panel, select a style, and create an override. The change applies only to that style and does not modify the product's master template.

An MMO-DEV WEB update may change the master template while preserving your override. Compatible changes are merged automatically. If the same lines changed in both the product and the override, the control panel reports a conflict for comparison and manual merging. Admin templates can only be changed during product development.

Inheritance (extends)​

Inheritance allows you to create a base layout and override its parts in child templates.

Base layout​

Base layout
{# @site/layout.html.twig #}
<!DOCTYPE html>
<html lang="{{ mw.lang }}">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ mw.projectName }}{% endblock %}</title>
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('css/style.css') }}">
{% endblock %}
</head>
<body>
{% block header %}
{% include '@site/partials/header.html.twig' %}
{% endblock %}

<main>
{% block content %}{% endblock %}
</main>

{% block footer %}
{% include '@site/partials/footer.html.twig' %}
{% endblock %}

{% block javascripts %}
<script src="{{ asset('js/app.js') }}"></script>
{% endblock %}
</body>
</html>

Child template​

Child template
{# @site/pages/news.html.twig #}
{% extends '@site/layout.html.twig' %}

{% block title %}News - {{ parent() }}{% endblock %}

{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('css/news.css') }}">
{% endblock %}

{% block content %}
<h1>News</h1>
{% for article in news %}
<article>
<h2>{{ article.title }}</h2>
<p>{{ article.summary }}</p>
</article>
{% endfor %}
{% endblock %}

parent()​

The parent() function returns the content of the block from the parent template:

parent()
{% block sidebar %}
{{ parent() }}
{# Append content to the parent block instead of replacing it #}
<div class="extra-widget">...</div>
{% endblock %}
tip

The {% extends %} tag must be the first tag in the template. Any text before it will cause an error.

Including templates (include)​

Basic inclusion​

Basic inclusion
{% include '@common/components/alert.html.twig' %}

{# Passing additional variables #}
{% include '@common/components/card.html.twig' with {
title: 'Title',
body: 'Content'
} %}

{# Only the specified variables (without access to the parent context) #}
{% include '@common/components/card.html.twig' with {
title: 'Title'
} only %}

Conditional inclusion​

Conditional inclusion
{# Ignore error if the template is not found #}
{% include '@site/partials/sidebar.html.twig' ignore missing %}

{# Try multiple templates #}
{% include [
'@site/pages/' ~ page ~ '.html.twig',
'@site/pages/default.html.twig'
] %}

Inline inclusion (embed)​

embed is a combination of include and extends. It allows you to include a template and override its blocks:

Inline inclusion (embed)
{# Component @common/components/modal.html.twig #}
<div class="modal">
<div class="modal-header">
{% block header %}Title{% endblock %}
</div>
<div class="modal-body">
{% block body %}{% endblock %}
</div>
<div class="modal-footer">
{% block footer %}
<button class="btn">Close</button>
{% endblock %}
</div>
</div>
Inline inclusion (embed)
{# Using embed #}
{% embed '@common/components/modal.html.twig' %}
{% block header %}Delete confirmation{% endblock %}
{% block body %}
<p>Are you sure you want to delete this item?</p>
{% endblock %}
{% block footer %}
<button class="btn btn-danger">Delete</button>
<button class="btn">Cancel</button>
{% endblock %}
{% endembed %}

Horizontal reuse (use)​

use imports blocks from another template (similar to traits in PHP):

Horizontal reuse (use)
{# blocks/forms.html.twig #}
{% block input %}
<input type="text" name="{{ name }}" value="{{ value }}">
{% endblock %}

{% block textarea %}
<textarea name="{{ name }}">{{ value }}</textarea>
{% endblock %}
Horizontal reuse (use)
{# Importing blocks #}
{% use 'blocks/forms.html.twig' %}

{# Now the input and textarea blocks are available #}
{{ block('input') }}

{# Renaming in case of name conflicts #}
{% use 'blocks/forms.html.twig' with input as form_input %}

Macros (macro)​

Macros are reusable template fragments, similar to functions:

Definition​

Definition
{# @common/macros/forms.html.twig #}
{% macro input(name, value, type, attrs) %}
{% set type = type|default('text') %}
{% set attrs = attrs|default({}) %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}"
{% for attr, val in attrs %} {{ attr }}="{{ val }}"{% endfor %}
>
{% endmacro %}

{% macro select(name, options, selected) %}
<select name="{{ name }}">
{% for value, label in options %}
<option value="{{ value }}"
{% if value == selected %} selected{% endif %}
>{{ label }}</option>
{% endfor %}
</select>
{% endmacro %}

Import and usage​

Import and usage
{# Import all macros #}
{% import '@common/macros/forms.html.twig' as forms %}

{{ forms.input('email', '', 'email', {class: 'form-control', required: 'required'}) }}
{{ forms.select('country', {ru: 'Russia', us: 'USA'}, 'ru') }}
Import and usage
{# Import specific macros #}
{% from '@common/macros/forms.html.twig' import input, select %}

{{ input('username', user.name) }}
{{ select('role', roles, user.role) }}

Macros in the same file​

Macros in the same file
{# Definition and usage in the same template #}
{% macro badge(text, type) %}
<span class="badge badge-{{ type|default('info') }}">{{ text }}</span>
{% endmacro %}

{# Call via _self (deprecated method) or import #}
{% import _self as self %}
{{ self.badge('New', 'success') }}

Summary table​

MechanismPurposeBlock overriding
extendsLayout inheritanceYes
includeTemplate insertionNo
embedInsertion with overridingYes
useBlock import (traits)No (renaming)
macroReusable functionsNo