Twig Basics
Twig Basics
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Time for our pages to look like REAL web pages. Twig is Symfony's template engine – safe, compact syntax instead of mixing raw PHP into HTML files.
Why a template engine at all?
PHP itself COULD be embedded directly in HTML (<?= $variable ?>) – Twig offers two decisive advantages over raw PHP: AUTOMATIC HTML escaping (protects against XSS by default, chapter 18 goes deeper) and a deliberately RESTRICTED syntax that keeps designers/frontend developers from accidentally writing complex business logic in templates.
Rendering a template from the controller
#[Route('/projects', name: 'project_index', methods: ['GET'])]
public function index(): Response
{
$projects = [
['id' => 1, 'name' => 'Website Relaunch'],
['id' => 2, 'name' => 'Mobile App'],
];
return $this->render('project/index.html.twig', [
'projects' => $projects,
]);
}render() (from AbstractController) takes the template path RELATIVE to templates/, plus an array – each key becomes a variable available in the template.
Outputting variables: {{ }}
<h1>Projects</h1>
<ul>
{% for project in projects %}
<li>{{ project.name }}</li>
{% endfor %}
</ul>{{ ... }} outputs a value (and auto-escapes it), {% ... %} controls LOGIC (loops, conditions) without outputting anything itself. project.name works EQUALLY for array keys ($project['name']) and object properties/getters ($project->getName()) – Twig automatically tries both access forms.
Conditions: {% if %}
{% if projects is empty %}
<p>No projects yet.</p>
{% else %}
<ul>
{% for project in projects %}
<li>{{ project.name }}</li>
{% endfor %}
</ul>
{% endif %}Important filters: transforming values
{{ project.name|upper }} {# UPPERCASE #}
{{ project.description|slice(0, 100) }} {# first 100 characters #}
{{ project.createdAt|date('Y-m-d') }} {# format a date #}
{{ project.description|default('No description') }} {# fallback for null/empty #}
{{ project.tags|join(', ') }} {# array to string #}Filters get appended with | and can be CHAINED: {{ project.name|lower|trim }} applies lower and then trim in sequence.
Comments in Twig
{# This comment does NOT end up in the rendered HTML #}The global app variable
Automatically available in EVERY template, without passing it explicitly from the controller:
app.user– the logged-in user, ornull(block 5).app.request– the current request object.app.flashes– flash messages, already seen in chapter 12.app.environment– the current environment (dev/prod/test).
Checking Twig syntax ahead of time
php bin/console lint:twig templates/Tipp: lint:twig checks ALL templates for syntax errors, WITHOUT actually calling the application – useful BEFORE every deployment (chapter 48), to catch Twig typos early instead of only on the first page load.