Dari pure function, immutability, closure, hingga pipeline map-filter-reduce — semua bahan kini sudah tergeletak di meja. Satu teknik tersisa yang mengikat semuanya: function composition, seni merangkai fungsi kecil menjadi fungsi besar tanpa menulis perekat yang sama berulang-ulang. Idenya sederhana: keluaran fungsi pertama menjadi masukan fungsi kedua, keluaran kedua masuk ke ketiga, dan seterusnya — persis seperti pipeline pada chapter sebelumnya, tetapi kini kita bangun alat rakitannya sendiri. PHP tidak menyediakan compose() bawaan, tetapi dengan closure, helper seperti itu hanya beberapa baris.
compose: Dua Fungsi Jadi Satu #
Blok bangunan paling dasar bernama compose(): menerima dua fungsi, mengembalikan satu fungsi gabungan.
<?php
// compose(f, g) mengembalikan closure yang menjalankan g dulu, lalu f.
function compose(callable $f, callable $g): callable
{
return fn (mixed $x): mixed => $f($g($x));
}
$jadikanKodeProduk = compose(
fn (string $s): string => strtoupper($s),
fn (string $s): string => str_replace(' ', '_', $s),
);
echo $jadikanKodeProduk('kopi gayo premium') . "\n";Output yang Dihasilkan:
KOPI_GAYO_PREMIUMMari kita bedah urutan kerjanya:
compose($f, $g)mengembalikan closure yang menjalankan$glebih dulu, lalu hasilnya diumpankan ke$f— sesuai notasi matematikaf∘g(x) = f(g(x)).- Tahap
$gmengganti spasi dengan garis bawah:'kopi gayo premium'menjadi'kopi_gayo_premium'. - Tahap
$fmengapitalkan seluruh hurufnya, sehingga keluaran akhirnyaKOPI_GAYO_PREMIUM. $jadikanKodeProdukkini berfungsi seperti satu fungsi utuh — bisa dipanggil, dioper kearray_map(), atau dirangkai lagi dengan fungsi lain.
Urutan “argumen kedua dijalankan lebih dulu” memang konvensi matematika, tetapi jujur saja arah bacanya membingungkan manusia. Kabar baiknya ada saudaranya yang lebih ramah.
pipe: Pipeline yang Dibaca Seperti Kalimat #
pipe() adalah compose() yang dibalik arahnya: tahap dieksekusi dari kiri ke kanan, persis urutan bacanya. Versi ini juga menerima jumlah tahap tak terbatas lewat parameter variadic:
<?php
function pipe(callable ...$tahap): callable
{
return function (mixed $masukan) use ($tahap): mixed {
$hasil = $masukan;
foreach ($tahap as $fn) {
$hasil = $fn($hasil);
}
return $hasil;
};
}
$hitungDiskonMember = fn (float $total): float => $total * 0.9;
$tambahOngkir = fn (float $total): float => $total + 20000;
$formatRupiah = fn (float $total): string => 'Rp' . number_format($total, 0, ',', '.');
$ringkasBelanja = pipe($hitungDiskonMember, $tambahOngkir, $formatRupiah);
echo $ringkasBelanja(500000) . "\n";
echo $ringkasBelanja(1250000) . "\n";Output yang Dihasilkan:
Rp470.000
Rp1.145.000Mari kita amati cara kerjanya pada input 500000:
pipe(...)membungkus seluruh tahap ke dalam satu closure; array$tahaptertangkap sebagai lingkungan closure, persis materi Chapter Closures .$hitungDiskonMembermengalikan0.9menghasilkan450000, lalu$tambahOngkirmenambah20000menjadi470000, akhirnya$formatRupiahmemformatnya menjadiRp470.000.$ringkasBelanjaadalah fungsi baru yang lahir dari tiga fungsi kecil — masing-masing tetap bisa dipakai sendiri, dan kombinasinya bisa dipanggil berulang dengan masukan berbeda.
Chapter array_map, array_filter, dan array_reduce
sudah memperkenalkan buatPipeline() versi ringkas yang dibangun di atas array_reduce(); pipe() di sini adalah generalisasi formalnya. Bagi yang pernah memakai collection di framework, bentuk ini terasa seperti method chaining ->filter()->map() — hanya saja tanpa objek pembungkus, murni fungsi bertumpuk fungsi.
Menjaga Komposisi Tetap Terbaca #
Komposisi yang cerdas bisa cepat berubah menjadi teka-teki. Beberapa kebiasaan menjaganya tetap jernih: pastikan setiap tahap menerima satu masukan dan menghasilkan satu keluaran; beri nama yang bercerita ($tambahOngkir, bukan $fn2); jaga setiap tahap kecil; dan jangan malu memakai variabel perantara bernama. Contoh terakhir ini sengaja tidak memakai pipe() sama sekali:
<?php
$penjualan = [
['produk' => 'Kopi Gayo', 'nilai' => 170000, 'lunas' => true],
['produk' => 'Gula Aren', 'nilai' => 100000, 'lunas' => false],
['produk' => 'Teh Melati', 'nilai' => 135000, 'lunas' => true],
];
// Langkah bernama: mudah dibaca, mudah diperiksa satu per satu.
$yangLunas = array_filter($penjualan, fn (array $j): bool => $j['lunas']);
$nilaiLunas = array_column($yangLunas, 'nilai');
$totalLunas = array_sum($nilaiLunas);
echo "Total penjualan lunas: Rp$totalLunas\n";Output yang Dihasilkan:
Total penjualan lunas: Rp305000Tiga baris perantara tersebut membentuk pipeline yang sama seperti pipe(array_filter(...), 'array_column', 'array_sum') — hanya saja setiap tahap kini punya label yang bisa dibaca penyelidik: $yangLunas bisa di-var_dump() saat hasil aneh, $nilaiLunas bisa diperiksa isinya, dan array_column serta array_sum bahkan dipanggil tanpa closure sama sekali. Komposisi eksplisit dengan variabel bernama seringkali menjadi titik manis antara keringkasan dan keterlacakan.
Kapan Komposisi Menyulitkan #
Kejujuran bagian ini sama pentingnya dengan tekniknya. Pertama, debugging: pipeline panjang menyembunyikan nilai antara — saat keluaran salah, kita harus membongkar rantai atau sementara menyisipkan var_dump() di tengah tahap. Kedua, tipe: mixed pada helper generik memangkas sebagian jaminan tipe; pipeline float -> float -> string aman karena kita menuliskannya secara eksplisit, tetapi rantai campuran array -> string -> array mudah tersandung. Ketiga, tim: tidak semua pembaca PHP nyaman dengan compose(compose(...)) berlapis — kode yang tak terbaca rekan satu tim adalah kode yang mahal. Terakhir, ada saatnya metode pada value object (seperti pola wither di Chapter Immutability
) membaca lebih alami daripada rantai fungsi lepas. Komposisi adalah alat untuk pengulangan yang nyata, bukan tujuan estetika.
Ringkasan #
compose() merangkai dua fungsi dari kanan ke kiri mengikuti konvensi matematika; pipe() membaliknya menjadi kiri-ke-kanan dan menerima tahap tak terbatas lewat parameter variadic. Fungsi kecil yang murni dan satu-masukan-satu-keluaran adalah bahan bakunya, sedangkan variabel perantara bernama tetap sah sebagai bentuk komposisi eksplisit yang mudah diperiksa. Dengan ini perangkat fungsional kita lengkap — tinggal satu pertanyaan besar yang tersisa: bagaimana gaya ini berdiri di hadapan object-oriented programming yang selama ini menjadi wajah utama PHP? Chapter penutup menjawabnya secara jujur.