PHP Generators

Что такое 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: виды использования

  1. Простое значение

yield $value; // ключ будет 0, 1, 2...
  1. Ключ-значение

yield $key => $value;

Пример:

function kv(): Generator {
    yield 'a' => 100;
    yield 'b' => 200;
}
  1. Делегирование (PHP 7+)

yield from $iterable; // встраивает другой итератор или генератор
  1. Управление генератором через методы
    Методы 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() - перейти к следующему yield

  • valid() - валидность текущей позиции

  • 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+.

Комментарии (0)

Войдите, чтобы оставить комментарий

Похожие статьи

Как установить Docker и Docker Compose на Ubuntu и RedHat системы (2025)

Подробная инструкция по установке Docker и Docker Compose на Debian-based и RedHat-based системы. Разбор всех команд по шагам, настройка GPG-ключей, добавление репозиториев, запуск сервиса, проверка и устранение типичных ошибок.

2 0 2 мин