Loop foreach adalah roti harian PHP — dan tetap sah-sah saja. Masalahnya bukan loop itu sendiri, melainkan loop yang menumpuk tiga pekerjaan sekaligus: memilih elemen yang relevan, mentransformasikan isinya, lalu mengakumulasi hasil, sambil menimpa variabel di sepanjang jalan. Trilogi array_map(), array_filter(), dan array_reduce() memisahkan ketiga pekerjaan itu sehingga niat kode terbaca dari strukturnya. Referensi lengkap ketiga fungsi — urutan parameter, perilaku kunci array, varian array_map() multi-array — sudah tersedia di Chapter array_map, array_filter, dan array_reduce
pada Section Arrays dan Collections; chapter ini fokus pada sudut paradigmanya: berpikir dalam pipeline.
Dari foreach ke Pipeline #
Mari kita mulai dengan satu masalah yang dikerjakan dua cara: menjumlahkan subtotal pesanan yang harganya minimal 50000.
<?php
$pesanan = [
['nama' => 'Kopi Gayo', 'harga' => 85000, 'jumlah' => 2],
['nama' => 'Gula Aren', 'harga' => 25000, 'jumlah' => 1],
['nama' => 'Teh Melati', 'harga' => 45000, 'jumlah' => 3],
];
// Versi loop: tiga keputusan (pilih, hitung, akumulasikan) bercampur.
$totalLoop = 0;
foreach ($pesanan as $item) {
if ($item['harga'] >= 50000) {
$totalLoop += $item['harga'] * $item['jumlah'];
}
}
echo "Total versi loop: $totalLoop\n";
// Versi pipeline: filter dulu, lalu reduce.
$totalPipeline = array_reduce(
array_filter(
$pesanan,
fn (array $item): bool => $item['harga'] >= 50000
),
fn (int $total, array $item): int => $total + $item['harga'] * $item['jumlah'],
0
);
echo "Total versi pipeline: $totalPipeline\n";Output yang Dihasilkan:
Total versi loop: 170000
Total versi pipeline: 170000Kedua versi menghasilkan 170000 (hanya Kopi Gayo yang lolos syarat harga: 85000 x 2). Bedanya ada pada cara membacanya. Versi loop memaksa pembaca menjalankan kode di kepala untuk mengetahui mana syarat, mana perhitungan, mana akumulasi; versi pipeline menamai setiap tahap langsung dari strukturnya — array_filter() menjawab “elemen mana yang relevan?”, array_reduce() menjawab “bagaimana meringkasnya?”. Versi pipeline juga selaras dengan chapter Immutability
: tidak ada satu pun variabel yang ditimpa bertahap; data mengalir dari satu tahap ke tahap berikutnya sebagai nilai baru.
Trilogi dengan Satu Pertanyaan Masing-masing #
Ketiga fungsi ini bukan sekadar pengganti sintaksis — masing-masing menjawab satu pertanyaan baku:
| Fungsi | Pertanyaan yang dijawab | Bentuk hasil |
|---|---|---|
array_map() | “Setiap elemen menjadi apa?” | array sepanjang input |
array_filter() | “Elemen mana yang lolos?” | array sepanjang itu atau lebih pendek |
array_reduce() | “Seluruh array diringkas menjadi apa?” | satu nilai bertipe apa pun |
Ketiganya bekerja berpasangan pada data ujian kelas:
<?php
$nilaiUjian = [78, 92, 65, 88, 54];
// map: mentransformasikan setiap elemen.
$kategori = array_map(
fn (int $nilai): string => $nilai >= 80 ? 'A' : ($nilai >= 70 ? 'B' : ($nilai >= 60 ? 'C' : 'D')),
$nilaiUjian
);
// filter: memilih elemen yang lolos syarat.
$yangLulus = array_filter($nilaiUjian, fn (int $nilai): bool => $nilai >= 70);
// reduce: meringkas seluruh array menjadi satu nilai.
$totalSkor = array_reduce(
$nilaiUjian,
fn (int $total, int $nilai): int => $total + $nilai,
0
);
echo 'Kategori: ' . implode(', ', $kategori) . "\n";
echo 'Lulus: ' . implode(', ', $yangLulus) . "\n";
echo 'Total: ' . $totalSkor . "\n";Output yang Dihasilkan:
Kategori: B, A, C, A, D
Lulus: 78, 92, 88
Total: 377Perhatikan bentuk hasilnya sesuai tabel: lima nilai masuk array_map(), lima kategori keluar (78 menjadi B, 88 menjadi A, dan seterusnya — satu masukan tepat satu keluaran). array_filter() menerima lima nilai namun hanya menyisakan tiga yang lolos ambang 70. array_reduce() merapatkan kelimanya menjadi satu angka 377 lewat callback dua-parameter: $total sebagai akumulator yang berjalan dan $nilai sebagai elemen saat ini, dengan nilai awal 0 sebagai parameter ketiga.
Membaca dari Kiri ke Kanan #
Panggilan fungsi bersarang punya satu kelemahan tua: dibaca dari dalam ke luar, berkebalikan dengan arah baca manusia. Bandingkan dua ekspresi berikut:
<?php
// Pipeline ringkas: dibangun dari tahap-tahap berurutan.
function buatPipeline(callable ...$tahap): callable
{
return fn (mixed $masukan): mixed => array_reduce(
$tahap,
fn (mixed $hasil, callable $tahapFn): mixed => $tahapFn($hasil),
$masukan
);
}
// Panggilan bersarang dibaca dari DALAM ke luar — melelahkan.
$slugBersarang = str_replace(' ', '-', strtolower(trim(' Belajar PHP Fungsional ')));
// Pipeline dibaca dari kiri ke kanan, seperti cerita.
$slugPipeline = buatPipeline(
fn (string $s): string => trim($s),
fn (string $s): string => strtolower($s),
fn (string $s): string => str_replace(' ', '-', $s),
)(' Belajar PHP Fungsional ');
echo $slugBersarang . "\n";
echo $slugPipeline . "\n";Output yang Dihasilkan:
belajar-php-fungsional
belajar-php-fungsionalKedua ekspresi menghasilkan belajar-php-fungsional, tetapi $slugBersarang menuntut pembaca melompat: cari panggilan terdalam (trim), naik ke strtolower, baru str_replace. Versi pipeline menyusun tahapan dalam urutan baca alami: rapikan spasi, jadikan huruf kecil, ganti spasi dengan tanda hubung. Menariknya, buatPipeline() sendiri dibangun dari array_reduce() — daftar tahap dipadatkan menjadi satu closure, dengan akumulator berupa hasil tahap sebelumnya. Ini contoh meta dari kekuatan reduce, dan juga bibit dari function composition yang kita bahas tuntas pada Chapter Function Composition
.
Kapan Loop Masih Lebih Jelas #
Kejujuran penting di sini: pipeline bukan selalu pemenangnya. Kelemahan pertama adalah early exit — array_filter() memindai seluruh array meskipun jawaban sudah ketemu di elemen kedua:
<?php
$stokProduk = [
['nama' => 'Kopi Gayo', 'stok' => 12],
['nama' => 'Gula Aren', 'stok' => 0],
['nama' => 'Teh Melati', 'stok' => 0],
];
// array_filter memindai SELURUH array meski jawaban sudah ditemukan.
$semuaHabis = array_filter($stokProduk, fn (array $p): bool => $p['stok'] === 0);
echo count($semuaHabis) . " produk kehabisan stok\n";
// PHP 8.4: array_find() berhenti di elemen pertama yang cocok.
$habisPertama = array_find($stokProduk, fn (array $p): bool => $p['stok'] === 0);
echo "Habis pertama: {$habisPertama['nama']}\n";
// Untuk logika keluar-dini yang lebih kompleks, loop tetap paling jernih.
foreach ($stokProduk as $indeks => $p) {
if ($p['stok'] === 0) {
echo "Loop berhenti di indeks $indeks\n";
break;
}
}Output yang Dihasilkan:
2 produk kehabisan stok
Habis pertama: Gula Aren
Loop berhenti di indeks 1Ketiganya sah: array_filter() tepat ketika kita memang butuh semua kecocokan, array_find() (baru hadir di PHP 8.4) menutup celah “cari yang pertama”, dan loop dengan break tetap paling jernih ketika kondisi keluarnya berlapis atau perlu efek tambahan di tengah jalan. Selain kasus keluar-dini, loop juga lebih wajar ketika akumulasi butuh banyak status paralel, atau ketika data sangat besar sehingga lebih hemat dievaluasi malas lewat generator (yield) alih-alih dibangun jadi array penuh.
Ringkasan #
array_map() mentransformasikan, array_filter() memilih, array_reduce() meringkas — ketiganya menggantikan pola loop yang menumpuk keputusan dengan pipeline yang membaca dari kiri ke kanan. Helper seperti buatPipeline() (yang didorong array_reduce() sendiri) memulihkan arah baca alami pada panggilan bersarang. Namun loop tetap unggul untuk keluar-dini yang kompleks dan evaluasi malas — paradigma adalah alat, bukan kewajiban. Pipeline kita selama ini sering memanggil fungsi bernama seperti strtoupper; PHP 8.1 bahkan memberi sintaks khusus untuk mengoper fungsi itu sebagai nilai, topik first-class callable pada chapter berikutnya.