Closure dengan use() sangat ampuh, tetapi untuk kasus paling sering — callback satu baris di dalam array_map() atau array_filter() — ritualnya terasa panjang: kata kunci function, kurung kurawal, klausa use(), lalu return. PHP 7.4 menjawab dengan arrow function: sintaks fn() => ekspresi yang menangkap variabel luar secara otomatis tanpa use(). Pada Chapter ini kita pelajari bentuk sintaksnya, aturan capture otomatis beserta sifat snapshot-nya, batasan satu ekspresi, cara menyusun arrow function bersarang, dan panduan memilih antara arrow function dan closure biasa.
Sintaks fn() => Ekspresi #
Arrow function dideklarasikan dengan kata kunci fn, daftar parameter, tanda =>, lalu tepat satu ekspresi. Nilai ekspresi tersebut otomatis menjadi return value — tanpa kata kunci return. Tidak perlu klausa use(); variabel luar yang dipakai di dalam ekspresi ditangkap secara otomatis:
<?php
$kuotaSisa = 10;
$sisaKuota = fn(int $terpakai) => $kuotaSisa - $terpakai;
echo $sisaKuota(4) . "\n";
echo $sisaKuota(9) . "\n";Output yang Dihasilkan:
6
1Mari kita telaah:
$kuotaSisadari scope global dipakai langsung di dalam ekspresi tanpause()— arrow function menangkap variabel tersebut secara otomatis.$sisaKuota(4)mengevaluasi10 - 4dan mengembalikan6; pemanggilan kedua mengembalikan10 - 9yaitu1.
Capture Otomatis secara By Value #
Perbedaan mendasar arrow function dari anonymous function biasa ada pada cara mendapatkan variabel luar. Anonymous function harus mendaftarkan tiap variabel lewat use(); arrow function memindai ekspresinya sendiri dan menangkap variabel yang benar-benar dipakai — selalu secara by value. Dua fungsi berikut setara:
<?php
$pajak = 0.11;
$versiClosure = function (float $harga) use ($pajak): float {
return $harga * (1 + $pajak);
};
$versiArrow = fn(float $harga) => $harga * (1 + $pajak);
echo $versiClosure(100000) . "\n";
echo $versiArrow(100000) . "\n";Output yang Dihasilkan:
111000
111000Mari kita bandingkan keduanya:
- Versi
functionmembutuhkan tiga baris untuk satu perkalian: definisi, klausause ($pajak), danreturn. - Versi
fnmenangkap$pajakotomatis karena variabel itu muncul di ekspresi; hasilnya identik,111000. - Daftar variabel yang berhak masuk ditentukan oleh pemakaian, bukan deklarasi — variabel luar yang tidak dipakai di ekspresi tidak ikut tertangkap.
Batasan Satu Ekspresi #
Tubuh arrow function harus berupa tepat satu ekspresi: tidak ada blok statement, tidak ada beberapa baris, tidak ada return eksplisit. Mencoba menulis blok kurung kurawal langsung ditolak parser:
Parse error: syntax error, unexpected token "{"Pesan di atas muncul ketika kita menulis fn() => { return 1; }; — parser menuntut sebuah ekspresi setelah tanda =>, bukan blok. Begitu callback membutuhkan lebih dari satu langkah — validasi masukan, variabel antara, atau pelemparan exception — kembalilah ke anonymous function dengan use():
<?php
$persenDiskon = 0.15;
$hitungHargaFinal = function (float $harga) use ($persenDiskon): float {
if ($harga < 0) {
throw new InvalidArgumentException("Harga tidak boleh negatif");
}
$diskon = $harga * $persenDiskon;
return $harga - $diskon;
};
echo $hitungHargaFinal(200000) . "\n";Output yang Dihasilkan:
170000Mari kita bedah mengapa versi fn tidak mungkin dipakai di sini:
- Callback membutuhkan pemeriksaan
$harga < 0yang melempar exception untuk masukan tidak valid — sebuah statement, bukan ekspresi tunggal. - Perhitungan dipecah menjadi variabel antara
$diskonsebelumreturnakhir — dua langkah yang tidak bisa dimampatkan tanpa mengorbankan kejelasan. - Klausa
use ($persenDiskon)kembali wajib karena ini anonymous function biasa, bukan arrow function.
Arrow Function Bersarang #
Arrow function boleh berada di dalam arrow function lain, dan penangkapan otomatis menembus berlapis: arrow function terdalam boleh memakai variabel dari scope paling luar sekalipun variabel itu tidak disebut di tingkat tengah. Pola ini membuat rantai array_map() dan array_sum() terbaca seperti satu rumus:
<?php
$persenPajak = 0.11;
$hitungTotal = fn(array $daftarHarga) => array_sum(
array_map(
fn(int $harga) => (int) round($harga * (1 + $persenPajak)),
$daftarHarga
)
);
echo $hitungTotal([50000, 125000, 80000]) . "\n";Output yang Dihasilkan:
283050Mari kita bedah alurnya:
- Arrow function luar menerima
$daftarHargalalu mengembalikanarray_sum()dari hasilarray_map()— keduanya adalah ekspresi, sehingga tetap sah sebagai tubuh arrow. - Arrow function dalam memakai
$persenPajakyang tertangkap otomatis dari scope global, menembus batas arrow function luar. - Tiga harga final
55500 + 138750 + 88800dijumlahkan menjadi283050.
Jebakan Snapshot pada Capture Otomatis #
Capture otomatis tidak mengubah sifat dasar penangkapan: tetap by value, tetap salinan yang diambil saat arrow function dibuat. Akibatnya arrow function tidak bisa dipakai untuk memperbarui variabel luar — increment di dalamnya hanya mengenai salinan per pemanggilan:
<?php
$counter = 0;
$naikkan = fn() => $counter++;
echo "Kembalian pemanggilan: " . $naikkan() . "\n";
$naikkan();
echo "Counter di luar: $counter\n";Output yang Dihasilkan:
Kembalian pemanggilan: 0
Counter di luar: 0Mari kita bedah hasil yang tampak salah ini:
$counter++adalah ekspresi yang sah untuk tubuh arrow function — tetapi yang dinaikkan adalah salinan$counterbernilai0yang tertangkap saat pembuatan.- Nilai kembalian
0adalah nilai$countersebelum kenaikan; salinannya kemudian dibuang begitu pemanggilan selesai. - Setelah dua pemanggilan,
$counterdi luar tetap0— tidak ada satu pun kenaikan yang mengenai variabel asli.
Memilih Arrow Function atau Closure #
Kedua bentuk menghasilkan object Closure yang sama persis; pilihan di tangan kita hanyalah soal keterbacaan. Gunakan tabel berikut saat ragu:
| Kriteria | Arrow function fn() => | Closure function () use () |
|---|---|---|
| Panjang tubuh | Satu ekspresi saja | Beberapa statement bebas |
| Penangkapan variabel | Otomatis, by value | Eksplisit lewat use(), by value atau by reference |
| Keterbacaan | Unggul untuk one-liner | Unggul untuk logika bertahap |
| Contoh khas | Callback array_map() / array_filter() | Callback ber-validasi, event handler, pabrik fungsi |
Aturan praktis yang lazim dipakai komunitas: mulailah dari arrow function, lalu pindah ke closure begitu ekspresi mulai butuh bernapas — butuh variabel antara, percabangan, atau efek pada variabel luar. Konsistensi tim lebih berharga daripada preferensi pribadi, jadi sepakati satu gaya penulisan lalu pertahankan di seluruh proyek.
Ringkasan #
Arrow function memampatkan closure by value menjadi fn() => ekspresi: penangkapan variabel luar berlangsung otomatis, tubuhnya tepat satu ekspresi, dan nilai ekspresi langsung menjadi return value. Sifat snapshot-nya sama dengan use() biasa — hanya membaca, tidak pernah mengubah variabel luar — sedangkan kebutuhan statement ganda atau penangkapan by reference otomatis mengembalikan kita ke anonymous function penuh. Dengan tiga bentuk fungsi tanpa nama yang kini lengkap, kita melangkah ke teknik memperlakukan fungsi bernama pun sebagai nilai pada Chapter First-Class Callables
, sebelum menutup Section ini dengan rekursi.