Синтаксис генераторов
Генератор в целом выглядит как обычная функция, за исключением того, что вместо возвращения одного значения, генератор будет перебирать столько значений, сколько необходимо. Любая функция, содержащая 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
Замечание:
Последовательность целых чисел будет связана с получаемыми от генератора значениями, как будто перебирается неассоциативный массив.
Значение, которое будет присвоено
$data, - это значение,
переданное
Generator::send() или
null
, если вместо этого вызывается
Generator::next().
Получение значений с ключами
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 Любит блоки
Также как и для одиночных значений, получение пар ключ/значение в выражениях требует оборачивания их в скобки:
$data = (yield $key => $value);
Получение 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() имеет второй параметр
use_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