Gaya PHP modern yang kita latih di Chapter Closures
dan Arrow Functions
banyak bergantung pada fungsi yang dikirim sebagai argumen: array_map() menerima callback, usort() menerima pembanding, dan pipeline data memperlakukan fungsi seperti nilai biasa. Pertanyaannya, bagaimana meneruskan fungsi bernama seperti strtoupper() atau method milik sebuah objek ke tempat-tempat tersebut? Sebelum PHP 8.1 kita terpaksa memakai string atau array yang rentan salah ketik. PHP 8.1 memperkenalkan sintaks first-class callable — cukup tulis strtoupper(...) — yang mengubah callable mana pun menjadi objek Closure sungguhan. Pada Chapter ini kita pelajari masalah yang diselesaikan sintaks ini, pemakaiannya untuk fungsi global, method static, dan method instance, serta kaitannya dengan Closure::fromCallable().
Cara Lama Meneruskan Fungsi sebagai Nilai #
Sebelum PHP 8.1, fungsi bernama diteruskan sebagai nilai dalam tiga bentuk: string berisi nama fungsi ('strtoupper'), string berisi format NamaClass::method, dan array [$objek, 'method']. PHP baru menyelesaikan bentuk-bentuk tersebut menjadi pemanggilan sungguhan saat callable digunakan. Mari kita lihat dua bentuk yang paling sering dijumpai:
<?php
class Pemformat
{
public function rupiah(int $nilai): string
{
return 'Rp' . number_format($nilai, 0, ',', '.');
}
}
$format = new Pemformat();
// Bentuk lama 1: nama fungsi ditulis sebagai string
$perbesar = 'strtoupper';
echo $perbesar('budi santoso') . "\n";
// Bentuk lama 2: array [objek, nama method]
$keRupiah = [$format, 'rupiah'];
echo $keRupiah(150000) . "\n";Output yang Dihasilkan:
BUDI SANTOSO
Rp150.000Kedua pemanggilan di atas berhasil karena string 'strtoupper' dan array [$format, 'rupiah'] sama-sama dikenali PHP sebagai callable. Bentuk lama ini tetap sah sampai sekarang, tetapi menyisakan tiga masalah mahal. Pertama, nama fungsi berada di dalam string sehingga salah ketik baru terdeteksi saat pemanggilan terjadi di runtime. Kedua, alat analisis statis seperti PHPStan sulit mengikuti referensi yang tersembunyi di dalam string. Ketiga, saat sebuah fungsi dinamai ulang, string yang tersisa berhenti menunjuk ke fungsi mana pun tanpa ada peringatan sebelumnya.
Sintaks First-Class Callable #
PHP 8.1 memperkenalkan notasi ringkas untuk masalah di atas: tuliskan callable apa pun diikuti tiga titik di dalam kurung, misalnya strlen(...). Tiga titik ini bukan operator spread dan bukan tanda pemanggilan — hanya penanda bahwa kita ingin membuat referensi ke callable tersebut dalam bentuk objek Closure yang siap dipanggil kapan saja.
<?php
$panjang = strlen(...);
var_dump($panjang instanceof Closure);
echo $panjang('Belajar PHP') . "\n";Output yang Dihasilkan:
bool(true)
11Mari kita telaah baris-baris kode di atas:
- Baris 2 (
$panjang = strlen(...);): ekspresistrlen(...)dievaluasi menjadi objekClosureyang membungkus fungsistrlen; fungsi aslinya belum dijalankan sama sekali. - Baris 4 (
var_dump(...)):instanceof Closuremembuktikan hasilnya benar-benar objekClosure, bukan string dan bukan nama fungsi. - Baris 5 (
echo $panjang('Belajar PHP');): closure dipanggil persis seperti fungsi biasa; argumen diteruskan kestrlenyang mengembalikan panjang string.
Karena hasilnya adalah Closure sungguhan, seluruh bekal kita dari Chapter Closures langsung berlaku: closure bisa disimpan di variabel, dimasukkan ke dalam array, dikirim sebagai argumen, bahkan dikembalikan dari fungsi lain. PHP sebenarnya sudah lama memiliki cara pembuatan yang setara — method Closure::fromCallable() — dan sintaks baru ini memberikan hasil identik dengan penulisan yang lebih ringkas:
<?php
$lewatSintaks = strlen(...);
$lewatMethod = Closure::fromCallable('strlen');
echo $lewatSintaks('PHP') . ' dan ' . $lewatMethod('PHP') . "\n";
var_dump($lewatSintaks instanceof Closure, $lewatMethod instanceof Closure);Output yang Dihasilkan:
3 dan 3
bool(true)
bool(true)Meneruskan Fungsi Bernama ke Fungsi Lain #
Medan utama pemakaian first-class callable adalah fungsi-fungsi bawaan yang menerima callback. Perbandingan berikut menunjukkan betapa dekatnya jarak antara cara lama dan cara baru — sekaligus betapa lebih jelasnya bentuk baru bagi mata:
<?php
$nama = ['budi', 'siti', 'andi'];
// Cara lama: nama fungsi sebagai string
$hasilLama = array_map('strtoupper', $nama);
// PHP 8.1: first-class callable
$hasilBaru = array_map(strtoupper(...), $nama);
echo implode(', ', $hasilLama) . "\n";
echo implode(', ', $hasilBaru) . "\n";Output yang Dihasilkan:
BUDI, SITI, ANDI
BUDI, SITI, ANDISintaks ini juga berlaku untuk method static. Kasus klasiknya adalah callback pembanding untuk usort(): sebelumnya kita menulis array ['PembandingHarga', 'bandingHarga'], kini cukup PembandingHarga::bandingHarga(...) — method pembandingnya sendiri memakai operator spaceship <=> yang mengembalikan -1, 0, atau 1 sesuai urutan dua nilai:
<?php
class PembandingHarga
{
public static function bandingHarga(array $a, array $b): int
{
return $a['harga'] <=> $b['harga'];
}
}
$produk = [
['nama' => 'Kopi', 'harga' => 25000],
['nama' => 'Teh', 'harga' => 8000],
['nama' => 'Gula', 'harga' => 15000],
];
usort($produk, PembandingHarga::bandingHarga(...));
foreach ($produk as $item) {
echo $item['nama'] . ': Rp' . $item['harga'] . "\n";
}Output yang Dihasilkan:
Teh: Rp8000
Gula: Rp15000
Kopi: Rp25000Baris usort($produk, PembandingHarga::bandingHarga(...)) membungkus method static bandingHarga menjadi closure, lalu usort() memanggil closure tersebut berulang kali untuk membandingkan pasangan elemen. Hasilnya produk terurut dari harga termurah karena <=> mengembalikan nilai negatif saat harga di sisi kiri lebih kecil daripada harga di sisi kanan.
Callable dari Method Instance #
Method instance diakses melalui objeknya: tulis $objek->method(...) dan closure yang terbentuk mengikat objek tersebut sebagai $this. Setelah dibuat sekali, closure dapat dipakai berulang kali tanpa perlu menyertakan objeknya lagi:
<?php
class Pemformat
{
public function rupiah(int $nilai): string
{
return 'Rp' . number_format($nilai, 0, ',', '.');
}
}
$format = new Pemformat();
$keRupiah = $format->rupiah(...);
echo $keRupiah(150000) . "\n";
$harga = [25000, 100000, 7500];
$daftarHarga = array_map($format->rupiah(...), $harga);
echo implode(' | ', $daftarHarga) . "\n";Output yang Dihasilkan:
Rp150.000
Rp25.000 | Rp100.000 | Rp7.500Perhatikan baris $keRupiah = $format->rupiah(...);: closure yang tersimpan mengingat objek $format, sehingga pemanggilan $keRupiah(150000) menjalankan method seolah ditulis $format->rupiah(150000) — method tetap melihat $format sebagai $this. Pemanggilan array_map($format->rupiah(...), $harga) memakai bentuk yang sama untuk mengubah seluruh daftar harga sekaligus. Di dalam class, notasi serupa juga berlaku untuk self::method(...) dan parent::method(...), sehingga seluruh bentuk callable kini punya satu gaya penulisan yang seragam.
Ringkasan #
Sintaks first-class callable namaCallable(...) mengubah fungsi global, method static, maupun method instance menjadi objek Closure yang dapat disimpan dan dikirim seperti nilai lain, dan setara dengan Closure::fromCallable(). Dibanding string atau array callable, bentuk ini gagal lebih cepat saat salah ketik, lebih mudah dilacak alat analisis, dan membuat seluruh callback di kode tampak seragam. Sejauh ini semua fungsi kita berjalan dari awal hingga akhir tanpa menoleh ke belakang; pada Chapter berikutnya kita mempelajari fungsi yang memanggil dirinya sendiri — Recursion
.