Menguasai routing berbasis attribute #[Route] dengan parameter path, requirement, dan pembatasan method, lalu mengenal controller berbasis AbstractController, tipe-tipe Response (JSON, redirect, file), serta dependency injection langsung ke method controller.

Setelah di episode 3 kalian memiliki project Symfony pertama yang berjalan, episode ini adalah langkah pertama membangun perilaku aplikasi: routing (menghubungkan URL ke kode) dan controller (kode yang menghasilkan response).
Routing adalah "pintu depan" aplikasi web — tanpa memahami cara kerjanya, mustahil men-debug mengapa sebuah URL mengembalikan 404 atau 500. Lebih dari itu, pola yang dipelajari di sini (attribute, parameter, response types) adalah bahasa dasar yang dipakai seluruh episode berikutnya, dari render Twig di episode 5 sampai API Platform di episode 13.
Gunakan MakerBundle untuk boilerplate cepat, lalu kita bedah hasilnya:
php bin/console make:controller ArticleControllerMaker menghasilkan class di src/Controller/ArticleController.php. Perhatikan atribut #[Route] dan pewarisan AbstractController:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ArticleController extends AbstractController
{
#[Route('/articles', name: 'app_articles')]
public function index(): Response
{
return $this->render('article/index.html.twig', [
'articles' => [],
]);
}
}Dua elemen penting:
#[Route('/articles', name: 'app_articles')] — memetakan URL /articles ke method index(), dengan nama app_articles sebagai identifier.AbstractController — memberi kalian helper seperti $this->render(), $this->redirectToRoute(), dan $this->json().Verifikasi dengan php bin/console debug:router — daftar route sekarang berisi app_articles.
Route statis jarang cukup. Tambahkan parameter dinamis:
#[Route('/articles/{slug}', name: 'app_article_show', requirements: ['slug' => '[a-z0-9-]+'])]
public function show(string $slug): Response
{
return $this->json(['slug' => $slug]);
}{slug} adalah placeholder yang otomatis di-inject sebagai argument method $slug. Requirement [a-z0-9-]+ membatasi karakter yang diterima — tanpa requirement, {slug} menerima apa saja; dengan requirement, URL yang tidak cocok otomatis menjadi 404.
| Fitur | Contoh | Efek |
|---|---|---|
| Parameter | {id} | Nilai dari URL di-inject ke argumen method |
| Requirement | requirements: ['id' => '\d+'] | Hanya angka yang cocok |
| Default value | {page} + defaults: ['page' => 1] | Nilai default jika parameter tidak ada |
| Method | methods: ['GET', 'POST'] | Batasi HTTP method |
Untuk parameter opsional, gabungkan default value dengan nullable type:
#[Route('/blog/{page}', name: 'app_blog', defaults: ['page' => 1], requirements: ['page' => '\d+'])]
public function blog(int $page): Response
{
return $this->json(['page' => $page]);
}URL /blog akan memakai page=1; /blog/5 memakai page=5.
Untuk mengelompokkan URL, taruh #[Route] di level class:
#[Route('/admin', name: 'app_admin_')]
class AdminController extends AbstractController
{
#[Route('/users', name: 'users')]
public function users(): Response
{
// menghasilkan route app_admin_users → URL /admin/users
}
}Ini menjaga konsistensi penamaan (app_admin_users) dan memudahkan pemindahan sekelompok endpoint nanti.
Controller boleh mengembalikan berbagai bentuk response — semuanya subclass Response:
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
// JSON untuk API
return $this->json(['status' => 'ok']);
// Redirect ke route lain (misal setelah form disubmit)
return $this->redirectToRoute('app_articles');
// Download file
return new BinaryFileResponse('/path/to/report.pdf');
// Response dengan status code khusus
return $this->json(['error' => 'not found'], Response::HTTP_NOT_FOUND);Aturan praktis: gunakan $this->json() untuk API, $this->render() untuk HTML, redirectToRoute() untuk pola PRG (Post/Redirect/Get), dan BinaryFileResponse untuk unduhan.
Warning
Jangan mengembalikan null atau data non-Response dari controller — Symfony akan melempar exception saat kernel mencoba mengirim hasil. Selalu kembalikan objek Response (atau subclass-nya) dari setiap method controller.
Controller bisa menerima service langsung sebagai argument method (bukan __construct):
use Psr\Log\LoggerInterface;
#[Route('/articles/{id}', name: 'app_article_show', requirements: ['id' => '\d+'])]
public function show(int $id, LoggerInterface $logger): Response
{
$logger->info("Viewing article #{id}", ['id' => $id]);
return $this->json(['id' => $id]);
}LoggerInterface otomatis disuntikkan karena container tahu implementasinya (autowiring). Kita bedah mekanisme lengkapnya di episode 8 — untuk sekarang, ingat polanya: type-hint sebuah service, dan Symfony yang mencarikannya.
Saat URL menghasilkan 404, prioritas pengecekan:
debug:router)./articles/abc dengan requirement \d+ → 404.POST ke route GET-only → 405 Method Not Allowed.php bin/console debug:router --show-controllers menampilkan route beserta controller yang dituju — senjata utama men-debug masalah routing.
Pada episode 4 ini, kalian telah menguasai routing & controller dasar.
Inti yang harus dibawa pulang:
#[Route]; name adalah identifier yang dipakai redirectToRoute().{param} di URL ter-inject ke argumen method; requirements memfilter nilai yang diterima.Response atau subclass-nya; kenali json(), render(), redirectToRoute(), BinaryFileResponse.Di episode 5 selanjutnya, kita membangun templating dengan Twig: syntax {{ }}, {% %}, template inheritance dengan block dan extends, filter & function bawaan, partials, serta dasar UX Twig Components. Sampai jumpa di episode 5!