Функция-генератор выглядит как обычная функция, за исключением того, что
вместо возврата значения генератор выдаёт столько значений, столько
ему необходимо.
Каждая функция с оператором yield
— функция-генератор.
Когда вызывается генератор, он возвращает объект, который можно итерировать.
При итерации по этому объекту (например, в цикле foreach
), PHP вызывает
методы итерации объекта каждый раз, когда ему требуется значение, а затем
сохраняет состояние генератора и при следующем вызове возвращает следующее
значение.
Когда значения в генераторе закончатся, генератор может просто выполнить возврат, и вызывающий код продолжится так же, как если бы в массиве закончились значения.
Замечание:
Генераторы умеют возвращать значения, которые можно получить методом Generator::getReturn().
Сердце функции-генератора — ключевое слово 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
, нужно вызвать 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...
Делегирование генератора позволяет получать значения из другого генератора, объекта Traversable или массива через ключевые слова yield from. Внешний генератор будет возвращать значения из внутреннего генератора, объекта или массива до тех пор, пока они не перестанут действовать, после чего выполнение продолжится во внешнем генераторе.
Если генератор используется с ключевыми словами yield from, выражение yield from также будет возвращать значения из внутреннего генератора.
Ключевые слова 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