Lima episode sudah kita lalui bersama di seri CodeIgniter 4: install dan konsep dasar (#68), routing controller view (#69), model dan query builder (#70), serta validasi dan session (#71). Setiap episode memberi satu keping puzzle. Hari ini kita merangkainya menjadi gambar utuh: aplikasi pendataan menu kantin yang lengkap dan bisa dipakai beneran.
Kenapa proyek, bukan satu tutorial lagi? Karena pengetahuan yang terpisah-pisah itu rapuh. Kamu bisa paham routing hari Senin dan lupa hari Kamis kalau tidak pernah melihatnya bekerja bersama komponen lain. Proyek memaksamu mengambil keputusan nyata: route apa saja yang dibutuhkan, data apa yang dikirim ke view mana, validasi apa yang dipasang di mana. Itulah kerja programmer sehari-hari.
Aplikasi yang kita bangun sederhana tapi lengkap: daftar menu kantin yang bisa ditambah, dilihat, diubah, dan dihapus, dengan validasi form dan pesan sukses. CRUD yang kamu pelajari di #64 akhirnya tampil sebagai aplikasi web utuh. Siapkan proyek kantin-online-mu dari episode #68, kita mulai.
Peta Proyek: Tujuh Langkah
Sebelum mengetik, petakan dulu pekerjaannya supaya tidak tersesat di tengah jalan:
- Migration: buat tabel
menuyang rapi dan bisa diulang. - Model:
MenuModelsebagai pintu database. - Route: daftarkan URL untuk tiap aksi.
- Controller:
Menudengan enam method (index, tambah, simpan, edit, update, hapus). - View: halaman daftar dan form tambah/edit.
- Validasi dan flashdata di setiap aksi tulis.
- Uji alur lengkapnya dari browser.
Urutan ini disengaja: dari data ke tampilan, dari fondasi ke atap. Kalau migration-nya beres, model tinggal mengikuti. Kalau route-nya jelas, controller tahu method apa yang harus ada.
Langkah 1: Migration untuk Tabel Menu
Jangan buat tabel lewat phpMyAdmin. Tulis migration supaya struktur database-mu tercatat sebagai kode yang bisa diulang di laptop lain:
php spark migrate:create create_menu_table
Buka file yang terbentuk di app/Database/Migrations dan isi method up():
public function up()
{
$this->forge->addField([
'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
'nama' => ['type' => 'VARCHAR', 'constraint' => 100],
'harga' => ['type' => 'INT'],
'kategori' => ['type' => 'VARCHAR', 'constraint' => 50],
]);
$this->forge->addPrimaryKey('id');
$this->forge->createTable('menu');
}
public function down()
{
$this->forge->dropTable('menu');
}
Jalankan dengan php spark migrate. Method down() adalah "tombol undo"-nya migration: kalau suatu hari struktur ini salah, php spark migrate:rollback akan menghapus tabelnya dengan rapi. Database yang dibangun dari migration bisa direproduksi siapa pun, kapan pun, itulah bedanya dengan klik-klik manual.
Langkah 2 dan 3: Model dan Route
Modelnya sudah kamu kenal dari #70:
<?php
namespace App\Models;
use CodeIgniter\Model;
class MenuModel extends Model
{
protected $table = 'menu';
protected $primaryKey = 'id';
protected $returnType = 'array';
protected $allowedFields = ['nama', 'harga', 'kategori'];
}
Route-nya daftarkan semua aksi sekaligus di app/Config/Routes.php:
$routes->get('menu', 'Menu::index');
$routes->get('menu/tambah', 'Menu::tambah');
$routes->post('menu/simpan', 'Menu::simpan');
$routes->get('menu/edit/(:num)', 'Menu::edit/$1');
$routes->post('menu/update/(:num)', 'Menu::update/$1');
$routes->get('menu/hapus/(:num)', 'Menu::hapus/$1');
Perhatikan pola verb-nya: get untuk menampilkan halaman dan form, post untuk aksi yang mengubah data. Ini bukan aturan ngawur, ini konvensi HTTP: GET seharusnya aman dan bisa diulang, POST untuk perubahan. Kebiasaan ini melindungimu dari bencana seperti mesin pencari yang "mengklik" link hapus saat mengindeks situsmu.
Langkah 4: Controller Menu
Ini bagian terbesarnya. Controller app/Controllers/Menu.php merangkum semua yang kamu pelajari di #69 sampai #71:
<?php
namespace App\Controllers;
use App\Models\MenuModel;
class Menu extends BaseController
{
public function index()
{
$model = new MenuModel();
return view('menu/index', [
'title' => 'Daftar Menu',
'menus' => $model->orderBy('nama', 'ASC')->findAll(),
]);
}
public function tambah()
{
return view('menu/form', ['title' => 'Tambah Menu']);
}
public function simpan()
{
$rules = [
'nama' => 'required|min_length[3]',
'harga' => 'required|numeric|greater_than[0]',
'kategori' => 'required',
];
if (! $this->validate($rules)) {
return redirect()->back()->withInput();
}
(new MenuModel())->insert([
'nama' => $this->request->getPost('nama'),
'harga' => $this->request->getPost('harga'),
'kategori' => $this->request->getPost('kategori'),
]);
session()->setFlashdata('sukses', 'Menu berhasil ditambahkan.');
return redirect()->to('/menu');
}
public function edit($id)
{
$menu = (new MenuModel())->find($id);
if (! $menu) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
return view('menu/form', ['title' => 'Edit Menu', 'menu' => $menu]);
}
public function update($id)
{
$rules = [
'nama' => 'required|min_length[3]',
'harga' => 'required|numeric|greater_than[0]',
'kategori' => 'required',
];
if (! $this->validate($rules)) {
return redirect()->back()->withInput();
}
(new MenuModel())->update($id, [
'nama' => $this->request->getPost('nama'),
'harga' => $this->request->getPost('harga'),
'kategori' => $this->request->getPost('kategori'),
]);
session()->setFlashdata('sukses', 'Menu berhasil diubah.');
return redirect()->to('/menu');
}
public function hapus($id)
{
(new MenuModel())->delete($id);
session()->setFlashdata('sukses', 'Menu berhasil dihapus.');
return redirect()->to('/menu');
}
}
Perhatikan tiga keputusan desain di sini. Pertama, simpan() dan update() memakai rules validasi yang sama, karena syarat data yang baik tidak berubah antara tambah dan edit. Kedua, edit() melempar 404 kalau id tidak ditemukan, bukan menampilkan form kosong yang membingungkan. Ketiga, setiap aksi tulis diakhiri redirect, bukan return view().
Berhenti dan tebak dulu! Kenapa simpan() diakhiri redirect()->to('/menu'), bukan langsung return view('menu/index', ...)? Bukankah hasilnya sama-sama menampilkan daftar menu?
Langkah 5: View Daftar dan Form
View daftar (app/Views/menu/index.php): tabel data plus pesan flashdata.
<h1><?= esc($title) ?></h1>
<?php if (session()->getFlashdata('sukses')): ?>
<p><?= esc(session()->getFlashdata('sukses')) ?></p>
<?php endif ?>
<p><a href="/menu/tambah">Tambah Menu</a></p>
<table border="1">
<tr><th>Nama</th><th>Harga</th><th>Kategori</th><th>Aksi</th></tr>
<?php foreach ($menus as $m): ?>
<tr>
<td><?= esc($m['nama']) ?></td>
<td><?= esc($m['harga']) ?></td>
<td><?= esc($m['kategori']) ?></td>
<td>
<a href="/menu/edit/<?= $m['id'] ?>">Edit</a>
<a href="/menu/hapus/<?= $m['id'] ?>" onclick="return confirm('Yakin hapus?')">Hapus</a>
</td>
</tr>
<?php endforeach ?>
</table>
View form (app/Views/menu/form.php) dipakai bersama untuk tambah dan edit. Triknya: kalau variabel $menu ada, form mengarah ke update dan field terisi data lama.
<h1><?= esc($title) ?></h1>
<?= service('validation')->listErrors() ?>
<form method="post" action="<?= isset($menu) ? '/menu/update/'.$menu['id'] : '/menu/simpan' ?>">
<?= csrf_field() ?>
<p>Nama: <input name="nama" value="<?= old('nama', $menu['nama'] ?? '') ?>"></p>
<p>Harga: <input name="harga" value="<?= old('harga', $menu['harga'] ?? '') ?>"></p>
<p>Kategori: <input name="kategori" value="<?= old('kategori', $menu['kategori'] ?? '') ?>"></p>
<p><button>Simpan</button></p>
</form>
Berhenti dan tebak dulu! Pada input nama tertulis old('nama', $menu['nama'] ?? ''). Kapan masing-masing nilai dipakai?
Dua hal yang perlu diluruskan. Mitos pertama: "proyek latihan tidak perlu validasi, nanti saja". Justru di proyek latihan kebiasaan dibentuk. Menambahkan validasi sejak awal hanya butuh lima menit, menambalnya setelah aplikasi dipakai jauh lebih mahal. Mitos kedua: "link hapus pakai GET tidak apa-apa untuk latihan". Secara teknis jalan, tapi biasakan pola pikir yang benar: GET tidak boleh mengubah data. Untuk proyek ini, konfirmasi JavaScript confirm() adalah pagar minimal. Nanti saat sudah mahir, ganti dengan form POST kecil per baris.
Kuis Cepat
Kuis cepat: Setelah simpan() berhasil, pengguna menekan refresh di browser. Apa yang terjadi dengan pola redirect di atas?
Langkah 6 dan 7: Uji dan Kembangkan
Jalankan php spark serve dan uji alur lengkapnya dari browser:
- Buka
/menu, pastikan daftar tampil (mungkin masih kosong). - Tambah menu baru. Coba juga submit form kosong dan pastikan validasi menolak dengan pesan error.
- Edit salah satu menu, ubah harganya, simpan.
- Hapus satu menu dengan konfirmasi.
- Tekan refresh setelah tiap aksi tulis, pastikan tidak ada data ganda.
Kalau semua lolos, selamat: kamu baru saja membangun aplikasi CRUD lengkap dengan framework. Sekarang kembangkan sendiri, karena proyek yang berhenti di tutorial tidak akan bertahan di kepalamu:
- Ubah: tambahkan kolom
stoklewat migration baru (jangan edit migration lama yang sudah jalan). - Tambah: fitur pencarian nama menu di halaman daftar, memakai
like()di model. - Tantangan: tambahkan login sederhana dari #71, dan lindungi semua route
/menu/*supaya hanya bisa diakses setelah login. (Petunjuk: cek session di constructor atau pakai filter.)
Dengan ini seri CodeIgniter 4 resmi tamat (#68-72). Kamu sekarang memegang pola MVC yang utuh. Di episode #73 kita membuka seri baru: Kenalan dengan Laravel dan Cara Installnya. Framework yang lebih besar, lebih kaya fitur, dan konsepnya akan terasa familiar karena fondasi CodeIgniter-mu sudah kokoh.
Diskusi & Komentar 0
Punya tanggapan, pertanyaan, atau tips seputar tutorial ini? Tulis komentar kamu di bawah!
Jadilah yang pertama membuka obrolan dan berbagi pandangan seputar tutorial resep kode ini.
Tulis Komentar
Komentar menggunakan akun Google. Harap berkomentar dengan santun dan berbobot.