Generator adalah fungsi yang menghasilkan nilainya satu per satu: dia berhenti sebentar di tiap yield, lalu lanjut saat nilai berikutnya diminta. Bayangin dua cara dapur Warung Bu Tini bekerja. Cara pertama: pagi-pagi memasak 1.000 porsi sekaligus, dan raknya penuh sebelum pembeli pertama datang. Cara kedua: memasak satu porsi setiap ada pesanan, jadi dapur cuma memegang satu piring. Fungsi yang mengembalikan array itu cara pertama. Generator itu cara kedua.
🧪 Tebakan cepat:
range(1, 1_000_000)membuat array berisi sejuta angka. Kira-kira berapa memory yang dipakainya: sekitar 1 MB, 16 MB, atau 100 MB? Dan generator yang menghasilkan angka yang sama? Tebak dulu, jawabannya ada di bagian "Memory: array lawan generator".
yield: nilai satu per satu
<?php
function antrean(): Generator {
echo "[dapur buka]\n";
yield "Nasi Goreng";
echo "[lanjut]\n";
yield "Es Teh";
echo "[dapur tutup]\n";
}
$gen = antrean();
echo "generator dibuat\n";
foreach ($gen as $menu) {
echo "dapat: $menu\n";
}
generator dibuat
[dapur buka]
dapat: Nasi Goreng
[lanjut]
dapat: Es Teh
[dapur tutup]
- Fungsi yang berisi
yieldadalah generator. Memanggilnya gak menjalankan isinya:antrean()cuma mengembalikan objekGenerator, makanya "generator dibuat" tercetak duluan. foreach(dari Loop di PHP) meminta nilai pertama. Fungsinya jalan sampaiyield "Nasi Goreng", menyerahkan nilai itu, lalu berhenti di situ.- Putaran berikutnya melanjutkan dari baris sesudah
yield, sampaiyieldberikutnya. Saat fungsinya selesai,foreachjuga selesai. - Tipe kembaliannya ditulis
Generator.
Memory: array lawan generator
function angka(int $n): Generator {
for ($i = 1; $i <= $n; $i++) {
yield $i;
}
}
$awal = memory_get_usage();
$data = range(1, 1_000_000); // versi kedua: $data = angka(1_000_000);
$total = 0;
foreach ($data as $x) {
$total += $x;
}
echo memory_get_usage() - $awal;
memory_get_usage() mengembalikan byte yang sedang dipakai PHP. Diukur di PHP 8.4:
| Jumlah angka | range() (array) |
angka() (generator) |
|---|---|---|
| 1.000 | 20.536 byte | 320 byte |
| 100.000 | 2.113.616 byte | 320 byte |
| 1.000.000 | 16.793.680 byte (sekitar 16 MB) | 320 byte |
Jadi jawaban tebakannya: sekitar 16 MB untuk array, dan cuma 320 byte untuk generator, berapa pun jumlahnya, karena generator cuma memegang satu angka setiap saat. range(1, 10_000_000) bahkan gagal di batas memory bawaan 128 MB: PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 268435464 bytes).
Baca file besar baris per baris
Kegunaan paling nyata: file yang terlalu besar untuk dimuat sekaligus, misalnya penjualan.csv berisi sejuta baris penjualan (25,4 MB):
function bacaBaris(string $path): Generator {
$file = fopen($path, "r");
while (($baris = fgets($file)) !== false) {
yield trim($baris);
}
fclose($file);
}
$total = 0;
foreach (bacaBaris("penjualan.csv") as $baris) {
$total += (int) explode(",", $baris)[2];
}
fopenmembuka file,fgetsmembaca satu baris per panggilan, danfalseberarti file sudah habis.- Tiap baris di-
yieldbegitu dibaca, dipakai, lalu dibuang sebelum baris berikutnya dibaca. - Diukur dengan
memory_get_peak_usage(): versi generator memuncak di 445.912 byte (0,45 MB). Versi yang mengumpulkan semua baris ke array dulu memuncak di 70.012.040 byte (70 MB).
Ide yang sama ada di fetch() milik PDO yang mengambil satu baris per panggilan, dan di Laravel lewat LazyCollection dan cursor().
Deret tanpa ujung
Karena nilainya baru dibuat saat diminta, generator boleh gak pernah selesai. Yang menghentikannya pemakainya:
function nomorAntrean(): Generator {
$nomor = 1;
while (true) {
yield $nomor++;
}
}
foreach (nomorAntrean() as $n) {
if ($n > 3) {
break;
}
echo "Antrean $n\n";
}
// Antrean 1, Antrean 2, Antrean 3
Deret Fibonacci juga cocok ditulis begini: mulai dari 0 dan 1, dan tiap angka berikutnya adalah jumlah dua angka sebelumnya, jadi 0 1 1 2 3 5 8 13. Simpan dua angka terakhir di variabel, yield yang pertama, lalu geser keduanya. Batasi dengan parameter $n dan loop for, supaya generatornya berhenti sendiri sesudah $n angka.
💡 Key, yield from, dan send
function hargaMenu(): Generator {
yield "Nasi Goreng" => 15000;
yield "Es Teh" => 5000;
}
foreach (hargaMenu() as $nama => $harga) {
echo "$nama: $harga\n"; // Nasi Goreng: 15000, lalu Es Teh: 5000
}
yield kunci => nilaimemberi key sendiri. Tanpa itu key-nya 0, 1, 2...yield from makanan();menyerahkan semua nilai generator lain di tempat itu. Key-nya ikut dari generator itu, jadi bisa dobel:iterator_to_array()atasyield from makanan()(Nasi Goreng, Mie Ayam) laluyield "Es Teh"mengembalikan["Es Teh","Mie Ayam"], karena Es Teh menimpa key 0. Tulisiterator_to_array($gen, false)untuk mendapat["Nasi Goreng","Mie Ayam","Es Teh"].$gen->send(15000)mengirim nilai masuk ke generator: nilai itu jadi hasil ekspresiyieldyang sedang menunggu. Kasir yang ditulis$harga = yield $total; $total += $harga;mencetak 0, lalu 15000 setelahsend(15000), lalu 20000 setelahsend(5000).
⚠️ Jebakan yang sering ketemu
- Dipakai dua kali. Generator cuma bisa dijalani sekali.
foreachkedua atas objek yang sama melemparException: Cannot traverse an already closed generator. Panggil fungsinya lagi untuk generator baru. count()atas generator. Ditolak:TypeError: count(): Argument #1 ($value) must be of type Countable|array, Generator given. Generator gak tahu jumlahnya sampai habis dijalani.- Mengira isinya jalan saat dipanggil.
antrean();sendirian gak mencetak apa pun. Isinya baru jalan saat nilainya diminta. - Generator untuk data kecil. Lima menu yang perlu
$menu[2]dancount()lebih cocok array biasa.
TL;DR: Fungsi yang berisi yield adalah generator: memanggilnya cuma membuat objek Generator, dan isinya jalan sedikit demi sedikit setiap foreach meminta nilai berikutnya, berhenti di tiap yield. Keunggulan utamanya memory: sejuta angka dari range() memakan 16.793.680 byte, generator yang sama cuma 320 byte, dan file besar bisa dibaca baris per baris dengan fgets plus yield. yield kunci => nilai memberi key, yield from meneruskan generator lain (awas key dobel di iterator_to_array), dan send() mengirim nilai masuk. Generator cuma bisa dijalani sekali dan gak bisa di-count().