Что такое PHP Generators и зачем они нужны?
Генератор - это функция, которая вместо того чтобы возвращать весь массив целиком, отдаёт значения частями с помощью yield. Это позволяет:
работать с большими наборами данных, экономя память;
обрабатывать данные по мере необходимости (ленивая обработка);
писать более чистый и понятный код при потоковой обработке.
Генераторы особенно полезны для работы с большими файлами, логами, потоками данных или большими выборками из базы данных.
Как работает генератор
Обычная функция возвращает результат и завершает своё выполнение. Генератор позволяет:
приостанавливать выполнение на каждом
yield;возобновлять выполнение с того места, где остановился;
отдавать значения по одному в итерации (
foreach) без загрузки всего набора данных в память.
Когда вы вызываете генератор (функцию с yield), вы получаете объект Generator. Итерация по нему происходит как по обычному массиву, но значения создаются по мере обхода.
Простой пример
function numbers(): Generator {
yield 1;
yield 2;
yield 3;
}
foreach (numbers() as $n) {
echo $n . PHP_EOL;
}Это аналогично возврату массива [1, 2, 3], но без выделения памяти под весь массив.
Yield: виды использования
Простое значение
yield $value; // ключ будет 0, 1, 2...Ключ-значение
yield $key => $value;Пример:
function kv(): Generator {
yield 'a' => 100;
yield 'b' => 200;
}Делегирование (PHP 7+)
yield from $iterable; // встраивает другой итератор или генераторУправление генератором через методы
МетодыGeneratorпозволяют:
send($value)- передать значение внутрь генератора;throw(Exception $e)- бросить исключение внутрь генератора;getReturn()- получить значение изreturnвнутри генератора (PHP 7+).
Пример с send():
function greeter(): Generator {
$name = yield "What's your name?";
yield "Hello, $name!";
}
$g = greeter();
echo $g->current(); // "What's your name?"
$g->send('Alice'); // передаём 'Alice' внутрь генератора
echo $g->current(); // "Hello, Alice!"Методы и свойства Generator
current()- текущее значениеkey()- текущий ключnext()- перейти к следующемуyieldvalid()- валидность текущей позицииrewind()- перемотать генератор (обычно в начале)send($value)- отправить значение внутрь генератораthrow(Exception $e)- бросить исключение внутрь генератораgetReturn()- получить значение изreturnвнутри генератора (PHP 7+)
Реальные кейсы использования
1. Чтение и обработка большого CSV
function readCsv(string $filePath): Generator {
$f = fopen($filePath, 'r');
if (!$f) {
throw new RuntimeException("Can't open file");
}
while (($row = fgetcsv($f)) !== false) {
yield $row; // отдаём по одной строке
}
fclose($f);
}
foreach (readCsv('/path/huge.csv') as $row) {
// обработка строки (валидация, запись в БД пачками и т.д.)
}Преимущество: память остаётся почти постоянной, так как одновременно хранится только одна строка.
2. Пайплайн обработки логов/событий
function readLines($stream): Generator {
while (($line = fgets($stream)) !== false) {
yield $line;
}
}
function parseJsonLines(Generator $lines): Generator {
foreach ($lines as $line) {
$obj = json_decode($line, true);
if ($obj !== null) yield $obj;
}
}
function filterByLevel(Generator $events, string $level): Generator {
foreach ($events as $e) {
if (($e['level'] ?? '') === $level) yield $e;
}
}
// Использование:
$fh = fopen('/var/log/events.ndjson', 'r');
$lines = readLines($fh);
$events = parseJsonLines($lines);
$fatal = filterByLevel($events, 'FATAL');
foreach ($fatal as $e) {
// обработка аварийных событий
}Такой подход позволяет читать и обрабатывать данные построчно, экономя память и упрощая код.
Производительность и память
Генераторы помогают уменьшить использование памяти, особенно при:
больших файлах;
потоковых API (сокеты, STDIN);
веб-парсинге, где результат приходит по частям;
больших выборках из базы данных.
Важно: генераторы не ускоряют выполнение кода, но уменьшают потребление памяти и позволяют получать первые результаты раньше.
Подводные камни и лучшие практики
Не сериализуйте генераторы. Они не сериализируемы.
rewind()поведение: нельзя просто перезапустить генератор, использующий внешние ресурсы.Отладка: стек вызовов может выглядеть непривычно.
Не для малых наборов данных: массивы могут быть быстрее и проще.
Совместимость:
yield- PHP 5.5+,yield fromиgetReturn()- PHP 7+.