Merender UI di server dengan template engine: memahami cara kerja Blade (Laravel) dan Twig (Symfony) mulai dari layout, component, partial, dan inheritance, hingga escaping otomatis output yang menjadi pertahanan pertama terhadap serangan XSS.

Setelah API dan autentikasi, saatnya menjawab pertanyaan "bagaimana pengguna melihat antarmuka?" Untuk aplikasi web tradisional, PHP merender HTML di server — dan di sinilah template engine berperan. Blade (Laravel) dan Twig (Symfony) adalah dua template engine paling populer, dan keduanya berbagi satu prinsip penting: memisahkan logika dari presentasi.
Mengapa penting? Di episode 1 kita melihat PHP bisa disisipkan langsung ke HTML. Masalahnya, tanpa disiplin, HTML berubah jadi kumpulan <?php ... ?> yang tak terbaca — dan yang lebih berbahaya, output yang tidak di-escape membuka pintu XSS. Template engine modern menyelesaikan keduanya dengan sintaks yang bersih dan escaping otomatis.
Bandingkan dua cara merender daftar pengguna:
<ul>
<?php foreach ($users as $u): ?>
<li>
<?php echo $u["nama"]; ?>
(<?php echo $u["email"]; ?>)
</li>
<?php endforeach; ?>
</ul><ul>
@foreach ($users as $u)
<li>{{ $u->nama }} ({{ $u->email }})</li>
@endforeach
</ul><ul>
{% for u in users %}
<li>{{ u.nama }} ({{ u.email }})</li>
{% endfor %}
</ul>Template engine memberi sintaks ekspresif dan — yang terpenting — {{ }} otomatis meng-escape output. Nilai $u->nama yang berisi <script>alert(1)</script> akan dirender sebagai teks polos, bukan dieksekusi. Ini pertahanan XSS baris pertama (lengkap di episode 17).
Blade menggunakan @extends dan @section untuk mewarisi layout:
<!DOCTYPE html>
<html lang="id">
<head>
<title>@yield("title") - Toko</title>
</head>
<body>
<header>@include("partials.navbar")</header>
<main>
@yield("content")
</main>
<footer>© {{ date("Y") }} Devvnull</footer>
</body>
</html>@extends("layouts.app")
@section("title", "Beranda")
@section("content")
<h1>Selamat datang, {{ $nama }}!</h1>
<p>Kami punya {{ $jumlahProduk }} produk tersedia.</p>
@endsectionKontroller tinggal mengembalikan view:
public function beranda()
{
return view("pages.beranda", [
"nama" => "Devvnull",
"jumlahProduk" => 42,
]);
}@include("partials.navbar")) — potongan yang di-include dengan data.@props(["type" => "info"])
<div class="alert alert-{{ $type }}" role="alert">
{{ $slot }}
</div><x-alert type="warning">
Stok produk akan segera habis!
</x-alert>Component adalah evolusi modern dari partial: lebih terstruktur, punya atribut props, dan mudah diuji.
{{ $nama }} {{-- escaped output --}}
{!! $htmlAman !!} {{-- tanpa escape: HATI-HATI --}}
@if ($stok > 0)
<p>Masih ada {{ $stok }}</p>
@else
<p>Stok habis</p>
@endif
@foreach ($produk as $p)
<li>{{ $p->nama }}</li>
@endforeach
{{-- @forelse, @switch, @auth/@guest, @error, dll --}}Aturan emas: hampir selalu pakai {{ }}. {!! !!} menonaktifkan escaping — hanya untuk HTML yang sudah dipastikan aman (misal dari CMS terpercaya yang di-sanitize), dan itu pun harus dikaji ulang.
Twig (Symfony) menggunakan sintaks berbeda tapi konsep sama:
{% extends "base.html.twig" %}
{% block body %}
<h1>Daftar Produk</h1>
<table>
<tr><th>Nama</th><th>Harga</th></tr>
{% for p in produk %}
<tr>
<td>{{ p.nama }}</td>
<td>{{ p.harga|number_format(0, ",", ".") }}</td>
</tr>
{% else %}
<tr><td colspan="2">Belum ada produk.</td></tr>
{% endfor %}
</table>
{% endblock %}Twig sangat menekankan keamanan dan keterbatasan: tidak ada cara langsung memanggil fungsi PHP sewenang-wenang (kecuali diizinkan via extension). Filter (|number_format, |upper, |date) adalah cara transformasi data. Twig juga autoescape secara default — sama seperti Blade.
| Aspek | Blade (Laravel) | Twig (Symfony) |
|---|---|---|
| Sintaks | @if, {{ }} | {% if %}, {{ }} |
| Filosofi | Kompilasi ke PHP, ekspresif | Isolasi kuat, terbatas |
| Component | Kelas + Blade, sangat matang | Twig components (lebih baru) |
| Autoescape | Ya ({{ }}) | Ya (default, ketat) |
| Cocok untuk | Aplikasi Laravel | Symfony, project non-Laravel |
Tip
Konsep inti identik: layout inheritance, partial, component, dan escaping otomatis. Kalian yang menguasai Blade akan paham Twig dalam hitungan jam dan sebaliknya. Fokus pada prinsip, bukan sintaksnya.
Ini inti keamanan templating. Aturan tiga lapis:
{{ }}/autoescape mengubah <script> menjadi <script> sehingga tidak dieksekusi browser.{{ $user->nama }} {{-- teks --}}
{{ $user->nama }} {{-- di dalam atribut: tetap safe di Blade --}}
<img src="{{ $url }}" alt="{{ $nama }}">
{{-- data untuk JavaScript: json_encode dulu --}}
<script>
const user = @json($user); {{-- Laravel --}}
</script>Danger
{!! !!} (Blade) dan |raw (Twig) menonaktifkan escaping. Memakainya untuk data pengguna sama dengan mengundang XSS. Batasi penggunaannya hanya untuk konten yang kalian kendalikan penuh dan sudah disanitasi.
{!! $input !!} tanpa sanitasi — XSS store; escape selalu di default.$user->orders memicu N+1 (episode 18); eager-load di controller.php artisan view:cache tidak meninggalkan versi lama saat deploy (episode 19).Inti yang harus dibawa pulang:
@extends/@section untuk layout, @include untuk partial, <x-component> untuk component.{% extends %}/{% block %}, filter |..., autoescape ketat.{{ }} — {!! !!}/|raw hanya untuk HTML yang dipastikan aman.Di episode 16 selanjutnya kita melihat ke dalam bahasa itu sendiri: PHP 8.5 & Fitur Terbaru — pipe operator |>, extension URI, Clone With, #[NoDiscard], array_first()/array_last(), penyempurnaan CLI, plus rekap fitur 8.3 dan 8.4 seperti property hooks dan typed class constants, hingga arah PHP 8.6.