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

Функция-генератор выглядит как обычная функция, за исключением того, что вместо возврата значения генератор выдаёт столько значений, столько ему необходимо. Каждая функция с оператором yield — функция-генератор.

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

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

Замечание:

Генераторы умеют возвращать значения, которые можно получить методом Generator::getReturn().

Ключевое слово 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

Замечание:

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

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

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

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

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

<?php

/* Переменная $input содержит пары ключ и значение, которые разделили точкой с запятой */

$input = <<<'EOF'
1;PHP;Любит знаки доллара
2;Python;Любит пробелы
3;Ruby;Любит блоки
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
    Любит знаки доллара
2:
    Python
    Любит пробелы
3:
    Ruby
    Любит блоки

Получение значений null

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

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

<?php

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

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

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

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

Сохранение в массив (например, через функцию iterator_to_array())

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

Распространенный случай, когда это имеет значение, — функция iterator_to_array(), которая возвращает массив с ключом по умолчанию, что иногда приводит к неожиданным результатам. У функции iterator_to_array() есть второй параметр preserve_keys, которому можно присвоить значение false для генерации собственных ключей и игнорирования ключей, которые передаются из объекта Generator.

Пример #5 Выражение yield from с функцией iterator_to_array()

<?php

function inner()
{
yield
1; // Ключ 0
yield 2; // Ключ 1
yield 3; // Ключ 2
}

function
gen()
{
yield
0; // Ключ 0
yield from inner(); // Ключи 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 [
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 ";
}

?>

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

1 2 3 4 5 6 7 8 9 10

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

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

?>

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

1 2 3 4 5 6 7 8 9 10