PHP'de generator (üreteç), değerleri tek seferde bir dizi olarak döndürmek yerine yield anahtar sözcüğüyle birer birer üreten fonksiyondur. İçinde yield geçen her fonksiyon otomatik olarak bir üreteç fonksiyonuna dönüşür; çağrıldığında gövdesini çalıştırmaz, bunun yerine bir Generator nesnesi döndürür. Siz bu nesneyi foreach ile dolaştıkça fonksiyon bir sonraki yield satırına kadar ilerler, değeri verir ve durumunu koruyarak bekler. Sonuç: milyonlarca satırlık bir veriyi belleğe almadan işleyebilir, Iterator arayüzünü elle uygulamadan özel döngü kaynakları yazabilirsiniz.
Bu yazıda üreteçlerin adım adım nasıl çalıştığını, anahtar ve değer üretmeyi, yield from ile başka bir üretece görev devretmeyi, send() ile üretece değer göndermeyi, getReturn() ile dönüş değeri almayı ve üreteçlerin sınırlarını PHP 8.5.12 ile çalıştırılmış örneklerle görüyorsunuz. Döngü sözdiziminin temelleri için PHP döngüler: for, foreach, while, tüm çağrı yığınını duraklatabilen daha güçlü bir yapı için PHP Fiber kullanımı yazısına bakabilirsiniz.
İlk üreteç: yield nasıl çalışır?

Aşağıdaki örnekte üreteç her adımda ne yaptığını yazdırıyor. Çıktıdaki sıralamaya dikkat edin; üretecin tembel (lazy) çalıştığını açıkça gösteriyor.
<?php
function sayac(int $bas, int $son): Generator
{
echo "[üreteç başladı]\n";
for ($i = $bas; $i <= $son; $i++) {
echo "[$i üretiliyor]\n";
yield $i;
}
echo "[üreteç bitti]\n";
}
$g = sayac(1, 3);
echo get_class($g), "\n"; // çıktı: Generator
foreach ($g as $sayi) {
echo "döngü aldı: $sayi\n";
}Generator
[üreteç başladı]
[1 üretiliyor]
döngü aldı: 1
[2 üretiliyor]
döngü aldı: 2
[3 üretiliyor]
döngü aldı: 3
[üreteç bitti]sayac(1, 3) çağrısı fonksiyonun tek bir satırını bile çalıştırmadı: önce sınıf adı yazıldı, "[üreteç başladı]" mesajı ancak foreach ilk değeri istediğinde göründü. Her yield bir değeri döngüye verir ve fonksiyonun yürütülmesini o noktada dondurur; yerel değişkenler (burada $i) korunur. Döngü bir sonraki değeri istediğinde fonksiyon kaldığı yerden devam eder. Gövdenin sonuna ulaşıldığında ya da return çalıştığında üreteç kapanır ve döngü, bir dizinin elemanları tükenmiş gibi biter.
Kılavuz bu yapıyı, Iterator arayüzünü uygulayan bir sınıf yazmanın getireceği ek yük ve karmaşıklık olmadan basit yineleyiciler yazmanın kolay yolu olarak tanımlar. Gerçekten de aynı işi bir sınıfla yapmak için current(), key(), next(), rewind() ve valid() metotlarını ayrı ayrı yazmanız gerekirdi.
Anahtar ve değer üretmek
Bir dizide olduğu gibi üreteç de değerle birlikte bir anahtar üretebilir. Sözdizimi ilişkisel dizi tanımına benzer: yield $anahtar => $deger;. Anahtar verilmezse PHP, sıradan bir listedeki gibi sıfırdan başlayan ardışık tamsayı anahtarlar atar.
<?php
function urunler(): Generator
{
yield 'TR-01' => 'Klavye';
yield 'TR-02' => 'Fare';
yield 'Monitör'; // anahtar verilmezse otomatik tamsayı
}
foreach (urunler() as $kod => $ad) {
echo "$kod: $ad\n";
}TR-01: Klavye
TR-02: Fare
0: MonitörÜçüncü değerin anahtarı 0 oldu, çünkü otomatik anahtar sayacı yalnızca anahtarsız yield ifadelerinde ilerler ve bu ilk anahtarsız değerdi. Argümansız bir yield; yazarsanız değer olarak null üretilir; bu, özellikle send() ile dışarıdan değer beklediğiniz durumlarda işe yarar.
Bellek avantajı: dizi yerine üreteç
Üreteçlerin en sık kullanılma nedeni bellektir. Kılavuz bunu range() örneğiyle anlatır: range(0, 1000000) tüm değerleri içeren bir dizi oluşturduğu için 10 MB'ın oldukça üzerinde bellek kullanır, aynı aralığı üreten bir üreteç ise yalnızca o anki durumunu tutar ve 1 kilobaytın altında yer kaplar. Aşağıdaki örnek bu farkı sayılarla değil, karşılaştırmayla doğruluyor:
<?php
function aralik(int $bas, int $son): Generator
{
for ($i = $bas; $i <= $son; $i++) {
yield $i;
}
}
$once = memory_get_usage();
$dizi = range(1, 1_000_000);
$diziBellek = memory_get_usage() - $once;
unset($dizi);
$once = memory_get_usage();
$gen = aralik(1, 1_000_000);
$toplam = 0;
foreach ($gen as $i) {
$toplam += $i;
}
$genBellek = memory_get_usage() - $once;
echo $toplam, "\n"; // çıktı: 500000500000
var_dump($diziBellek > 10 * 1024 * 1024); // çıktı: bool(true)
var_dump($genBellek < 1024 * 10); // çıktı: bool(true)Örnek bir milyon sayıyı her iki yolla da topluyor. Dizi yaklaşımı 10 MB'tan fazla bellek ayırırken üreteç yaklaşımı aynı toplamı 10 KB'ın altında bir bellek artışıyla hesaplıyor. Kesin değerler PHP derlemesine ve platforma göre değişir, bu yüzden örnek mutlak sayı yerine eşik karşılaştırması yazdırıyor. Bellek sınırına takılan betikler için Allowed memory size hatası yazısında başka nedenler de anlatılıyor; büyük veriyi parça parça işlemek bunların en etkili çözümlerinden biridir.
Dosyayı satır satır okumak ve işlem hattı kurmak

Üreteçler birbirine bağlanabilir: bir üretecin çıktısını başka bir üretece girdi olarak verirsiniz ve her biri bir sonraki değeri ancak istendiğinde hesaplar. Aşağıda bir üreteç günlük dosyasını satır satır okuyor, ikincisi yalnızca hata satırlarını süzüyor. Hiçbir aşamada dosyanın tamamı belleğe alınmıyor.
<?php
function satirlar(string $dosya): Generator
{
$f = fopen($dosya, 'r');
try {
while (($satir = fgets($f)) !== false) {
yield rtrim($satir, "\r\n");
}
} finally {
fclose($f);
echo "[dosya kapatıldı]\n";
}
}
function filtrele(iterable $kaynak, callable $kosul): Generator
{
foreach ($kaynak as $k => $v) {
if ($kosul($v)) {
yield $k => $v;
}
}
}
$dosya = __DIR__ . '/log.txt';
file_put_contents($dosya, "INFO açıldı\nERROR disk dolu\nINFO kapandı\nERROR zaman aşımı\n");
foreach (filtrele(satirlar($dosya), fn($s) => str_starts_with($s, 'ERROR')) as $no => $satir) {
echo ($no + 1), ". satır: $satir\n";
}
unlink($dosya);2. satır: ERROR disk dolu
4. satır: ERROR zaman aşımı
[dosya kapatıldı]İki ayrıntı dikkat çekici. Birincisi, filtrele() parametresini iterable olarak tanımlıyor; bu tip, kılavuza göre array|Traversable için yerleşik bir takma addır, yani fonksiyon hem dizi hem üreteç kabul eder. İkincisi, dosya finally bloğunda kapatılıyor. Üreteç sonuna kadar tüketildiğinde finally çalışır; döngü erken kesilir ve üreteç nesnesi yok edilirse de kılavuzun finally sayfasında anlatıldığı gibi üretecin içindeki finally bloğu çalıştırılır. Dosya fonksiyonlarının kendisi için PHP dosya okuma ve yazma yazısına bakabilirsiniz.
yield from ile görev devretmek
yield from, başka bir üretecin, bir Traversable nesnenin ya da bir dizinin tüm değerlerini dışarıdaki üreteç adına üretir. İç kaynak tükenince dış üreteç kaldığı yerden devam eder. İç kaynak bir üreteçse, yield from ifadesinin değeri o üretecin return ile döndürdüğü değerdir. Üretecin kendi dönüş değerine ise üreteç bittikten sonra getReturn() ile ulaşırsınız.
<?php
function ic(): Generator
{
yield 1;
yield 2;
return 'iç bitti';
}
function dis(): Generator
{
yield 0;
$sonuc = yield from ic();
echo "[ic() dönüşü: $sonuc]\n";
yield 3;
return 'dış bitti';
}
$g = dis();
foreach ($g as $k => $v) {
echo "$k => $v\n";
}
echo $g->getReturn(), "\n";
var_dump(iterator_to_array(dis(), false));0 => 0
0 => 1
1 => 2
[ic() dönüşü: iç bitti]
1 => 3
dış bitti
[ic() dönüşü: iç bitti]
array(4) {
[0]=>
int(0)
[1]=>
int(1)
[2]=>
int(2)
[3]=>
int(3)
}Çıktının ilk dört satırındaki anahtarlara bakın: 0, 0, 1, 1. Kılavuzun özellikle uyardığı nokta budur: yield from anahtarları sıfırlamaz, iç kaynağın anahtarlarını aynen aktarır. Dış üretecin kendi otomatik anahtar sayacı ise kaldığı yerden devam eder. foreach ile dolaşırken bu çoğu zaman sorun değildir, ama sonucu iterator_to_array() ile diziye dönüştürdüğünüzde aynı anahtarlar birbirinin üzerine yazar:
<?php
function ic(): Generator { yield 1; yield 2; }
function dis(): Generator { yield 0; yield from ic(); yield 3; }
print_r(iterator_to_array(dis()));Array
(
[0] => 1
[1] => 3
)Dört değer üretildiği halde dizide yalnızca iki eleman kaldı. Çözüm, önceki örnekte yapıldığı gibi iterator_to_array() fonksiyonunun ikinci parametresine false vermektir; bu durumda anahtarlar yok sayılır ve değerler sırayla toplanır.
send() ile üretece değer göndermek
Üreteçler yalnızca değer üretmez, dışarıdan değer de alabilir. yield bir ifade olarak kullanıldığında, send() ile gönderilen değer o ifadenin sonucu olur. send() değeri gönderir, üreteci bir sonraki yield ifadesine kadar ilerletir ve orada üretilen değeri döndürür.
<?php
function ortalama(): Generator
{
$toplam = 0;
$adet = 0;
$ort = null;
while (true) {
$deger = yield $ort;
if ($deger === null) {
return $adet;
}
$toplam += $deger;
$adet++;
$ort = $toplam / $adet;
}
}
$g = ortalama();
echo $g->send(10), "\n"; // çıktı: 10
echo $g->send(20), "\n"; // çıktı: 15
echo $g->send(60), "\n"; // çıktı: 30
$g->send(null);
echo 'Toplam değer sayısı: ', $g->getReturn(), "\n"; // çıktı: Toplam değer sayısı: 3Kılavuzun belirttiği önemli bir ayrıntı: üreteç henüz bir yield ifadesinde beklemiyorsa send() önce onu ilk yield noktasına kadar çalıştırır, sonra değeri gönderir. Bu yüzden PHP'de, bazı dillerde gerektiği gibi üreteci önceden bir next() çağrısıyla "hazırlamak" gerekmez. Örnekteki ilk send(10) çağrısı üreteci ilk yield satırına getirdi, 10 değerini oraya verdi ve döngünün bir sonraki turundaki yield ifadesinin ürettiği ortalamayı döndürdü. null gönderildiğinde üreteç return ile kapandı ve getReturn() işlenen değer sayısını verdi.
Benzer şekilde throw() metodu, üretecin beklediği yield noktasına bir istisna fırlatır; davranış, o yield ifadesinin yerinde bir throw deyimi varmış gibidir. Üreteç bu istisnayı kendi try/catch bloğuyla yakalayabilir. İstisnalar konusunun genel kuralları PHP try/catch yazısında anlatılıyor.
Üreteçler tek yönlüdür: tekrar dolaşamazsınız
Kılavuz üreteçlerin bedelini açıkça yazar: üreteçler yalnızca ileri giden yineleyicilerdir, yineleme başladıktan sonra geri sarılamazlar ve aynı üreteç birden fazla kez dolaşılamaz. Rastgele erişim de yoktur; beşinci elemana atlamak için ilk dördünü üretmeniz gerekir.
<?php
function harfler(): Generator
{
yield 'a';
yield 'b';
}
$g = harfler();
foreach ($g as $h) {
echo $h;
}
echo "\n";
try {
foreach ($g as $h) {
echo $h;
}
} catch (Exception $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}
$g2 = harfler();
try {
echo $g2->getReturn();
} catch (Exception $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}ab
Exception: Cannot traverse an already closed generator
Exception: Cannot get return value of a generator that hasn't returnedİlk foreach üreteci sonuna kadar tükettiği için ikinci döngü bir Exception fırlattı. Aynı verileri tekrar dolaşmanız gerekiyorsa üreteç fonksiyonunu yeniden çağırarak yeni bir nesne oluşturun ya da sonucu bir kez diziye alın. İkinci deneme de bir başka kuralı gösteriyor: getReturn() yalnızca üreteç return ile bittikten sonra çağrılabilir. rewind() metodu için de benzer bir sınır vardır: üreteç ilk yield noktasında duruyorsa bir şey yapmaz, ama bu noktanın ötesine geçmişse istisna fırlatır.
Generator sınıfının metotları

Üreteç fonksiyonunun döndürdüğü nesne, Iterator arayüzünü uygulayan final bir Generator sınıfının örneğidir ve new ile oluşturulamaz. foreach bu metotları sizin yerinize çağırır, ama gerektiğinde doğrudan da kullanabilirsiniz:
| Metot | Ne yapar |
|---|---|
| current() | O an üretilmiş değeri döndürür |
| key() | O an üretilmiş anahtarı döndürür |
| next() | Üreteci bir sonraki yield noktasına ilerletir |
| valid() | Üreteç hâlâ açıksa true, kapandıysa false döndürür |
| rewind() | İlk yield noktasına kadar çalıştırır; daha ileri gidilmişse istisna fırlatır |
| send($deger) | Değeri o anki yield ifadesine gönderir ve bir sonraki üretilen değeri döndürür |
| throw($istisna) | O anki yield noktasına istisna fırlatıp üreteci devam ettirir |
| getReturn() | Üreteç bittiyse return ile döndürülen değeri verir |
Bir fonksiyonun dönüş tipini bildirirken Generator, Iterator, Traversable veya iterable yazabilirsiniz. Kılavuz, dönüş tipi iterable olarak bildirilen fonksiyonların da üreteç olabileceğini özellikle belirtir. Çağıran tarafın yalnızca dolaşması gerekiyorsa iterable daha esnek bir sözleşmedir; send() veya getReturn() kullanılacaksa Generator yazmak niyeti açıkça gösterir. Tip bildirimlerinin genel kuralları PHP tip bildirimleri yazısında.
Uygulamalı örnek: kayıtları partiler halinde işlemek
Büyük bir veri kümesini tek tek değil, örneğin yüzerli gruplar halinde işlemek sık karşılaşılan bir ihtiyaçtır: toplu e-posta gönderimi, veritabanına toplu ekleme ya da bir dış servise sınırlı boyutta istek atmak gibi. Aşağıdaki parcala() üreteci herhangi bir iterable kaynağı alır ve değerleri belirtilen boyutta dizilere toplayarak üretir. Kaynak da bir üreteç olduğu için belleğe aynı anda yalnızca bir parti alınır.
<?php
function parcala(iterable $kaynak, int $boyut): Generator
{
$parti = [];
foreach ($kaynak as $deger) {
$parti[] = $deger;
if (count($parti) === $boyut) {
yield $parti;
$parti = [];
}
}
if ($parti) {
yield $parti; // artan son parti
}
}
function kayitlar(int $adet): Generator
{
for ($i = 1; $i <= $adet; $i++) {
yield ['id' => $i, 'eposta' => "uye$i@example.com"];
}
}
foreach (parcala(kayitlar(7), 3) as $no => $parti) {
$idler = implode(', ', array_column($parti, 'id'));
echo "Parti $no: ", count($parti), " kayıt (id: $idler)\n";
}Parti 0: 3 kayıt (id: 1, 2, 3)
Parti 1: 3 kayıt (id: 4, 5, 6)
Parti 2: 1 kayıt (id: 7)Örnekte dikkat edilmesi gereken nokta döngüden sonraki if ($parti) kontrolüdür. Kayıt sayısı parti boyutuna tam bölünmediğinde artan elemanlar döngü bittiğinde hâlâ $parti dizisindedir ve bu son yield olmadan kaybolurlar. Parti numaraları otomatik anahtarlardan geliyor ve sıfırdan başlıyor. Bu yapıyı PDO ile veritabanı yazısında anlatılan transaction kullanımıyla birleştirirseniz her partiyi tek bir transaction içinde yazabilirsiniz. array_column() gibi dizi fonksiyonları burada her partiye ayrı ayrı uygulanabiliyor, çünkü her parti sıradan bir PHP dizisidir.
Hiç değer üretmeyen üreteç
Bir fonksiyonun üreteç olup olmadığına çalışma anında değil, gövdesinde yield bulunup bulunmadığına bakılarak karar verilir. Bu yüzden hiç çalışmayacak bir yield satırı bile fonksiyonu üretece çevirir. Bu teknik, "boş bir yineleyici döndür" ihtiyacında kullanılabilir:
<?php
function bos(): Generator
{
return;
yield; // bu satır hiç çalışmaz ama fonksiyonu üreteç yapar
}
$g = bos();
var_dump($g instanceof Generator); // çıktı: bool(true)
var_dump(count(iterator_to_array($g))); // çıktı: int(0)
var_dump($g->valid()); // çıktı: bool(false)bool(true)
int(0)
bool(false)Aynı kural ters yönde bir tuzak da yaratır: normal bir fonksiyona sonradan bir yield eklediğinizde, fonksiyonun çağrıldığı her yerde dönüş değeri artık bir Generator nesnesi olur ve gövde ancak dolaşıldığında çalışır. Yalnızca yan etkisi için çağrılan (örneğin veritabanına yazan) bir fonksiyon böylece sessizce hiç çalışmaz hale gelebilir.
Sık yapılan hatalar
- Üreteci iki kez dolaşmak. İkinci
foreach"Cannot traverse an already closed generator" istisnası verir; yeni bir üreteç oluşturun. - iterator_to_array() ile anahtar çakışması.
yield fromkullanılan üreteçlerde ikinci parametreyifalseverin. - Gövdenin çağrı anında çalıştığını sanmak. Üreteç fonksiyonu çağrıldığında hiçbir kod çalışmaz; doğrulama ve istisnalar ilk değer istendiğinde ortaya çıkar.
- Bitmemiş üreteçte getReturn() çağırmak. Önce üreteci sonuna kadar tüketin.
- count() ile eleman sayısı almaya çalışmak. Üreteç
Countabledeğildir; saymak için dolaşmanız ya daiterator_count()kullanmanız gerekir, bu da üreteci tüketir. - Kaynağı finally olmadan açık bırakmak. Döngü erken kesilebileceği için dosya veya bağlantı kapatma kodunu
finallybloğuna koyun.
Sık Sorulan Sorular
Üreteç ile dizi döndüren fonksiyon arasında ne zaman seçim yapmalıyım?
Veri büyükse, tamamına aynı anda ihtiyaç yoksa ya da yalnızca bir kez sırayla dolaşılacaksa üreteç uygundur. Veriyi birden fazla kez dolaşacak, sıralayacak, sayacak veya indeksle erişecekseniz dizi daha pratiktir. Dizi fonksiyonları için array_map ve array_filter yazısına bakabilirsiniz.
Üreteç ile Fiber arasındaki fark nedir?
Üreteç yalnızca kendi gövdesindeki yield noktalarında durabilir ve dönüş tipi bir Generator olmak zorundadır. Fiber ise kendi çağrı yığınına sahiptir ve iç içe çağrılmış herhangi bir fonksiyonun içinden askıya alınabilir. Ayrıntılı karşılaştırma PHP Fiber yazısında.
Üretecin içinde return kullanabilir miyim?
Evet. return üreteci kapatır; döndürdüğü değer döngüye verilmez ama getReturn() ile okunabilir ve yield from ifadesinin sonucu olur.
Referansla değer üretmek mümkün mü?
Evet. Fonksiyon adının önüne & koyarak (function &uret()) referans üretebilir, döngüde foreach ($g as &$v) ile değişkeni değiştirdiğinizde üretecin içindeki değeri de değiştirebilirsiniz. Referansların genel davranışı PHP referanslar yazısında anlatılıyor.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: PHP öğrenme rehberi · PHP Fiber kullanımı · PHP SPL veri yapıları · PHP dosya okuma · PHP döngüler