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:

AspekResource controllerSingle action controller
Jumlah tugasTujuh aksi CRUDTepat satu tugas
Bentuk routeRoute::resource(...) atau [X::class, 'method']Route::get(..., X::class)
Nama routeposts.index, posts.show, dst.Satu nama bebas
Cocok untukEntity seperti post, produkRingkasan, 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:

bash
php artisan make:controller RingkasanController --invokable

Output yang Dihasilkan:

text
  INFO  Controller [app/Http/Controllers/RingkasanController.php] created successfully.  

Kita isi method __invoke() untuk membangun halaman ringkasan jumlah post:

php
<?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
<?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:

bash
php artisan route:list --except-vendor --path=ringkasan

Output yang Dihasilkan:

text
  GET|HEAD       ringkasan ................... ringkasan › RingkasanController

                                                            Showing [1] routes

Perhatikan 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
<?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:

text
  PASS  Tests\Feature\RingkasanTest
  ✓  halaman ringkasan menampilkan total dan post terbaru                0.15s  

  Tests:    1 passed (3 assertions)
  Duration: 0.23s

Dua 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 .