У меня есть этот код:
<?php
function generator() {
yield 'First value';
for ($i = 1; $i <= 3; $i++) {
yield $i;
}
}
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
//$gen->next();
foreach ($gen as $value) {
echo $value . '<br/>';
}
Это выводит:
First value
First value
1
2
3
Мне нужно «Первое значение», чтобы уступить только один раз. Если я раскомментирую строку $ gen-> next (), произошла фатальная ошибка:
Неустранимая ошибка: необработанное исключение «Исключение» с сообщением «Невозможно перемотать генератор, который уже был запущен»
Как я могу решить это?
Проблема в том, что foreach
попробуй сбросить (перемотка) Генератор. Но rewind()
генерирует исключение, если генератор в настоящее время находится после первого выхода.
Таким образом, вы должны избегать foreach
и использовать while
вместо
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}
ответ чумкиу правильный. Некоторые дополнительные идеи.
(Это последняя версия, которую я добавляю сюда, но, возможно, лучшая)
PHP 7+:
function remaining(\Generator $generator) {
yield from $generator;
}
PHP 5.5+ < 7:
function remaining(\Generator $generator) {
for (; $generator->valid(); $generator->next()) {
yield $generator->current();
}
}
Использование (все версии PHP):
function foo() {
for ($i = 0; $i < 5; ++$i) {
yield $i;
}
}
$gen = foo();
if (!$gen->valid()) {
// Not even the first item exists.
return;
}
$first = $gen->current();
$gen->next();
$values = [];
foreach (remaining($gen) as $value) {
$values[] = $value;
}
Там могут быть некоторые косвенные накладные расходы. Но семантически это довольно элегантно, я думаю.
В качестве хорошей синтаксической альтернативы я предлагаю использовать for()
вместо while()
чтобы уменьшить беспорядок от ->next()
вызов и инициализация.
Простая версия, без вашего начального значения:
for ($gen = generator(); $gen->valid(); $gen->next()) {
echo $gen->current();
}
С начальным значением:
$gen = generator();
if (!$gen->valid()) {
echo "Not even the first value exists.<br/>";
return;
}
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
for (; $gen->valid(); $gen->next()) {
echo $gen->current() . '<br/>';
}
Вы могли бы поставить первый $gen->next()
в for()
утверждение, но я не думаю, что это добавит много читабельности.
Небольшой тест, который я сделал локально (с PHP 5.6), показал, что эта версия с for () или while () с явными вызовами -> next (), current () и т. Д. Медленнее, чем неявная версия с foreach(generator() as $value)
,
Это работает, только если у вас есть контроль над функцией генератора.
function generator($offset = 0) {
if ($offset <= 0) {
yield 'First value';
$offset = 1;
}
for ($i = $offset; $i <= 3; $i++) {
yield $i;
}
}
foreach (generator() as $firstValue) {
print "First: " . $firstValue . "\n";
break;
}
foreach (generator(1) as value) {
print $value . "\n";
}
Это будет означать, что любая инициализация будет выполняться дважды. Может быть, не желательно.
Кроме того, он позволяет звонить как генератор (9999) с очень высокими номерами пропуска. Например. кто-то может использовать это для обработки последовательности генератора в чанках. Но начинать с 0 каждый раз, а затем пропускать огромное количество элементов, кажется плохой идеей с точки зрения производительности. Например. если данные поступают из файла, и пропуск означает чтение + игнорирование первых 9999 строк файла.