# PHP 5.5 generator (yield) ile büyük veri kümelerini gezmek

> Belleği şişirmeden büyük döngüler kurmanın dil düzeyindeki yolu: PHP 5.5 generator ve yield kullanımı.

- Yayın: 2014-12-07
- Kategori: Diller
- Etiketler: PHP
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/php-55-generator-yield-ile-buyuk-veri-kumelerini-gezmek/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Bu yıl [Laravel](/blog/laravel-4-ile-ilk-projeme-baslarken), [Composer](/blog/composera-gecis-php-bagimliliklarini-elle-yonetmeyi-birakmak) ve [Git](/blog/gite-gecis-ftp-ile-dosya-atmayi-birakmak) gibi araçlarla geçti; son yazıda dil düzeyinde bir özelliğe odaklanmak istedim. PHP 5.5 ile gelen **generator (üreteç)** ve `yield` anahtar kelimesini keşfettim; büyük veri kümeleriyle çalışırken işe nasıl yaradığını paylaşmak istiyorum.

## Problem: Büyük dizi bellekte şişmek

Diyelim ki bir CSV dosyasında 500.000 satır var ve bunları işleyeceğim. Saf yaklaşım her şeyi diziye almak:

```php
<?php
function csvSatirlariniOku($dosya)
{
    $satirlar = [];
    $f = fopen($dosya, 'r');
    while (($satir = fgetcsv($f)) !== false) {
        $satirlar[] = $satir;
    }
    fclose($f);
    return $satirlar;
}

$tumSatirlar = csvSatirlariniOku('buyuk.csv');
foreach ($tumSatirlar as $satir) {
    // işle
}
```

Bu kod 500.000 satırın tamamını belleğe (memory) yüklüyor. Küçük dosyalarda sorun yok. Büyük dosyada `memory_limit` aşımıyla karşılaşabilirsiniz.

Bunu ilk gerçek proje üzerinde gördüm: bir müşterinin aktarım sisteminde günlük CSV dosyaları 300-400 bin satıra çıkıyordu. Script yarı yolda `Fatal error: Allowed memory size exhausted` ile duruyordu. `memory_limit` ini değerini artırmak çözüm gibi görünse de bu sadece sorunu erteliyordu; dosya boyutu artmaya devam edecekti.

## Generator nedir?

**Generator (Üreteç)**, değerleri hepsini bir arada hesaplamak yerine birer birer, talep üzerine üreten bir fonksiyon türüdür. Normal bir fonksiyondan farkı `return` yerine `yield` anahtar kelimesini kullanmasıdır. Bir değeri üretir, duraklar; sonraki çağrıda kaldığı yerden devam eder.

PHP 5.5 ile dile eklendi.

## yield ile aynı örnek

```php
<?php
function csvSatirlariniOkuGenerator($dosya)
{
    $f = fopen($dosya, 'r');
    while (($satir = fgetcsv($f)) !== false) {
        yield $satir;
    }
    fclose($f);
}

foreach (csvSatirlariniOkuGenerator('buyuk.csv') as $satir) {
    // Her iterasyonda bellekte yalnızca bir satır var
}
```

Görünüşte küçük bir fark; ama çalışma biçimi çok farklı. `yield` her çalıştığında o anki `$satir` değerini dışarıya veriyor ve fonksiyon duruyor. `foreach` bir sonraki değeri istediğinde fonksiyon kaldığı yerden devam ediyor. Bellekte 500.000 satır yerine hep tek bir satır var.

## Sayı aralığı örneği

Generator'ın ne işe yaradığını görmek için sayı dizisi üretici basit bir örnek:

```php
<?php
function aralik($baslangic, $bitis)
{
    for ($i = $baslangic; $i <= $bitis; $i++) {
        yield $i;
    }
}

foreach (aralik(1, 1000000) as $sayi) {
    echo $sayi . "\n";
}
```

PHP'nin yerleşik `range(1, 1000000)` fonksiyonu 1 milyonluk bir dizi oluşturur ve tamamını belleğe yükler. Generator versiyonu ise her adımda yalnızca o anki sayıyı tutar.

## Generator nesnesi

Generator fonksiyonu çağrıldığında hemen çalışmaz; bir `Generator` nesnesi döner. Bu nesneyi `foreach` ile döngüye sokmak da mümkün, ya da manuel kontrol etmek de:

```php
<?php
function ureticim()
{
    yield 'birinci';
    yield 'ikinci';
    yield 'ucuncu';
}

$uretec = ureticim();
echo $uretec->current(); // birinci
$uretec->next();
echo $uretec->current(); // ikinci
```

`current()` mevcut değeri, `next()` bir sonraki adıma geçişi sağlıyor. Pratikte bu düzeyde manuel kontrol nadiren gerekiyor; `foreach` çoğu durumda yeterli.

## yield ile anahtar-değer çiftleri

Normal dizi gibi anahtar-değer üretmek de mümkün:

```php
<?php
function veriUret()
{
    yield 'ad'    => 'Ahmet';
    yield 'email' => 'ahmet@ornek.com';
    yield 'yas'   => 30;
}

foreach (veriUret() as $anahtar => $deger) {
    echo "{$anahtar}: {$deger}\n";
}
```

## Dikkat: generator yeniden başlatılamaz

Bir generator'ı yalnızca bir kez gezeblirsiniz. `foreach` döngüsü tamamlandıktan sonra aynı generator nesnesini yeniden döngüye sokmaya kalkışırsanız boş geçer; değerler yeniden üretilmez. Eğer aynı veriyi iki kez işlemeniz gerekiyorsa fonksiyonu yeniden çağırmanız ve yeni bir generator nesnesi almanız gerekiyor. Bu, sıradan diziden temel farkı; unutunca sessizce hatalı davranış üretebilir.

## Ne zaman kullanmalı?

Her yerde generator kullanmak şart değil. Veri kümesi sınırlı ve belleğe rahatça sığıyorsa normal dizi daha basit.

Generator işe yarıyor:
- Büyük dosyaları satır satır işlerken
- [Veritabanından](/blog/eloquent-orm-ile-veritabani-islemlerini-sadestirmek) çok sayıda kayıt çekerken ve hepsine aynı anda ihtiyaç olmadığında
- Sonsuz veya çok büyük bir sırayla çalışırken (API sayfalaması, akış verisi)

Bu özelliği öğrenmek için somut bir ihtiyacım oldu: bir projede günlük CSV içe aktarma işlemi bellek sınırına takılıyordu. Generator ile çözdüm; şimdi ne zaman büyük veri döngüsü yazacağım aklımın bir köşesinde bu seçenek duruyor.

2014'ün son yazısı olarak biraz farklı bir konu seçmek istedim. Bir sonraki yıl Laravel'in yeni sürümüyle devam edeceğim.
