Для чего нужны генераторы в PHP
Что проверяют
Понимаете ли вы, что генератор это ленивый итератор, и знаете ли, где он реально экономит память. Следом обычно спрашивают, чем yield отличается от return, что делает yield from и можно ли пройти генератор дважды.
Как отвечать
Функция с yield при вызове не выполняется, а возвращает объект Generator, который реализует Iterator. Тело работает кусками: до ближайшего yield, отдаёт значение и замирает до следующей итерации.
- большие файлы: чтение CSV или лога через
fgetsв цикле сyield. В памяти одна строка, а не весь файл, и файл на гигабайты читается с тем же потреблением, что и маленький; - выборки из БД:
fetch()в цикле сyieldвместоfetchAll(). В Laravel для этого естьcursor()иlazy()сLazyCollection, в DoctrinetoIterable(); - конвейеры: несколько генераторов цепочкой, фильтр, преобразование, запись, без промежуточных массивов;
- ключи и делегирование:
yield $key => $valueотдаёт пару,yield fromпередаёт управление другому генератору или массиву; - двусторонний обмен:
$gen->send($x)передаёт значение внутрь, оно становится результатом выраженияyield, аgetReturn()отдаёт значениеreturnпосле завершения.
Честно назовите цену: экономится память, а не процессорное время. На небольшом массиве генератор обычно медленнее, у каждой итерации есть накладные расходы.
Про асинхронность: Amp до третьей версии строил корутины на генераторах, но с PHP 8.1 есть Fibers, и Amp 3 вместе с циклом событий Revolt работает уже на них. Сам по себе генератор ничего асинхронно не выполняет.
Где спотыкаются
Пытаются пройти генератор второй раз. После начала обхода rewind() бросает исключение, нужен новый вызов функции.
Делают iterator_to_array($gen) и получают весь набор в памяти, теряя смысл генератора. Вдобавок при yield from ключи повторяются и перезаписывают друг друга, если не передать false вторым аргументом.
Ждут, что return внутри генератора выдаст значение в foreach. Он только завершает обход, а значение достаётся через getReturn().