Di chapter Fungsi kita sudah menulis fungsi bernama dengan function namaFungsi(). Laravel memakai bentuk lain yang jauh lebih sering muncul di kesehariannya: anonymous function — fungsi tanpa nama yang diperlakukan sebagai nilai, bisa disimpan ke variabel, dimasukkan ke array, atau dikirim sebagai argumen. Ketika anonymous function “membawa” variabel dari lingkup sekitarnya, struktur tersebut disebut closure. Hampir semua titik kustomisasi Laravel menerima closure, jadi membaca pola ini dengan nyaman adalah kunci membaca kode framework.

Anonymous Function sebagai Nilai #

Anonymous function dibuat dengan function (...) use (...) tanpa nama, lalu dipanggil melalui variabel penampungnya. Perhatikan tanda ; setelah kurung penutup — kita sedang mengakhiri sebuah penugasan, bukan deklarasi:

php
<?php
$sapa = function (string $nama): string {
    return "Halo, {$nama}!";
};

echo $sapa('Budi') . "\n";
echo $sapa('Siti') . "\n";

Output yang Dihasilkan:

text
Halo, Budi!
Halo, Siti!

Variabel $sapa kini berisi sesuatu yang bisa dipanggil (callable). Tipenya adalah objek internal bernama Closure, dan seperti fungsi bernama, anonymous function juga bisa memiliki type declaration untuk parameter dan return value.

Keyword use dan Lingkup Variabel #

Secara bawaan, anonymous function tidak melihat variabel di luar tubuhnya. Variabel dari lingkup sekitar harus diimpor secara eksplisit lewat use. Mekanisme inilah yang membuat closure bisa “mengingat” nilai saat closure dibuat:

php
<?php
function buatPenyapa(string $nama): Closure
{
    return function (string $salam) use ($nama): string {
        return "{$salam}, {$nama}!";
    };
}

$penyapaBudi = buatPenyapa('Budi');
echo $penyapaBudi('Selamat pagi') . "\n";
echo $penyapaBudi('Selamat malam') . "\n";

Output yang Dihasilkan:

text
Selamat pagi, Budi!
Selamat malam, Budi!

Fungsi buatPenyapa() mengembalikan closure yang menyimpan nilai 'Budi' meskipun pemanggilan fungsi sudah selesai. Setiap pemanggilan $penyapaBudi() memakai nilai yang sama persis. Return type Closure di contoh ini juga mengilustrasikan cara menandai fungsi yang mengembalikan closure.

Arrow Function #

PHP 7.4 memperkenalkan bentuk ringkas bernama arrow function dengan sintaks fn(...) => .... Arrow function mengimpor variabel lingkup sekitar secara otomatis tanpa use, tetapi hanya untuk pembacaan nilai:

php
<?php
$diskon = 0.2;

$hitungDiskon = fn (int $harga): float => $harga * $diskon;

$tugas = [
    ['judul' => 'Deploy Produksi', 'prioritas' => 3],
    ['judul' => 'Migrasi Database', 'prioritas' => 1],
    ['judul' => 'Menulis Tes Fitur', 'prioritas' => 2],
];

usort($tugas, fn (array $a, array $b): int => $a['prioritas'] <=> $b['prioritas']);

foreach ($tugas as $t) {
    echo "{$t['prioritas']}. {$t['judul']}\n";
}

echo $hitungDiskon(100000) . "\n";

Output yang Dihasilkan:

text
1. Migrasi Database
2. Menulis Tes Fitur
3. Deploy Produksi
20000

Arrow function pada contoh ini dipakai dua peran sekaligus: sebagai callback usort() yang membandingkan dua array, dan sebagai kalkulator diskon yang membaca $diskon dari lingkup sekitar secara otomatis.

Closure sebagai Parameter #

Karena closure hanyalah nilai, sebuah fungsi bisa menerima closure lalu memanggilnya di dalam tubuhnya. Fungsi semacam ini disebut higher-order function — Laravel memakai pola ini untuk hampir semua konfigurasinya:

php
<?php
$pembayaran = 250000;

$terapkanBiaya = function (Closure $kalkulasi) use ($pembayaran): float {
    return $kalkulasi($pembayaran);
};

$total = $terapkanBiaya(fn (float $nilai): float => $nilai + 2500);

echo $total . "\n";

Output yang Dihasilkan:

text
252500

Perhatikan parameternya bertipe Closure: kita menandai secara eksplisit bahwa argumen yang masuk harus sesuatu yang bisa dipanggil. Perhatikan juga bahwa $terapkanBiaya sendiri adalah closure — closure yang menerima closure.

Closure di Kode Laravel #

Sekarang kita lihat pola yang sama di Laravel. Berikut isi routes/web.php pada sandbox Course ini setelah kita menambahkan dua route baru — perhatikan bahwa ketiga route memakai closure:

php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/salam', fn () => 'Halo dari closure!');

Route::prefix('tugas')->group(function () {
    Route::get('/{id}', function (string $id) {
        return "Detail tugas {$id}";
    });
});

Route pertama memakai anonymous function biasa, route kedua memakai arrow function, dan route ketiga memperlihatkan group() yang menerima closure untuk membungkus beberapa route sekaligus. Saat kita mendaftarkan route di atas, output php artisan route:list --json menunjukkan bahwa action-nya bertipe Closure:

text
[{"domain":null,"method":"GET|HEAD","uri":"salam","name":null,"action":"Closure","middleware":["web"],"path":"routes\/web.php:9"}]

Apakah route closure tersebut benar-benar bekerja? Kita tulis feature test kecil untuk memanggilnya:

php
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ClosureRouteTest extends TestCase
{
    public function test_route_closure_mengembalikan_teks(): void
    {
        $response = $this->get('/salam');

        $response->assertStatus(200);
        $response->assertSee('Halo dari closure!');
    }

    public function test_route_group_dengan_closure(): void
    {
        $response = $this->get('/tugas/7');

        $response->assertStatus(200);
        $response->assertSee('Detail tugas 7');
    }
}

Output yang Dihasilkan:

text
  PASS  Tests\Feature\ClosureRouteTest
  ✓ route closure mengembalikan teks                                    0.10s
  ✓ route group dengan closure                                          0.01s

  Tests:    2 passed (4 assertions)
  Duration: 0.19s

Closure juga menjadi cara Laravel menyerahkan keputusan kepada kita. Method Gate::define() menerima closure yang menentukan siapa boleh melakukan aksi; closure tersebut menerima user yang sedang login lalu mengembalikan true atau false. Runner berikut dijalankan pada sandbox:

php
<?php

use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::define('kelola-panel', function (User $user): bool {
    return $user->email === '[email protected]';
});

$budi = User::factory()->create(['name' => 'Budi', 'email' => '[email protected]']);
$siti = User::factory()->create(['name' => 'Siti', 'email' => '[email protected]']);

var_dump(Gate::forUser($budi)->allows('kelola-panel'));
var_dump(Gate::forUser($siti)->allows('kelola-panel'));

Output yang Dihasilkan:

text
bool(true)
bool(false)

Satu pola penting terakhir ada di bootstrap/app.php. Saat mengonfigurasi middleware dan exception, Laravel memanggil method seperti withMiddleware(function (Middleware $middleware): void { ... }) — higher-order function yang menyerahkan objek konfigurasi ke closure kita untuk disunting. Kita akan membongkar berkas tersebut di chapter Exception Handling dan Struktur Direktori Laravel. Referensi lengkap closure PHP tersedia di dokumentasi resmi anonymous functions , sedangkan contoh pemakaian closure di validasi dan authorization ada di dokumentasi validation Laravel.

Dengan closure, kita bisa melempar perilaku ke dalam framework. Namun closure hanyalah satu wajah dari dunia berorientasi objek Laravel — selanjutnya kita masuk ke class, inheritance, interface, dan trait yang menjadi rangka sesungguhnya dari framework ini.