Tidak semua halaman butuh tujuh method CRUD. Sebagian endpoint hanya punya satu tugas: halaman ringkasan yang menghitung statistik, endpoint webhook yang menerima notifikasi pembayaran, atau health check yang melaporkan status aplikasi. Membungkus satu tugas dalam resource controller berarti menyediakan enam method kosong yang tidak pernah dipakai. Untuk kasus tersebut, Laravel menyediakan single action controller — kelas dengan satu method bernama __invoke() yang dieksekusi setiap kali endpoint dipanggil.
Contoh keduanya mudah dibayangkan. Endpoint webhook dari penyedia pembayaran hanya perlu satu tugas: memverifikasi tanda tangan request lalu menyimpan status transaksi — tidak ada halaman daftar ataupun form. Begitu juga health check yang cukup membalas status database dan queue; menaruh keduanya sebagai method di PaymentController atau StatusController multi-method hanya menambah tempat mencari kode saat terjadi masalah.
Kapan Memilih __invoke #
Method __invoke() adalah magic method PHP: interpreter menjalankan method tersebut setiap kali objek dipanggil layaknya fungsi, misalnya $objek(). Laravel memanfaatkan perilaku ini agar route cukup menunjuk nama kelas tanpa nama method. Dua bentuk controller berikut menempatkan skenario yang berbeda:
| Aspek | Resource controller | Single action controller |
|---|---|---|
| Jumlah tugas | Tujuh aksi CRUD | Tepat satu tugas |
| Bentuk route | Route::resource(...) atau [X::class, 'method'] | Route::get(..., X::class) |
| Nama route | posts.index, posts.show, dst. | Satu nama bebas |
| Cocok untuk | Entity seperti post, produk | Ringkasan, webhook, ekspor, health check |
Pertanyaan penentu sederhana: jika suatu saat endpoint ini butuh aksi kedua, Resource controller lebih tepat; jika tugasnya memang tunggal dan jarang berubah, single action controller memberi nama kelas yang langsung terbaca sebagai tugas — RingkasanController terasa lebih jelas daripada PostController@ringkasan. Konsekuensi praktisnya juga bersifat positif: satu controller satu berkas berarti satu alasan untuk berubah, dan reviewer kode langsung tahu seluruh perilaku endpoint tanpa memindai method lain.
Membuat Controller Invokable #
Flag --invokable pada generator menghasilkan kerangka single action controller:
php artisan make:controller RingkasanController --invokableOutput yang Dihasilkan:
INFO Controller [app/Http/Controllers/RingkasanController.php] created successfully. Kita isi method __invoke() untuk membangun halaman ringkasan jumlah post:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\View\View;
class RingkasanController extends Controller
{
public function __invoke(Request $request): View
{
return view('ringkasan', [
'totalPost' => Post::count(),
'postTerbaru' => Post::latest('id')->first(),
]);
}
}Tidak ada method lain di kelas tersebut — satu tugas, satu method, dan seluruh logika halaman berada tepat di tempatnya. Parameter $request tetap tersedia seperti pada method biasa, sehingga controller invokable tetap bisa membaca input bila tugasnya membutuhkan data dari request.
Route Tanpa Nama Method #
Pendaftaran route-nya justru lebih pendek daripada controller biasa, karena handler hanya berupa nama kelas:
<?php
use App\Http\Controllers\RingkasanController;
use Illuminate\Support\Facades\Route;
Route::get('/ringkasan', RingkasanController::class)->name('ringkasan');Array [RingkasanController::class, 'index'] tidak diperlukan lagi — Laravel mendeteksi handler berupa kelas invokable dan otomatis menjalankan __invoke(). Kita periksa pendaftarannya:
php artisan route:list --except-vendor --path=ringkasanOutput yang Dihasilkan:
GET|HEAD ringkasan ................... ringkasan › RingkasanController
Showing [1] routesPerhatikan kolom kanan: handler tertulis RingkasanController tanpa akhiran @method — penanda visual yang langsung membedakan route invokable dari route biasa seperti PostController@index pada output chapter sebelumnya.
Menguji Halaman Ringkasan #
Feature test memastikan data yang dihitung __invoke() benar-benar sampai ke halaman:
<?php
namespace Tests\Feature;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class RingkasanTest extends TestCase
{
use RefreshDatabase;
public function test_halaman_ringkasan_menampilkan_total_dan_post_terbaru(): void
{
Post::create(['title' => 'Belajar Controller', 'content' => 'Isi pertama.']);
Post::create(['title' => 'Belajar Route', 'content' => 'Isi kedua.']);
$this->get('/ringkasan')
->assertOk()
->assertSee('Total post: 2')
->assertSee('Post terbaru: Belajar Route');
}
}Output yang Dihasilkan:
PASS Tests\Feature\RingkasanTest
✓ halaman ringkasan menampilkan total dan post terbaru 0.15s
Tests: 1 passed (3 assertions)
Duration: 0.23sDua post dibuat lebih dulu, lalu satu request GET /ringkasan menjalankan __invoke() dari awal sampai akhir: menghitung total, mengambil post terbaru, me-render view, dan mengembalikan response 200. Tiga assertion pada test tersebut cukup karena halamannya memang sekecil tugasnya.
Perhatikan satu hal pada contoh kita: __invoke() menerima Request $request dan bebas memanggil Post::count() di dalam kelas. Kedua objek tersebut sebenarnya disuntikkan oleh service container Laravel — mekanisme yang selama ini bekerja diam-diam di belakang setiap controller. Chapter Dependency Injection di Controller
berikutnya membuka cara kerjanya, dari method injection sampai pengikatan interface; seluruh bentuk handler route termuat di dokumentasi controllers
.