Синтаксис генераторов

Генератор в целом выглядит как обычная функция, за исключением того, что вместо возвращения одного значения, генератор будет перебирать столько значений, сколько вам нужно.

Когда вызывается генератор, он возвращает объект, который можно итерировать. Когда вы итерируете этот объект (к примеру в цикле foreach), PHP вызывает функцию генератор каждый раз, когда вам нужно новое значение, после чего сохраняет состояние генератора и при следующем вызове возвращает следующее значение.

Когда все значения в генераторе закончились, функция просто завершит работу ничего не вернув. После этого основной код продолжит работу, аналогично как, например когда в массиве закончились элементы для перебора.

Замечание:

В генераторе может присутствовать только пустой оператор return, для обозначения конца генерируемой последовательности (но можно обойтись и без него). Если вы попытаетесь вернуть этим оператором какое либо значение - произойдет ошибка на этапе компиляции.

Ключевое слово yield

Самая суть генератора заключается в ключевом слове yield. В самом простом варианте, оператор "yield" можно рассматривать как оператор "return", за исключением того, что вместо прекращения работы функции, "yield" только приостанавливает ее выполнение и возвращает текущее значение, и при следующем вызове функции она возобновит выполнения с места, на котором прервалась.

Пример #1 Простой пример выдачи значений

<?php
function gen_one_to_three() {
    for (
$i 1$i <= 3$i++) {
        
// Обратите внимание, что $i сохраняет свое значение между вызовами.
        
yield $i;
    }
}

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

Результат выполнения данного примера:

1
2
3

Замечание:

Последовательность целых чисел будет связана с получаемыми от генератора значениями, как будто перебирается неассоциативный массив.

Предостережение

Если вы хотите использовать "yield" в выражениях (например в правой части оператора присвоения), в PHP вы должны убирать его в скобки. Корректный пример:

$data = (yield $value);

А вот это не правильно и вызовет ошибку разбора в PHP 5:

$data = yield $value;

В PHP 7 этого ограничения нет.

Этот синтаксис можно использовать в сочетании с методом Generator::send().

Получение значений с ключами

PHP поддерживает ассоциативные массивы, и генераторы не являются исключением. Так же как можно получать простые значения, как показано выше, вы можете получать значения с ключами.

Синтаксис получения ключ/значение очень похож на синтаксис ассоциативных массивов, как показано ниже.

Пример #2 Получение пар ключ/значение

<?php
/* $input содержит пары ключ/значение разделенные точкой с запятой */

$input = <<<'EOF'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
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";
}
?>

Результат выполнения данного примера:

1:
    PHP
    Likes dollar signs
2:
    Python
    Likes whitespace
3:
    Ruby
    Likes blocks
Предостережение

Также как и для одиночных значений, получение пар ключ/значение в выражениях требует оборачивания их в скобки:

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

Получение NULL

Для получения NULL нужно вызвать "yield" без аргументов. Ключ сгенерируется автоматически.

Пример #3 Получение NULL

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

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

Результат выполнения данного примера:

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

Получение значения по ссылке

Генераторы могут отдавать значения по ссылке. Это делается так же, как описано в разделе "возвращение ссылки из функции", с помощью амперсанда (&) перед именем функции.

Пример #4 Получение значений по ссылке

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

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

/* Обратите внимание, что мы можем изменять $number в цикле, и
 * так как генератор возвращает ссылку, $value
 * в gen_reference() также изменится. */
foreach (gen_reference() as &$number) {
    echo (--
$number).'... ';
}
?>

Результат выполнения данного примера:

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

Делегирование генератора с помощью yield from

В PHP 7, делегирование генератора позволяет вам получать значения из другого генератора, объекта Traversable, или массива, используя yield from. Внешний генератор будет возвращать значения из внутреннего генератора, объекта или массива, до того момента, пока они их отдают, после чего продолжится выполнения внешнего генератора.

Если генератор используется с yield from, то выражение yield from также будет возвращать значения из внутреннего генератора.

Предостережение

Сохранение в массив (т.е. iterator_to_array())

yield from не сбрасывает ключи. Ключи, возвращенные из объекта Traversable или массива, сохранятся. Таким образом, некоторые значения, могут пересекаться по ключам с другими yield или yield from, что, при записи в массив, повлечет за собой перезапись уже записанных значений.

Общий случай, когда это имеет значение, это когда iterator_to_array() возвращает массив с ключами по умолчанию. В этом случае можно получить печальный результат. iterator_to_array() имеет второй параметр use_keys, который можно установить в FALSE, для генерации собственных ключей и игнорировании ключей, переданных из объекта Generator.

Пример #5 yield from с iterator_to_array()

<?php
function from() {
    
yield 1// ключ 0
    
yield 2// ключ 1
    
yield 3// ключ 2
}
function 
gen() {
    
yield 0// ключ 0
    
yield from from(); // ключи 0-2
    
yield 4// ключ 1
}
// Задайте false вторым параметром для получения массива [0, 1, 2, 3, 4]
var_dump(iterator_to_array(gen()));
?>

Результат выполнения данного примера:

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

Пример #6 Основы использования yield from

<?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 ";
}
?>

Результат выполнения данного примера:

1 2 3 4 5 6 7 8 9 10 

Пример #7 yield from и возвращаемые значения

<?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();
?>

Результат выполнения данного примера:

1 2 3 4 5 6 7 8 9 10