Ilustrasi tiga simpul terhubung dari rute ke kontroler ke tampilan

Di episode #68 kemarin CodeIgniter 4 sudah terpasang dan menyala di localhost:8080. Sekarang pertanyaan serunya: bagaimana caranya membuat halaman sendiri? Bagaimana URL /menu bisa menampilkan daftar menu, dan URL /menu/3 menampilkan detail menu nomor 3?

Jawabannya melibatkan tiga komponen yang bekerja berantai: routing (penerjemah URL), controller (pengambil keputusan), dan view (penyaji tampilan). Tiga serangkai ini adalah jantung pola MVC, dan memahaminya di CodeIgniter berarti kamu memahami cara kerja hampir semua framework web di dunia.

Analoginya seperti restoran. Routing adalah pelayan yang menerima pesanan dan tahu pesanan itu untuk meja berapa. Controller adalah kepala koki yang memutuskan apa yang harus dimasak dan bahan apa yang disiapkan. View adalah hidangan yang disajikan ke meja, cantik dan siap santap. Pelayan tidak memasak, koki tidak mengantar, penyaji tidak memutuskan menu. Masing-masing punya peran, dan kekacauan dimulai saat peran itu dicampuraduk.

Alur Request: Perjalanan Satu Klik

Saat pengunjung membuka http://localhost:8080/menu, inilah yang terjadi di dalam CodeIgniter:

  1. Request masuk lewat public/index.php (front controller, seperti yang kamu tebak di episode #68).
  2. Framework membuka app/Config/Routes.php dan mencari aturan yang cocok dengan URL /menu.
  3. Aturan yang cocok menunjuk ke sebuah controller dan method, misalnya Menu::index.
  4. Controller bekerja: mengambil data, mengolahnya, lalu memanggil view.
  5. View merender HTML dan dikirim kembali ke browser.

Lima langkah ini terjadi dalam milidetik untuk setiap halaman. Kalau ada yang error, kamu sekarang tahu di langkah mana mencarinya: URL tidak ketemu berarti masalah routing, data tidak muncul berarti masalah controller, tampilan rusak berarti masalah view.

Routing: Penerjemah URL

Buka app/Config/Routes.php. Di dalamnya ada baris seperti ini:

$routes->get('/', 'Home::index');

Artinya: request GET ke URL / ditangani oleh method index() di controller Home. Untuk halaman daftar menu, tambahkan:

$routes->get('menu', 'Menu::index');
$routes->get('menu/(:num)', 'Menu::detail/$1');

Baris kedua memakai placeholder (:num) yang artinya "angka apa pun". Tulisannya $1 berarti "teruskan angka yang cocok itu sebagai parameter pertama ke method detail". Jadi URL /menu/3 akan memanggil Menu::detail(3).

Placeholder lain yang tersedia: (:segment) untuk satu segmen teks apa pun, (:any) untuk apa pun termasuk garis miring, dan (:alpha) untuk huruf saja. Pilih yang paling ketat sesuai kebutuhanmu: (:num) untuk ID jauh lebih aman daripada (:any).

Satu aturan penting: CodeIgniter membaca route dari atas ke bawah dan memakai yang pertama cocok. Jadi route yang spesifik harus ditulis di atas route yang umum. Kalau (:any) ditulis paling atas, ia akan "memakan" semua URL di bawahnya.

Berhenti dan tebak dulu! Dengan route di bawah ini, method apa yang dipanggil saat pengunjung membuka /menu/nasi-goreng?

$routes->get('menu/(:num)', 'Menu::detail/$1');
$routes->get('menu/(:segment)', 'Menu::cari/$1');
Jawaban: Menu::cari('nasi-goreng'). Route pertama gagal cocok karena nasi-goreng bukan angka, jadi CodeIgniter lanjut ke route kedua dan (:segment) menangkapnya. Inilah kenapa urutan route penting dan kenapa placeholder yang ketat (:num) sebaiknya didahulukan. Pola ini sangat umum: ID angka untuk detail, teks untuk pencarian.

Controller: Kepala Koki Aplikasi

Controller tinggal di app/Controllers dan biasanya extends BaseController. Tugasnya: menerima request, memutuskan apa yang harus dilakukan, dan mengembalikan respons. Contoh controller menu sederhana:

<?php
namespace App\Controllers;

class Menu extends BaseController
{
    public function index()
    {
        $data = [
            'title' => 'Daftar Menu',
            'menus' => ['Nasi Goreng', 'Mie Goreng', 'Es Teh'],
        ];
        return view('menu/index', $data);
    }

    public function detail($id)
    {
        return "Detail menu nomor $id";
    }
}

Perhatikan method index(): ia menyiapkan array $data, lalu mengembalikan hasil view(). Kata "mengembalikan" ini penting dan sering dilupakan pemula.

Berhenti dan tebak dulu! Apa yang terjadi kalau kamu menulis view('menu/index', $data); tanpa kata return di depannya?

public function index()
{
    $data = ['title' => 'Daftar Menu'];
    view('menu/index', $data); // tanpa return!
}
Jawaban: halaman tampil kosong. Fungsi view() hanya membuat string HTML, ia tidak menampilkannya sendiri. Tanpa return, string itu dibuat lalu dibuang sia-sia, dan controller mengembalikan null alias halaman kosong. Ini jebakan klasik migrasi dari PHP native yang terbiasa memakai echo. Di CodeIgniter 4, controller berkomunikasi lewat nilai kembalian, bukan lewat echo.

View: Wajah yang Disajikan

View adalah file PHP biasa di app/Views. Buat app/Views/menu/index.php:

<h1><?= esc($title) ?></h1>
<ul>
  <?php foreach ($menus as $m): ?>
    <li><?= esc($m) ?></li>
  <?php endforeach ?>
</ul>

Data dikirim dari controller lewat array asosiatif di parameter kedua view(): kunci 'title' otomatis menjadi variabel $title di view, kunci 'menus' menjadi $menus. Sederhana dan eksplisit, tidak ada sihir.

Fungsi esc() wajib diperhatikan. Ia membersihkan output supaya aman dari serangan XSS (cross-site scripting): kalau nama menu berisi kode JavaScript jahat kiriman pengguna iseng, esc() menetralkannya jadi teks biasa. Aturannya: setiap data yang berasal dari luar dan ditampilkan ke browser harus lewat esc(). Tanpa kecuali.

Supaya tidak menulis kerangka HTML berulang di setiap view, CodeIgniter punya sistem layout. Buat layout dasar sekali, lalu tiap view "menumpang" di dalamnya:

<!-- app/Views/layouts/main.php -->
<html><head><title><?= esc($title ?? 'Kantin Online') ?></title></head>
<body>
  <?= $this->renderSection('content') ?>
</body></html>

<!-- app/Views/menu/index.php -->
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
  <h1>Daftar Menu</h1>
<?= $this->endSection() ?>

Pola extend dan section ini mirip dengan template engine di framework lain, termasuk Blade di Laravel nanti. Sekali paham di sini, pindah ke Laravel terasa familiar.

Kapan layout layak dibuat? Begitu kamu punya lebih dari dua halaman yang berbagi kerangka yang sama: header, navigasi, dan footer yang identik. Tanpa layout, mengubah satu link navigasi berarti mengedit semua file view satu per satu. Dengan layout, cukup satu file. Ini penerapan prinsip DRY (don't repeat yourself) di lapisan tampilan, dan ia menghemat banyak waktu saat aplikasi mulai punya belasan halaman.

Dua hal yang perlu diluruskan sebelum lanjut. Mitos pertama: "URL harus sama dengan nama file". Itu pola pikir PHP native, di mana menu.php diakses lewat /menu.php. Di framework, routing memisahkan URL dari struktur file. URL /menu/3 tidak berarti ada file bernama 3, ia hanya pola yang diterjemahkan route. Kebebasan ini memungkinkan URL yang cantik dan aman.

Mitos kedua: "logika boleh ditulis di view biar cepat". Godaannya besar: tinggal selipkan query database di tengah HTML. Tapi view yang berisi logika bisnis adalah awal dari kode spaghetti yang tidak bisa diuji dan sulit diubah. Aturannya keras: view hanya menampilkan, controller memutuskan, model mengakses data. Model akan kita bahas tuntas di episode #70.

Kuis Cepat

Kuis cepat: Pengunjung membuka /produk/25 dan route-nya $routes->get('produk/(:num)', 'Produk::lihat/$1'). Apa yang terjadi?

Latihan: Halaman Menu Kantin Versimu

  1. Buat controller Menu dengan method index() dan detail($id) seperti contoh di atas.
  2. Daarkan route untuk /menu dan /menu/(:num) di app/Config/Routes.php. Pastikan route yang spesifik ditulis di atas yang umum.
  3. Buat view menu/index.php yang menampilkan daftar menu dari array, lengkap dengan esc() di setiap output.
  4. Ubah, jangan cuma salin: tambahkan layout layouts/main.php, lalu ubah view-mu memakai extend dan section. Refresh dan pastikan tampilannya tetap benar.
  5. Eksperimen: sengaja hapus kata return di controller, refresh browser, dan amati halaman kosongnya. Kembalikan lagi. Pengalaman langsung ini lebih menancap daripada sekadar membaca.
  6. Tantangan: buat route /cari/(:segment) yang menampilkan "Kamu mencari: ..." sesuai teks di URL. Coba buka /cari/nasi-goreng.

Di episode #70, Model dan Query Builder di CodeIgniter, rantai MVC kita lengkap: controller tidak lagi memakai array khayalan, tapi mengambil data beneran dari database lewat model. Ilmu SQL dari seri kemarin akhirnya tampil di panggung framework.