PHP 8.3.4 Released!

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). yield içeren her işlev bir üreteç işlevidir.

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:

Bir üreteç değer döndürebilir ve Generator::getReturn() kullanılarak alınabilir.

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.

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

null değerlerin üretini

Yield deyimini bağımsız değişkensiz kullanmak null değerinin otomatik bir anahtarla birlikte üretilmesini sağlar.

Örnek 3 - null üretmek

<?php
function gen_three_nulls() {
foreach (
range(1, 3) 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

Üreteç ihalesi, değerlerin başka bir üreteçte, Traversable nesnesinden veya yield from anahtar sözcüğü kullanılarak bir diziden ü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 edecektir.

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 bağımsız değişkene sahiptir: Generator tarafından döndürülen anahtarları yoksayarken tüm değerleri toplamak için false atanabilen preserve_keys bağımsız değişkeni.

Örnek 5 - iterator_to_array() ile yield from

<?php
function inner() {
yield
1; // key 0
yield 2; // key 1
yield 3; // key 2
}
function
gen() {
yield
0; // key 0
yield from inner(); // keys 0-2
yield 4; // key 1
}
// [0, 1, 2, 3, 4] dizisini almak için ikinci bağımsız değişkene 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 [
3, 4];
yield from new
ArrayIterator([5, 6]);
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 [
3, 4];
yield from new
ArrayIterator([5, 6]);
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

User Contributed Notes 9 notes

up
122
Adil lhan (adilmedya at gmail dot com)
10 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
47
info at boukeversteegh dot nl
9 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
20
Hayley Watson
8 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
10
zilvinas at kuusas dot lt
8 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
13
Harun Yasar harunyasar at mail dot com
8 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
11
christophe dot maymard at gmail dot com
9 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
5
Shumeyko Dmitriy
10 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
-3
christianggimenez at gmail dot com
4 years ago
Module list of a number from 1 to 100.

<?php

function list_of_modulo(){

for(
$i = 1; $i <= 100; $i++){

if((
$i % 2) == 0){
yield
$i;
}
}
}

$modulos = list_of_modulo();

foreach(
$modulos as $value){

echo
"$value\n";
}

?>
up
-46
denshadewillspam at HOTMAIL dot com
9 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