PHPKonf Istanbul PHP Conference 2019 - Call for Papers

Generator sözdizimi

Bir üreteç işlevi, normal bir işlev gibi görünür fakat bir üreteç tek bir değer döndürmek yerine ihtiyaç duyulduğu kadar çok değer üretir (yield).

Bir üreteç işlevi çağrıldığında üzerinde yineleme yapılabilecek bir nesne döndürür. Bu nesne üzerinde yineleme yaptığınızda (örneğin foreach ile), PHP bir değere her ihtiyaç duyuşunda nesnenin yineleme yöntemlerini çağırır ve üreteç bir değer ürettiğinde üretecin durumunu kaydeder, böylece yeni bir değere her ihtiyaç duyuluşunda üreteç kaldığı yerden devam edebilir.

Üretilecek değer kalmadığında, üreteç basitçe çıkar ve adeta bir dizi değerlerini tüketmiş gibi kod çağrılmaya devam eder.

Bilginize:

PHP 5'te, bir üreteç bir değer döndüremezdi: bu bir derleme hatasıyla sonuçlanırdı. Üreteç içinde geçerli sözdizimi boş bir return deyimi idi ve üreteci sonlandırırdı. PHP 7.0 itibariyle, üreteç değerleri döndürebilmekte ve Generator::getReturn() kullanarak bunlar alınabilmektedir.

yield sözcüğü

Üreteç işlevinin kalbi yield sözcüğüdür. En basit halinde, bir yield deyimi çoğunlukla bir return deyimi gibi görünür. İşlevin çalışmasını durdurmak ve değer döndürmek yerine, yield, üreteç üzerindeki kod döngüsüne bir değer verir ve üreteç işlevini bekletir.

Örnek 1 - Basit bir yield örneği

<?php
function gen_one_to_three() {
    for (
$i 1$i <= 3$i++) {
        
// yield'ler arasında $i korunur.
        
yield $i;
    }
}

$generator gen_one_to_three();
foreach (
$generator as $value) {
    echo 
"$value\n";
}
?>

Yukarıdaki örneğin çıktısı:

1
2
3

Bilginize:

Dahili olarak, ilişkisel olmayan dizilerdeki gibi sıralı tamsayı anahtarlar üretilen (yield) değerlerle çiftler oluşturur.

Dikkat

yield sözcüğünü bir ifade bağlamında kullanıyorsanız, (örneğin bir atamanın sağ tarfında) PHP5'te yield deyimini parantez içine almanız gerekir. Örneğin aşağıdaki geçerlidir:

$data = (yield $value);

Bu yapılmazsa PHP 5'te bir çözümleme hatası ortaya çıkar:

$data = yield $value;

Parantez kısıtlamaları PHP7'de uygulanmaz.

Generator::send() yöntemine $data değiştirgesine atanan değer ya da yerine Generator::next() çağılırsa NULL atanır.

Değerleri anahtarlarla üretmek

PHP ilişkisel dizileri de destekler ve üreteçlerin bir farkı yoktur. Basit değerlerin üretilmesinin yanında, yukarıda gösterildiği gibi, aynı anda bir anahtar da üretebilirsiniz.

Bir anahtar/değer çifti üretmenin (yielding) sözdizimi, aşağıda gösterildiği gibi, bir ilişkisel dizi tanımlamada kullanılan sözdizimine çok benzer.

Örnek 2 - Bir anahtar/değer çifti üretimi

<?php
/*
 * girdi noktalı virgülle ayrılmış alanlardır,
 * ilk alan anahtar olarak kullanılacak ID'dir
 */

$input = <<<'EOF'
1;PHP;Dolar imlerini sever
2;Python;Boşlukları sever
3;Ruby;Blokları sever
EOF;

function 
input_parser($input) {
    foreach (
explode("\n"$input) as $line) {
        
$fields explode(';'$line);
        
$id array_shift($fields);

        
yield $id => $fields;
    }
}

foreach (
input_parser($input) as $id => $fields) {
    echo 
"$id:\n";
    echo 
"    $fields[0]\n";
    echo 
"    $fields[1]\n";
}
?>

Yukarıdaki örneğin çıktısı:

1:
    PHP
    Dolar imlerini sever
2:
    Python
    Boşlukları sever
3:
    Ruby
    Blokları sever
Dikkat

Evvelce gösterilen basit değerli üretimler gibi, bir anahtar/değer çiftini bir ifade bağlamı içinde üretmek, yield deyimin parantez içine alınmasını gerektirir (PHP 5 için):

$data = (yield $key => $value);

null değerlerin üretini

Yield deyimini değiştirgesiz kullanmak NULL değerinin özdevinimli bir anahtarla birlikte üretilmesini sağlar.

Örnek 3 - NULL üretmek

<?php
function gen_three_nulls() {
    foreach (
range(13) as $i) {
        
yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));
?>

Yukarıdaki örneğin çıktısı:

array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

Başvuruya göre üretim

Üreteç işlevleri üretimi, değerlerle yapabildiği gibi başvurularla da yapabilir. Bu, işlev isminin önüne bir '&' imi yerleştirip işlevlerden başvurları döndürerek yapılabilir:

Örnek 4 - Değerleri başvuruya göre üretmek

<?php
function &gen_reference() {
    
$value 3;

    while (
$value 0) {
        
yield $value;
    }
}

/*
 * $number döngü içinde değiştirilebilir,
 * üreteç başvuruları ürettiğinden, 
 * gen_reference() içindeki $value değişir.
 */
foreach (gen_reference() as &$number) {
    echo (--
$number).'... ';
}
?>

Yukarıdaki örneğin çıktısı:

2... 1... 0... 

yield from üzerinden üreteç ihalesi

PHP 7'de, üreteç ihalesi, değerlerin başka bir üreteçte (yield from deyimini kullanarak array veya Traversable nesnesinden) üretilmesini sağlar. Dış üreteç tüm değerleri iç üreteç, nesne veya diziden geçerliliğini yitirene kadar ürettikten sonra üretime dış üreteçte devam edilecektir.

Bir üreteç yield from ile kullanılırsa, yield from ifadesi ayrıca, iç üreteçten döndürülen değerleri de döndürecektir.

Dikkat

Bir dizide saklama (örn, iterator_to_array() ile)

yield from anahtarları sıfırlamaz, Traversable nesnesinden veya diziden döndürülen anahtarları korur. Böylece, bazı değerler başka bir yield veya yield from ile ortaklaşılan bir anahtarla paylaşılır (bir diziye değer girilmesiyle, anahtar ilk değerlerin üzerine yazılmasına sebep olur).

Bu konuda alışılmış durum, iterator_to_array() işlevinin öntanımlı olarak bir anahtarlı dizi döndürmesidir (muhtemelen beklenmedik sonuçlara yol açarak). iterator_to_array() ikinci bir değiştirgeye sahiptir: Generator tarafından döndürülen anahtarları yoksayarken tüm değerleri toplamak için FALSE atanabilen use_keys değiştirgesi.

Örnek 5 - iterator_to_array() ile yield from

<?php
function from() {
    
yield 1// key 0
    
yield 2// key 1
    
yield 3// key 2
}
function 
gen() {
    
yield 0// key 0
    
yield from from(); // keys 0-2
    
yield 4// key 1
}
// [0, 1, 2, 3, 4] dizisini almak için ikinci değiştirgeye false aktaralım
var_dump(iterator_to_array(gen()));
?>

Yukarıdaki örneğin çıktısı:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(4)
  [2]=>
  int(3)
}

Örnek 6 - yield from için temel kullanım

<?php
function count_to_ten() {
    
yield 1;
    
yield 2;
    
yield from [34];
    
yield from new ArrayIterator([56]);
    
yield from seven_eight();
    
yield 9;
    
yield 10;
}

function 
seven_eight() {
    
yield 7;
    
yield from eight();
}

function 
eight() {
    
yield 8;
}

foreach (
count_to_ten() as $num) {
    echo 
"$num ";
}
?>

Yukarıdaki örneğin çıktısı:

1 2 3 4 5 6 7 8 9 10 

Örnek 7 - yield from ve dönen değerler

<?php
function count_to_ten() {
    
yield 1;
    
yield 2;
    
yield from [34];
    
yield from new ArrayIterator([56]);
    
yield from seven_eight();
    return 
yield from nine_ten();
}

function 
seven_eight() {
    
yield 7;
    
yield from eight();
}

function 
eight() {
    
yield 8;
}

function 
nine_ten() {
    
yield 9;
    return 
10;
}

$gen count_to_ten();
foreach (
$gen as $num) {
    echo 
"$num ";
}
echo 
$gen->getReturn();
?>

Yukarıdaki örneğin çıktısı:

1 2 3 4 5 6 7 8 9 10
add a note add a note

User Contributed Notes 8 notes

up
97
Adil lhan (adilmedya at gmail dot com)
5 years ago
For example yield keyword with Fibonacci:

function getFibonacci()
{
    $i = 0;
    $k = 1; //first fibonacci value
    yield $k;
    while(true)
    {
        $k = $i + $k;
        $i = $k - $i;
        yield $k;       
    }
}

$y = 0;

foreach(getFibonacci() as $fibonacci)
{
    echo $fibonacci . "\n";
    $y++;   
    if($y > 30)
    {
        break; // infinite loop prevent
    }
}
up
24
info at boukeversteegh dot nl
3 years ago
[This comment replaces my previous comment]

You can use generators to do lazy loading of lists. You only compute the items that are actually used. However, when you want to load more items, how to cache the ones already loaded?

Here is how to do cached lazy loading with a generator:

<?php
class CachedGenerator {
    protected
$cache = [];
    protected
$generator = null;

    public function
__construct($generator) {
       
$this->generator = $generator;
    }

    public function
generator() {
        foreach(
$this->cache as $item) yield $item;

        while(
$this->generator->valid() ) {
           
$this->cache[] = $current = $this->generator->current();
           
$this->generator->next();
            yield
$current;
        }
    }
}
class
Foobar {
    protected
$loader = null;

    protected function
loadItems() {
        foreach(
range(0,10) as $i) {
           
usleep(200000);
            yield
$i;
        }
    }

    public function
getItems() {
       
$this->loader = $this->loader ?: new CachedGenerator($this->loadItems());
        return
$this->loader->generator();
    }
}

$f = new Foobar;

# First
print "First\n";
foreach(
$f->getItems() as $i) {
    print
$i . "\n";
    if(
$i == 5 ) {
        break;
    }
}

# Second (items 1-5 are cached, 6-10 are loaded)
print "Second\n";
foreach(
$f->getItems() as $i) {
    print
$i . "\n";
}

# Third (all items are cached and returned instantly)
print "Third\n";
foreach(
$f->getItems() as $i) {
    print
$i . "\n";
}
?>
up
12
Harun Yasar harunyasar at mail dot com
3 years ago
That is a simple fibonacci generator.

<?php
function fibonacci($item) {
   
$a = 0;
   
$b = 1;
    for (
$i = 0; $i < $item; $i++) {
        yield
$a;
       
$a = $b - $a;
       
$b = $a + $b;
    }
}

# give me the first ten fibonacci numbers
$fibo = fibonacci(10);
foreach (
$fibo as $value) {
    echo
"$value\n";
}
?>
up
7
christophe dot maymard at gmail dot com
4 years ago
<?php
//Example of class implementing IteratorAggregate using generator

class ValueCollection implements IteratorAggregate
{
    private
$items = array();
   
    public function
addValue($item)
    {
       
$this->items[] = $item;
        return
$this;
    }
   
    public function
getIterator()
    {
        foreach (
$this->items as $item) {
            yield
$item;
        }
    }
}

//Initializes a collection
$collection = new ValueCollection();
$collection
       
->addValue('A string')
        ->
addValue(new stdClass())
        ->
addValue(NULL);

foreach (
$collection as $item) {
   
var_dump($item);
}
up
4
Hayley Watson
2 years ago
If for some strange reason you need a generator that doesn't yield anything, an empty function doesn't work; the function needs a yield statement to be recognised as a generator.

<?php

function gndn()
{
}

foreach(
gndn() as $it)
{
    echo
'FNORD';
}

?>

But it's enough to have the yield syntactically present even if it's not reachable:

<?php

function gndn()
{
    if(
false) { yield; }
}

foreach(
gndn() as $it)
{
    echo
'FNORD';
}

?>
up
3
zilvinas at kuusas dot lt
3 years ago
Do not call generator functions directly, that won't work.

<?php

function my_transform($value) {
   
var_dump($value);
    return
$value * 2;
}

function
my_function(array $values) {
    foreach (
$values as $value) {
        yield
my_transform($value);
    }
}

$data = [1, 5, 10];
// my_transform() won't be called inside my_function()
my_function($data);

# my_transform() will be called.
foreach (my_function($data) as $val) {
   
// ...
}
?>
up
3
Shumeyko Dmitriy
5 years ago
This is little example of using generators with recursion. Used version of php is 5.5.5
[php]
<?php
define
("DS", DIRECTORY_SEPARATOR);
define ("ZERO_DEPTH", 0);
define ("DEPTHLESS", -1);
define ("OPEN_SUCCESS", True);
define ("END_OF_LIST", False);
define ("CURRENT_DIR", ".");
define ("PARENT_DIR", "..");

function
DirTreeTraversal($DirName, $MaxDepth = DEPTHLESS, $CurrDepth = ZERO_DEPTH)
{
  if ((
$MaxDepth === DEPTHLESS) || ($CurrDepth < $MaxDepth)) {
   
$DirHandle = opendir($DirName);
    if (
$DirHandle !== OPEN_SUCCESS) {
      try{
        while ((
$FileName = readdir($DirHandle)) !== END_OF_LIST) { //read all file in directory
         
if (($FileName != CURRENT_DIR) && ($FileName != PARENT_DIR)) {
           
$FullName = $DirName.$FileName;
            yield
$FullName;
            if(
is_dir($FullName)) { //include sub files and directories
             
$SubTrav = DirTreeTraversal($FullName.DS, $MaxDepth, ($CurrDepth + 1));
              foreach(
$SubTrav as $SubItem) yield $SubItem;
            }
          }
        }
      } finally {
       
closedir($DirHandle);
      }
    }
  }
}

$PathTrav = DirTreeTraversal("C:".DS, 2);
print
"<pre>";
foreach(
$PathTrav as $FileName) printf("%s\n", $FileName);
print
"</pre>";
[/
php]
up
-25
denshadewillspam at HOTMAIL dot com
4 years ago
Note that you can't use count() on generators.

/**
* @return integer[]
*/
function xrange() {
    for ($a = 0; $a < 10; $a++)
    {
        yield $a;
    }
}

function mycount(Traversable $traversable)
{
    $skip = 0;
    foreach($traversable as $skip)
    {
        $skip++;
    }
    return $skip;
}
echo "Count:" . count(xrange()). PHP_EOL;
echo "Count:" . mycount(xrange()). PHP_EOL;
To Top