Twig templates
Twig is Symfony's template language. It has no PHP code: only markup and short constructs for printing data. Templates live in the templates folder and are named name.html.twig.
Printing
Double curly braces print a value:
<h1>{{ title }}</h1>
<p>Author: {{ post.author.name }}</p>The dot works for arrays and objects alike: post.title calls the getTitle() method or reads the title key.
All output is escaped automatically: a <script> from user data turns into harmless text.
Conditions
{% if posts is empty %}
<p>No posts yet.</p>
{% elseif posts|length == 1 %}
<p>One post.</p>
{% else %}
<p>Posts: {{ posts|length }}</p>
{% endif %}Constructs that do something are written in {% %}, output in {{ }}.
Loops
<ul>
{% for post in posts %}
<li>{{ loop.index }}. {{ post.title }}</li>
{% else %}
<li>Nothing found</li>
{% endfor %}
</ul>The else branch runs when the list is empty. The loop variable holds the step: loop.index, loop.first, loop.last.
Filters
A filter transforms a value and is written after a vertical bar:
{{ post.title|upper }}
{{ post.createdAt|date('Y-m-d') }}
{{ post.body|slice(0, 100) }}…
{{ user.nickname|default('Guest') }}
{{ price|number_format(2, '.', ',') }}Inheritance
The common page frame is described once — in a base template with blocks:
{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Blog{% endblock %}</title>
</head>
<body>
<main>{% block body %}{% endblock %}</main>
</body>
</html>A page inherits the frame and fills the blocks:
{# templates/blog/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Posts{% endblock %}
{% block body %}
<h1>Posts</h1>
{% endblock %}Including parts
A repeated fragment goes into a separate file:
{% for post in posts %}
{{ include('blog/_card.html.twig', {post: post}) }}
{% endfor %}By convention such file names start with an underscore.
Links and files
<a href="{{ path('blog_show', {slug: post.slug}) }}">Read</a>
<img src="{{ asset('images/logo.png') }}" alt="Logo">path() builds an address from a route name, asset() a path to a file in the public folder.
Common mistake
The raw filter turns escaping off. If you print user-entered text through it, someone else's script gets onto the page. Apply raw only to markup you created yourself or cleaned on the server.
{{ comment.text|raw }} {# dangerous #}
{{ comment.text }} {# safe #}