Menguasai Twig sebagai template engine: syntax output & statement, template inheritance dengan block/extends, filter & function bawaan, partials dan macros, objek global app, serta perkenalan UX Twig Components untuk membangun UI yang reusable.

Setelah di episode 4 controller mengembalikan response JSON, episode ini melengkapi sisi view dari MVC: Twig, template engine resmi Symfony. Di sinilah kalian mengubah data dari controller menjadi HTML yang manusiawi.
Mengapa Twig, bukan PHP template biasa? Twig didesain sebagai bahasa yang aman (auto-escaping bawaan, tidak ada eksekusi PHP sewenang-wenang) dan mudah dibaca (syntax yang lebih ringkas). Keamanan auto-escaping ini sangat penting — kita akan lihat kembali perannya dalam mencegah XSS di episode 18.
Twig memakai tiga pembatas:
| Pembatas | Fungsi |
|---|---|
{{ expr }} | Output — cetak hasil ekspresi |
{% statement %} | Statement — kontrol alur (if, for, set) |
{# comment #} | Komentar — tidak dirender |
Contoh cepat di dalam template:
<h1>{{ article.title }}</h1>
{% if article.published %}
<p>Dipublikasikan pada {{ article.createdAt|date('d M Y') }}</p>
{% else %}
<p>Draft — belum tampil.</p>
{% endif %}
{% for tag in article.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}Perhatikan |date('d M Y') — ini filter. Twig memakai pipe untuk transformasi output, mirip pipeline di shell.
Masalah klasik website: header, footer, dan navigasi yang sama di setiap halaman. Solusi Twig adalah inheritance — template anak mewarisi layout induk dan mengisi block tertentu:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Blog{% endblock %}</title>
</head>
<body>
<header>{% block header %}Logo & navigasi{% endblock %}</header>
<main>
{% block body %}{% endblock %}
</main>
<footer>{% block footer %}© 2026{% endblock %}</footer>
</body>
</html>{% extends 'base.html.twig' %}
{% block title %}Daftar Artikel{% endblock %}
{% block body %}
<h1>Daftar Artikel</h1>
<ul>
{% for article in articles %}
<li><a href="{{ path('app_article_show', {slug: article.slug}) }}">{{ article.title }}</a></li>
{% endfor %}
</ul>
{% endblock %}Inilah pola yang paling sering kalian lihat di project Symfony nyata: satu layout induk di base.html.twig, dan setiap halaman hanya mendefinisikan block yang berbeda. {{ path() }} adalah function bawaan untuk menghasilkan URL dari nama route — perhatikan penggunaan literal {slug: ...} di dalam pemanggilan function.
Twig menyediakan ratusan filter dan function. Yang paling sering dipakai:
{{ name|upper }} {# UPPERCASE #}
{{ article.body|striptags|u.truncate(120) }} {# ringkas teks #}
{{ price|number_format(2, ',', '.') }} {# 1.234,56 #}
{{ url|e }} {# escape HTML #}
{{ path('app_article_show', {slug: s}) }} {# URL dari route #}
{{ asset('build/app.js') }} {# URL file statis #}
{{ app.user ? app.user.email : 'anonym' }} {# user login #}app adalah global object yang menyediakan konteks: app.request, app.session, app.user, dan app.flashes. Ini cara template mengakses data di luar variabel yang dikirim controller.
Ketika blok kecil dipakai berulang, gunakan partial (include) atau macro (function template):
<article class="card">
<h2><a href="{{ path('app_article_show', {slug: article.slug}) }}">{{ article.title }}</a></h2>
<p>{{ article.excerpt }}</p>
</article>{% for article in articles %}
{{ include('partials/article-card.html.twig', {article: article}) }}
{% endfor %}Macro bekerja seperti function yang bisa dipanggil berkali-kali tanpa menulis ulang markup:
{% macro status_badge(label, ok) %}
<span class="badge {{ ok ? 'badge-ok' : 'badge-warn' }}">{{ label }}</span>
{% endmacro %}
{% import _self as ui %}
{{ ui.status_badge('Aktif', true) }}Twig memiliki integrasi form (kita bangun FormType-nya di episode 7). Ada dua level kontrol: cepat dan manual.
{{ form_start(form) }}
{{ form_row(form.title) }}
{{ form_row(form.body) }}
<button type="submit">Simpan</button>
{{ form_end(form) }}form_row() merender label + widget + error sekaligus; form_widget() hanya widget-nya. Untuk penyesuaian mendalam, tema form (form theme) bisa diganti per-form — topik yang kita selami di episode 7.
Kesalahan paling umum saat memakai Twig:
php bin/console debug:twig # daftar function & filter
php bin/console lint:twig templates # validasi syntax semua templatedump() di dalam template membantu inspeksi variabel:
{{ dump(article) }}Kombinasi debug:twig dan lint:twig menangkap mayoritas kesalahan sebelum halaman dibuka.
Warning
Jangan pernah memakai |raw pada data yang berasal dari user tanpa alasan kuat. Filter |raw mematikan auto-escaping dan membuka celah XSS. Kalau kalian butuh HTML dari user, sanitasi dulu di sisi server — kita bahas di episode 18.
Untuk project yang lebih modern, paket Symfony UX menghadirkan Twig Components: komponen PHP+template yang bisa dipakai seperti element HTML (<twig:LikeButton post="{post}" />). Ini menjembatani server-rendering dengan interaktivitas frontend, dan menjadi tren full-stack Symfony di 2026 — kita singgung kembali di episode 25.
Pada episode 5 ini, kalian telah menguasai dasar templating Twig.
Inti yang harus dibawa pulang:
{{ }} output, {% %} statement, {# #} komentar.extends + block) adalah pola utama layout yang reusable.|, function via path(), asset(), dan global app.include) untuk blok berulang, macro untuk logika tampilan.|raw tanpa alasan.Di episode 6 selanjutnya kita masuk ke lapisan model: Doctrine ORM & database — membuat entity, menggenerasi dan menjalankan migrasi, relasi antar entity, repository, serta data fixtures untuk mengisi data awal. Sampai jumpa di episode 6!