Что такое генераторы (Generators) в PHP и зачем они нужны?

«Что такое генераторы (Generators) в PHP и зачем они нужны?» — вопрос из категории PHP Core, который задают на 35% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Генераторы в PHP — это специальные функции, которые позволяют итерироваться по набору данных, не создавая весь набор в памяти сразу. Вместо ключевого слова return используется yield. При каждом вызове yield функция приостанавливает своё выполнение и возвращает текущее значение. При следующей итерации выполнение возобновляется с того же места.

Ключевая польза: Экономия оперативной памяти при работе с большими наборами данных (логи, большие файлы, объёмные результаты запросов из БД).

Сравним два подхода:

// ПЛОХО: Функция возвращает массив из миллиона чисел, загружая их все в память.
function getMillionNumbers(): array
{
    $numbers = [];
    for ($i = 0; $i < 1_000_000; $i++) {
        $numbers[] = $i;
    }
    return $numbers; // Массив из 1М элементов создан в памяти
}

foreach (getMillionNumbers() as $number) {
    // В начале цикла массив уже целиком в памяти.
    if ($number > 100) break; // Но мы использовали только 101 элемент!
}
// Пиковое использование памяти: ~130 МБ
// ХОРОШО: Генератор возвращает значения по одному.
function generateMillionNumbers(): Generator
{
    for ($i = 0; $i < 1_000_000; $i++) {
        yield $i; // Возвращает $i и приостанавливается
    }
}

foreach (generateMillionNumbers() as $number) {
    // На каждой итерации в памяти только текущее значение $i.
    if ($number > 100) break;
}
// Пиковое использование памяти: ~1 МБ

Практический пример: Чтение большого файла построчно.

function readLargeFile(string $filename): Generator
{
    $handle = fopen($filename, 'r');
    if (!$handle) {
        throw new RuntimeException("Could not open file: {$filename}");
    }

    try {
        while (($line = fgets($handle)) !== false) {
            yield trim($line); // Отдаём строку по одной
        }
    } finally {
        fclose($handle); // Гарантированное закрытие файла
    }
}

// Использование
foreach (readLargeFile('huge_log.txt') as $lineNumber => $lineContent) {
    // $lineNumber автоматически инкрементируется с каждым yield
    echo "Line {$lineNumber}: {$lineContent}" . PHP_EOL;
    // Файл никогда не загружается в память целиком.
}

Ключевые особенности генераторов в PHP:

  • Функция-генератор возвращает объект внутреннего класса Generator, который реализует интерфейсы Iterator и Traversable.
  • yield может возвращать ключ-значение: yield $key => $value;.
  • Можно делегировать другой генератор с помощью yield from (аналог await в некоторых других языках).
  • Начиная с PHP 7.0, генератор может возвращать финальное значение с помощью return, которое можно получить через $generator->getReturn().

Генераторы — это мощный инструмент для написания эффективного по памяти кода, особенно в контексте обработки потоков данных или реализации ленивых вычислений.