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

text
Toko Budi — Rp166.500

Mari kita telaah:

  1. use ($namaToko, $pajak) menyalin dua nilai dari scope global ke dalam closure pada saat fungsi dibuat.
  2. 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.
  3. Hasil 150000 * 1.11 diformat menjadi 166.500 dan 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
<?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:

text
Halo Siti, member level silver!

Mari kita urutkan kejadiannya secara kronologis:

  1. Saat baris $sapaMember = function ... dieksekusi, nilai $level masih "silver" — closure menyimpan salinan string itu.
  2. Baris $level = "gold"; mengubah variabel asli di scope global, tetapi tidak menyentuh salinan yang tersimpan di dalam closure.
  3. Pemanggilan $sapaMember("Siti") terjadi setelah perubahan, namun sapaan yang tercetak masih menyebut level silver.

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

text
[1] Koneksi gagal
[1] Query timeout
[1] Session kedaluwarsa
Total error tercatat: 1

Mari kita bedah mengapa hasilnya demikian:

  1. $catatErrorSalinan menangkap $jumlahError by value: setiap pemanggilan memulai dari salinan nilai saat pembuatan (0), menaikkannya menjadi 1, lalu membuang salinannya — itulah mengapa dua pemanggilan sama-sama mencetak [1].
  2. $catatErrorAsli menangkap &$jumlahError by reference: kenaikan di dalam closure mengenai variabel asli.
  3. Setelah tiga pemanggilan, variabel asli $jumlahError hanya bernilai 1 — 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
<?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:

text
50
125

Mari kita bedah:

  1. buatPengali(2) menjalankan fungsi pabrik; return function (...) use ($faktor) membentuk closure yang menyimpan salinan $faktor bernilai 2.
  2. buatPengali(5) membentuk closure lain dengan salinan $faktor bernilai 5 — kedua closure tidak berbagi apa pun.
  3. $kaliDua(25) menghasilkan 50 dan $kaliLima(25) menghasilkan 125 dari 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
<?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:

text
27750, 44400, 35520

Callback 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
<?php
$hitungPanjang = Closure::fromCallable("strlen");
echo $hitungPanjang("Belajar PHP") . "\n";

$hurufBesar = strtoupper(...);
echo $hurufBesar("budi santoso") . "\n";

Output yang Dihasilkan:

text
11
BUDI SANTOSO

Mari kita bedah:

  1. Closure::fromCallable("strlen") menghasilkan closure pembungkus fungsi strlen(), lengkap untuk dipanggil seperti closure biasa — "Belajar PHP" terhitung 11 karakter.
  2. strtoupper(...) adalah bentuk first-class callable; hasilnya juga object Closure, satu baris lebih ringkas.
  3. $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.

Referensi #