LaravelControllerDasarPemula4 mnt baca

Controller: Memisahkan Logika dari Route

Pindahkan logika dari closure route ke controller yang rapi dan testable.

Analogi: Dari Koki yang Mengerjakan Semuanya ke Kepala Koki

Di warung kecil, satu koki mengerjakan semuanya: menerima pesanan, memasak, mencuci piring. Selama pelanggannya lima orang, oke. Saat pelanggannya lima puluh, dapurnya kacau: pesanan tertukar, piring menumpuk.

Closure di route seperti koki serba bisa itu: cocok untuk contoh kecil. Controller adalah kepala koki yang mendelegasikan: tiap aksi punya method sendiri yang rapi, bisa di-test satu per satu, dan bisa dipakai ulang. Saat aplikasimu tumbuh, kamu butuh kepala koki.

Kenapa Controller Ada? Masalah Apa yang Diselesaikan?

Closure route punya empat masalah yang baru terasa saat project membesar:

  1. Tidak bisa di-test satuan. Closure anonim sulit dipanggil dari automated test.
  2. Tidak bisa di-cache. Seperti dibahas di modul lalu, route:cache gagal kalau ada closure.
  3. Tidak bisa dipakai ulang. Logika yang sama dibutuhkan di route web dan API? Copy-paste closure.
  4. File route menggembung. web.php 500 baris campuran URL dan logika bisnis tidak bisa dibaca.

Controller menyelesaikan semuanya: class bernama dengan method bernama, satu tanggung jawab per method, mudah di-test, mudah di-cache, mudah dipakai ulang.

Contoh 1: Dari Closure ke Controller

php
// routes/web.php: SEBELUM: logika menumpuk di route
Route::get('/posts', function () {
    $posts = Post::latest()->get();
    return view('posts.index', ['posts' => $posts]);
});

Route::get('/posts/{post}', function (Post $post) {
    return view('posts.show', compact('post'));
});
bash
# Buat controller-nya dulu
php artisan make:controller PostController
php
// app/Http/Controllers/PostController.php: SESUDAH: rapi per method
namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        return view('posts.index', [
            'posts' => Post::latest()->get(),
        ]);
    }

    public function show(Post $post)
    {
        return view('posts.show', compact('post'));
    }
}
php
// routes/web.php: SESUDAH: route hanya jadi daftar isi
use App\Http\Controllers\PostController;

Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::get('/posts/{post}', [PostController::class, 'show'])->name('posts.show');

Perhatikan pola penulisannya: [PostController::class, 'index']. Jangan tulis sebagai string 'PostController@index' (gaya lama Laravel 7 ke bawah, sudah tidak disarankan).

Contoh 2: Controller Menerima Request dan Memvalidasi

Controller yang sesungguhnya menerima Request, memvalidasi, lalu merespons:

php
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function store(Request $request)
    {
        // Validasi: gagal → otomatis kembali ke form dengan error
        $data = $request->validate([
            'title' => 'required|string|max:255',
            'body' => 'required|string',
        ]);

        $post = Post::create($data);

        return redirect()->route('posts.show', $post)
            ->with('sukses', 'Artikel berhasil diterbitkan!');
    }
}
blade
{{-- resources/views/posts/index.blade.php: tampilkan pesan sukses --}}
@if (session('sukses'))
    <div class="alert">{{ session('sukses') }}</div>
@endif

->with('sukses', ...) menyimpan pesan flash ke session yang hanya hidup satu request. Pola ini (validasi, simpan, redirect dengan pesan) adalah 80% isi controller aplikasi CRUD.

Catatan teknis: Controller sebaiknya kurus (thin). Aturan kasar: kalau satu method lebih dari 30 baris, itu bau yang perlu di-refactor. Logika bisnis yang kompleks pindah ke Service class atau ke Model. Controller hanya mengatur lalu lintas: terima request, panggil logika, kembalikan respons.

Kesalahan Umum Pemula

Salah: tetap memakai gaya string lama 'PostController@index'.

php
// SALAH: gaya lama, tidak dapat autocomplete IDE dan rawan typo
Route::get('/posts', 'PostController@index');
php
// BENAR: gaya modern dengan ::class, IDE bisa mengecek dan melengkapi
use App\Http\Controllers\PostController;
Route::get('/posts', [PostController::class, 'index']);

Salah: controller gendut, semua logika bisnis ditumpuk di satu method.

php
// SALAH: method store 60 baris berisi hitung diskon, kirim email,
// update stok, catat log, semuanya di controller
public function store(Request $request)
{
    // ... 60 baris chaos ...
}
php
// BENAR: controller kurus, logika pindah ke tempatnya
public function store(Request $request)
{
    $data = $request->validate([...]);
    $order = $this->orderService->checkout($data); // logika di Service class
    return redirect()->route('orders.show', $order);
}

Rangkuman

Controller memindahkan logika dari closure route ke class yang bernama, testable, dan cache-friendly. Buat dengan make:controller, daftarkan dengan [XController::class, 'method'], dan jaga tetap kurus. Untuk aksi yang benar-benar tunggal (misal webhook atau halaman status), ada juga single action controller: make:controller StatusController --invokable menghasilkan satu method __invoke(). Berikutnya: versi super controller untuk CRUD, resource controller.

Tantangan

Controller Buku

Buat BookController dengan method index() (teks 'Daftar buku') dan show($id) (teks "Buku ID: $id"). Daftarkan kedua route dengan nama books.index dan books.show. Test di browser.