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
<?php
$kuotaSisa = 10;

$sisaKuota = fn(int $terpakai) => $kuotaSisa - $terpakai;

echo $sisaKuota(4) . "\n";
echo $sisaKuota(9) . "\n";

Output yang Dihasilkan:

text
6
1

Mari kita telaah:

  1. $kuotaSisa dari scope global dipakai langsung di dalam ekspresi tanpa use() — arrow function menangkap variabel tersebut secara otomatis.
  2. $sisaKuota(4) mengevaluasi 10 - 4 dan mengembalikan 6; pemanggilan kedua mengembalikan 10 - 9 yaitu 1.

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

text
111000
111000

Mari kita bandingkan keduanya:

  1. Versi function membutuhkan tiga baris untuk satu perkalian: definisi, klausa use ($pajak), dan return.
  2. Versi fn menangkap $pajak otomatis karena variabel itu muncul di ekspresi; hasilnya identik, 111000.
  3. 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:

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

text
170000

Mari kita bedah mengapa versi fn tidak mungkin dipakai di sini:

  1. Callback membutuhkan pemeriksaan $harga < 0 yang melempar exception untuk masukan tidak valid — sebuah statement, bukan ekspresi tunggal.
  2. Perhitungan dipecah menjadi variabel antara $diskon sebelum return akhir — dua langkah yang tidak bisa dimampatkan tanpa mengorbankan kejelasan.
  3. 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
<?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:

text
283050

Mari kita bedah alurnya:

  1. Arrow function luar menerima $daftarHarga lalu mengembalikan array_sum() dari hasil array_map() — keduanya adalah ekspresi, sehingga tetap sah sebagai tubuh arrow.
  2. Arrow function dalam memakai $persenPajak yang tertangkap otomatis dari scope global, menembus batas arrow function luar.
  3. Tiga harga final 55500 + 138750 + 88800 dijumlahkan menjadi 283050.

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
<?php
$counter = 0;

$naikkan = fn() => $counter++;

echo "Kembalian pemanggilan: " . $naikkan() . "\n";
$naikkan();
echo "Counter di luar: $counter\n";

Output yang Dihasilkan:

text
Kembalian pemanggilan: 0
Counter di luar: 0

Mari kita bedah hasil yang tampak salah ini:

  1. $counter++ adalah ekspresi yang sah untuk tubuh arrow function — tetapi yang dinaikkan adalah salinan $counter bernilai 0 yang tertangkap saat pembuatan.
  2. Nilai kembalian 0 adalah nilai $counter sebelum kenaikan; salinannya kemudian dibuang begitu pemanggilan selesai.
  3. Setelah dua pemanggilan, $counter di luar tetap 0 — 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:

KriteriaArrow function fn() =>Closure function () use ()
Panjang tubuhSatu ekspresi sajaBeberapa statement bebas
Penangkapan variabelOtomatis, by valueEksplisit lewat use(), by value atau by reference
KeterbacaanUnggul untuk one-linerUnggul untuk logika bertahap
Contoh khasCallback 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.

Referensi #