Chapter sebelumnya berakhir pada satu pertanyaan: bagaimana anonymous function bisa mengakses variabel dari scope tempat fungsi itu dibuat? Jawabannya ada pada klausa use() — mekanisme penangkapan yang mengubah fungsi tanpa nama menjadi closure. Secara istilah, closure dan anonymous function di PHP sering dipakai bergantian karena keduanya diwakili object Closure; secara konsep, closure adalah anonymous function yang membawa environment — kumpulan variabel hasil tangkapannya — ke mana pun fungsi itu dikirim. Pada Chapter ini kita bedah cara kerja penangkapan tersebut: by value versus by reference, momen tepatnya salinan diambil, teknik pabrik fungsi, hingga cara membungkus fungsi bernama menjadi object Closure.
Menangkap Variabel dengan use() #
Klausa use() ditempatkan tepat setelah daftar parameter dan sebelum return type, lalu mendaftarkan variabel luar yang diizinkan masuk. Beberapa variabel dipisahkan dengan koma, dan secara bawaan semuanya ditangkap by value — nilainya disalin ke dalam fungsi. Contoh berikut menangkap dua variabel sekaligus untuk memformat harga:
<?php
$namaToko = "Toko Budi";
$pajak = 0.11;
$formatHarga = function (float $harga) use ($namaToko, $pajak): string {
$total = $harga * (1 + $pajak);
return "$namaToko — Rp" . number_format($total, 0, ",", ".");
};
echo $formatHarga(150000) . "\n";Output yang Dihasilkan:
Toko Budi — Rp166.500Mari kita telaah:
use ($namaToko, $pajak)menyalin dua nilai dari scope global ke dalam closure pada saat fungsi dibuat.- Di dalam tubuh fungsi, kedua nama dipakai seperti variabel lokal biasa;
number_format($total, 0, ",", ".")memformat angka dengan pemisah ribuan berupa titik, sesuai gaya penulisan Indonesia. - Hasil
150000 * 1.11diformat menjadi166.500dan digabung dengan nama toko dalam satu string.
Penangkapan Terjadi Saat Pembuatan #
Detail terpenting dari capture by value: salinan diambil pada saat closure dibuat, bukan saat closure dipanggil. Bila variabel sumber berubah setelah closure terbentuk, closure tetap memakai nilai lama. Perilaku snapshot ini sering mengejutkan pemula:
<?php
$level = "silver";
$sapaMember = function (string $nama) use ($level): string {
return "Halo $nama, member level $level!";
};
$level = "gold";
echo $sapaMember("Siti") . "\n";Output yang Dihasilkan:
Halo Siti, member level silver!Mari kita urutkan kejadiannya secara kronologis:
- Saat baris
$sapaMember = function ...dieksekusi, nilai$levelmasih"silver"— closure menyimpan salinan string itu. - Baris
$level = "gold";mengubah variabel asli di scope global, tetapi tidak menyentuh salinan yang tersimpan di dalam closure. - Pemanggilan
$sapaMember("Siti")terjadi setelah perubahan, namun sapaan yang tercetak masih menyebut levelsilver.
Bila Anda membutuhkan closure yang selalu melihat nilai terbaru dari variabel luar, mekanismenya ada — tetapi menuntut penangkapan by reference, yang kita bahas berikutnya.
Menangkap by Reference dengan & #
Callback kadang memang harus memperbarui variabel luar — misalnya menghitung jumlah kejadian dari dalam callback. Untuk itu tambahkan tanda & sebelum nama variabel di klausa use(), sehingga yang ditangkap adalah tautan ke variabel asli, bukan salinan. Contoh berikut membandingkan kedua gaya dalam satu program:
<?php
$jumlahError = 0;
$catatErrorSalinan = function (string $pesan) use ($jumlahError): void {
$jumlahError++;
echo "[$jumlahError] $pesan\n";
};
$catatErrorAsli = function (string $pesan) use (&$jumlahError): void {
$jumlahError++;
echo "[$jumlahError] $pesan\n";
};
$catatErrorSalinan("Koneksi gagal");
$catatErrorSalinan("Query timeout");
$catatErrorAsli("Session kedaluwarsa");
echo "Total error tercatat: $jumlahError\n";Output yang Dihasilkan:
[1] Koneksi gagal
[1] Query timeout
[1] Session kedaluwarsa
Total error tercatat: 1Mari kita bedah mengapa hasilnya demikian:
$catatErrorSalinanmenangkap$jumlahErrorby value: setiap pemanggilan memulai dari salinan nilai saat pembuatan (0), menaikkannya menjadi1, lalu membuang salinannya — itulah mengapa dua pemanggilan sama-sama mencetak[1].$catatErrorAslimenangkap&$jumlahErrorby reference: kenaikan di dalam closure mengenai variabel asli.- Setelah tiga pemanggilan, variabel asli
$jumlahErrorhanya bernilai1— dua pemanggilan versi salinan tidak pernah mengenainya.
Pabrik Fungsi yang Mengembalikan Closure #
Karena closure adalah nilai biasa, fungsi boleh mengembalikan closure sebagai hasil. Pola ini menjadikan fungsi luar semacam pabrik: setiap pemanggilan pabrik melahirkan closure mandiri dengan tangkapan miliknya sendiri:
<?php
function buatPengali(float $faktor): Closure
{
return function (float $nilai) use ($faktor): float {
return $nilai * $faktor;
};
}
$kaliDua = buatPengali(2);
$kaliLima = buatPengali(5);
echo $kaliDua(25) . "\n";
echo $kaliLima(25) . "\n";Output yang Dihasilkan:
50
125Mari kita bedah:
buatPengali(2)menjalankan fungsi pabrik;return function (...) use ($faktor)membentuk closure yang menyimpan salinan$faktorbernilai2.buatPengali(5)membentuk closure lain dengan salinan$faktorbernilai5— kedua closure tidak berbagi apa pun.$kaliDua(25)menghasilkan50dan$kaliLima(25)menghasilkan125dari konstanta yang berbeda.
Tanpa penangkapan use(), pola pabrik seperti ini mustahil dibuat: parameter $faktor sudah lenyap begitu buatPengali() selesai, tetapi closure tetap menyimpan salinannya. Inilah makna frasa “menutup” (close over) variabel yang menjadi asal nama closure — fungsi membawa serta lingkungan variabelnya melewati akhir hidup scope asal.
Callback dengan Konteks Tambahan #
Kombinasi array_map() dan use() adalah pola harian di aplikasi nyata: callback singkat yang tetap membutuhkan satu-dua nilai dari luar. Dibandingkan alternatif global pada Chapter global dan static Variables
, versi closure menyatakan konteksnya secara eksplisit dan tidak menyentuh scope global sama sekali:
<?php
$persenPajak = 0.11;
$hargaMenu = [25000, 40000, 32000];
$hargaFinal = array_map(function (int $harga) use ($persenPajak): int {
return (int) round($harga * (1 + $persenPajak));
}, $hargaMenu);
echo implode(", ", $hargaFinal) . "\n";Output yang Dihasilkan:
27750, 44400, 35520Callback menangkap $persenPajak sebesar 11%, memakainya untuk menghitung harga final setiap menu, lalu implode() merangkai hasilnya menjadi 27750, 44400, 35520. Seluruh konteks berpindah lewat satu klausa use() — tidak ada variabel global yang tersentuh. Bila kebijakan pajak berubah, cukup ubah satu variabel di sisi pemanggil.
Membungkus Fungsi Bernama menjadi Closure #
Terakhir, PHP menyediakan jalan dua arah antara fungsi bernama dan object Closure. Closure::fromCallable() membungkus fungsi bernama apa pun menjadi instance Closure, dan sejak PHP 8.1 tersedia sintaks yang lebih ringkas: first-class callable — cukup tulis nama fungsi diikuti tanda kurung tiga (...). Keduanya berguna saat sebuah API menuntut object Closure, misalnya untuk diseragamkan dengan callback lain:
<?php
$hitungPanjang = Closure::fromCallable("strlen");
echo $hitungPanjang("Belajar PHP") . "\n";
$hurufBesar = strtoupper(...);
echo $hurufBesar("budi santoso") . "\n";Output yang Dihasilkan:
11
BUDI SANTOSOMari kita bedah:
Closure::fromCallable("strlen")menghasilkan closure pembungkus fungsistrlen(), lengkap untuk dipanggil seperti closure biasa —"Belajar PHP"terhitung 11 karakter.strtoupper(...)adalah bentuk first-class callable; hasilnya juga objectClosure, satu baris lebih ringkas.$hurufBesar("budi santoso")menghasilkan"BUDI SANTOSO"— perilaku fungsi aslinya tidak berubah, hanya kemasannya.
Sintaks first-class callable akan kita temui lagi dengan peran lebih besar pada Chapter First-Class Callables
. Untuk sekarang, cukup ingat bahwa fungsi bernama dan anonymous function dapat saling dikonversi — keduanya berakhir sebagai object Closure.
Ringkasan #
Closure adalah anonymous function plus environment: variabel luar ditangkap lewat use() secara by value (salinan yang diambil saat pembuatan) atau by reference (&) bila callback perlu mengubah variabel asli. Pabrik fungsi melahirkan closure dengan tangkapan mandiri, array_map() menerima callback berkonteks tanpa menyentuh scope global, dan fungsi bernama pun bisa dibungkus menjadi Closure lewat Closure::fromCallable() maupun sintaks first-class callable. Untuk kasus paling umum — closure by value yang pendek — PHP menyiapkan bentuk yang jauh lebih ringkas: arrow function, topik Chapter berikutnya.