Генератор — функция, которая отдаёт поток значений
Генератор в PHP — это функция с yield. Снаружи она выглядит как источник данных для foreach, но внутри не строит весь массив заранее. При каждом yield функция отдаёт очередное значение и ставит своё выполнение на паузу: локальные переменные, текущая строка и состояние цикла сохраняются до следующего шага.
<?php
declare(strict_types=1);
function ids(int $from, int $to): Generator
{
for ($id = $from; $id <= $to; $id++) {
yield $id;
}
}
foreach (ids(100, 103) as $id) {
echo $id . PHP_EOL;
}Вызов ids(100, 103) не выполняет тело функции сразу до конца. Он возвращает объект Generator; выполнение начинается, когда код реально начинает итерацию. Это роднит генераторы с SPL, итераторы и коллекции, но писать отдельный класс Iterator для простого потока значений обычно не нужно.
flowchart TD
A[Вызов generator function] --> B[Возвращается объект Generator]
B --> C[foreach запрашивает первое значение]
C --> D[Код выполняется до yield]
D --> E[Значение отдано наружу, состояние сохранено]
E --> F{Нужно следующее значение?}
F -- да --> G[Продолжить после yield]
G --> D
F -- нет --> H[Итерация завершена]
H --> I[finally освобождает ресурсы, если он есть]yield вместо большого массива
Классический пример — обработка большого файла, API-выгрузки или результата постраничного запроса. Если функция возвращает массив, она должна сначала собрать всё в память. Если функция отдаёт генератор, потребитель получает строки по одной.
<?php
function readLines(string $path): Generator
{
$handle = fopen($path, 'rb');
if ($handle === false) {
throw new RuntimeException('Не удалось открыть файл');
}
try {
while (($line = fgets($handle)) !== false) {
yield rtrim($line, "\r\n");
}
} finally {
fclose($handle);
}
}
foreach (readLines(__DIR__ . '/events.log') as $line) {
if (str_contains($line, 'ERROR')) {
echo $line . PHP_EOL;
}
}finally здесь важен по той же причине, что и в Ошибки, исключения и Throwable: ресурс нужно закрыть и при нормальном завершении, и при break, и при исключении. Генератор не отменяет правила работы с ресурсами; он только меняет способ доставки значений.
Ключи, yield from и getReturn()
yield может отдавать не только значение, но и ключ. Синтаксис похож на ассоциативный массив, что естественно для PHP, где массив — ordered map; это напрямую связано с Массивы как ordered map.
<?php
function statusLabels(): Generator
{
yield 'new' => 'Новая';
yield 'paid' => 'Оплачена';
yield 'cancelled' => 'Отменена';
}
foreach (statusLabels() as $code => $label) {
echo "$code: $label" . PHP_EOL;
}yield from делегирует часть потока другому массиву, Traversable-объекту или генератору. Это удобно для композиции: один генератор собирает общий поток из нескольких маленьких источников.
<?php
function baseEvents(): Generator
{
yield 'app.started';
yield 'cache.warmed';
}
function allEvents(): Generator
{
yield 'boot';
yield from baseEvents();
yield 'ready';
}Ловушка: yield from сохраняет ключи внутреннего источника. Если потом вызвать iterator_to_array($generator) с настройкой по умолчанию, одинаковые ключи могут перезаписать значения. Когда нужны просто все значения подряд, используйте iterator_to_array($generator, false).
Генератор также может завершиться через return, а возвращённое значение доступно через $generator->getReturn() после полного завершения итерации. Это не замена обычным yielded values, а отдельный финальный результат: например, счётчик обработанных строк или checksum.
Generator как объект
Generator — встроенный final-класс, который нельзя создать через new. Его создаёт PHP при вызове функции с yield. Объект реализует Iterator и имеет методы current(), key(), next(), rewind(), valid(), а также send(), throw() и getReturn().
В обычном прикладном коде чаще всего достаточно foreach. Ручное управление через методы нужно для низкоуровневых итераторов, парсеров и старого coroutine-стиля, где значение можно «отправить обратно» в генератор:
<?php
function conversation(): Generator
{
$name = yield 'Как вас зовут?';
yield "Привет, $name";
}
$gen = conversation();
echo $gen->current() . PHP_EOL;
echo $gen->send('Анна') . PHP_EOL;Такой стиль существует, но в современном PHP его не стоит путать с полноценной асинхронностью. Генератор сам по себе не делает I/O неблокирующим и не запускает код параллельно. Он даёт ленивую итерацию и точку паузы на уровне yield.
Fiber — пауза всего стека вызовов
Fiber появился в PHP 8.1. Это уже не итератор, а «full-stack interruptible function»: выполнение можно приостановить внутри глубоко вложенного вызова, а затем продолжить позже. В отличие от генератора, функциям между стартом Fiber и местом Fiber::suspend() не нужно менять return type на Generator и протаскивать yield через весь стек.
<?php
$fiber = new Fiber(function (): string {
echo "A\n";
$value = Fiber::suspend('paused');
echo "B: $value\n";
return 'done';
});
$result = $fiber->start();
echo "start returned: $result\n";
$fiber->resume('resume value');
echo "return: " . $fiber->getReturn() . "\n";Вывод будет таким:
A
start returned: paused
B: resume value
return: donestart() запускает fiber и возвращает значение, переданное в Fiber::suspend(). resume() продолжает выполнение и передаёт значение обратно в место остановки. Если нужно продолжить выполнение исключением, есть Fiber::throw(Throwable $exception). Состояние можно проверять методами isStarted(), isSuspended(), isRunning() и isTerminated().
Где Fiber нужен на практике
Сам по себе Fiber — низкоуровневый механизм. В обычном контроллере, CLI-скрипте или модели его почти никогда не создают руками. Его сила проявляется в библиотеках: event loop, HTTP-клиентах, очередях, WebSocket-серверах, runtime вроде Swoole/OpenSwoole или долгоживущих воркерах. На верхнем уровне библиотека может дать API, похожий на синхронный код, а внутри переключать fiber, пока операция ждёт сеть, таймер или другой неблокирующий источник.
Важно не переоценить механизм. Fiber не превращает блокирующий file_get_contents() или обычный PDO-запрос в неблокирующую операцию. Если внутри fiber вызвать блокирующий системный вызов, процесс всё равно будет ждать. Для реальной конкурентности нужны неблокирующие драйверы, event loop и дисциплина на границах I/O. Поэтому эта тема естественно продолжается в Асинхронный PHP и event loop, Swoole и OpenSwoole и PHP-FPM, RoadRunner и долгоживущие воркеры.
Генератор или Fiber
Генератор выбирают, когда нужен поток значений: пройти большой файл, лениво сгенерировать диапазон, составить pipeline обработки, отдать данные в foreach. Его главный контракт — iterable.
Fiber выбирают не как коллекцию, а как механизм планирования выполнения. Он нужен, когда библиотека хочет прервать стек вызовов и продолжить его позже, не заставляя каждую промежуточную функцию становиться генератором. Его главный контракт — управление состоянием выполнения.
Если коротко: yield отвечает на вопрос «какое следующее значение?», а Fiber::suspend() — «где остановить эту ветку выполнения, пока рантайм займётся чем-то другим?».
Типичные ловушки
Первая ловушка — думать, что генератор ускоряет код сам по себе. Он прежде всего экономит память и позволяет обрабатывать данные лениво. По CPU он может быть сопоставимым или даже чуть дороже простого массива на малых объёмах.
Вторая — повторно итерировать один и тот же Generator. В отличие от массива, генератор обычно одноразовый: после прохода он завершён. Если нужен новый проход, вызовите генераторную функцию заново.
Третья — забывать про ключи при yield from и iterator_to_array(). Для списков часто нужен второй аргумент false.
Четвёртая — считать Fiber потоками. Это не threads и не preemptive multitasking. Переключение кооперативное: код должен дойти до точки suspend или до операции, которую библиотека умеет оборачивать.
См. также
- SPL, итераторы и коллекции — общий интерфейс
Iterator,Traversableи готовые итераторы стандартной библиотеки. - Массивы как ordered map — ключи, порядок и поведение
iterator_to_array()рядом с генераторами. - Функции, замыкания и callable — как generator function отличается от обычной функции и callable.
- Ошибки, исключения и Throwable —
finally,Throwableи передача исключений вGenerator::throw()илиFiber::throw(). - Асинхронный PHP и event loop — зачем Fiber нужен async-библиотекам.
- Swoole и OpenSwoole — альтернативные рантаймы и конкурентное выполнение PHP-кода.
- PHP-FPM, RoadRunner и долгоживущие воркеры — где состояние процесса живёт дольше одного HTTP-запроса.
- Очереди, фоновые задачи и воркеры — потоковая обработка и ошибки в фоновых процессах.