Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 65 · От первого yield к управлению обходом

Generator: выдача по одному элементу и полный справочник методов

От двух артикулов и первого yield до потока файлов, обмена сообщениями, делегирования и каждого метода Generator.

Получать позиции по одной: зачем нужен генератор

Наглядная модель

Два артикула: от вызова функции к текущему элементу

Функция productCodes описывает выдачу. Её вызов создаёт Generator, а foreach просит значения по одному.

  1. productCodes()

    Создаёт объект; тело ещё не выполняется.

  2. foreach просит элемент

    Тело доходит до yield «A-10» и приостанавливается.

  3. $code = «A-10»

    Читатель выводит первый артикул.

  4. Следующий шаг foreach

    Тело продолжается до yield «B-20».

  5. Конец тела

    Больше элементов нет; цикл заканчивается.

Модель показывает порядок выполнения одного PHP-процесса. Готовый массив всех товаров не создаётся, но стоимость исходных данных и каждого шага остаётся.

Нужно напечатать этикетки каталога по одной, не собирая сначала всю выгрузку в большой массив. Сначала получим две понятные строки — Чай и Кофе с ценами в копейках. Затем тот же потребитель сможет читать файл или страницы источника постепенно.

Нужны вызов функции и цикл foreach. function productLabels(): Generator объявляет функцию без аргументов, которая возвращает объект обхода. Фигурные скобки ограничивают тело. yield выдаёт одно значение и сохраняет место остановки; return завершил бы тело. В foreach (productLabels() as $label) очередная строка попадает в $label, а echo печатает её.

От конкретной этикетки до печати
  1. productLabels() создаёт Generator; тело ещё не исполнено
  2. foreach просит элемент → yield выдаёт Чай — 25900 коп.
  3. В $label приходит эта строка → echo печатает её с префиксом Печать
  4. Следующий шаг продолжает тело → Кофе — 45000 коп.
  5. Конец функции завершает обход
Сформировать очередь печати товарных этикетокСамостоятельный пример
<?php
declare(strict_types=1);

function productLabels(): Generator
{
    yield 'Чай — 25900 коп.';
    yield 'Кофе — 45000 коп.';
}
foreach (productLabels() as $label) {
    echo 'Печать: ', $label, "\n";
}
Ожидаемый результат
Печать: Чай — 25900 коп.
Печать: Кофе — 45000 коп.

Каждый yield отдаёт готовую строку для одного действия потребителя. Генератор не печатает её сам; печать происходит в теле foreach. Сначала это две записи, затем источник можно заменить построчным файлом, сохранив потребителя.

Увидеть момент настоящего запускаСамостоятельный пример
<?php
function labels() {
    echo "Началось тело\n";
    yield 'Посылка 41';
    echo "После паузы\n";
}
$labels = labels();
echo "Объект создан\n";
echo $labels->current(), "\n";
$labels->next();
Ожидаемый результат
Объект создан
Началось тело
Посылка 41
После паузы

current() запускает ещё не начатый генератор до первого yield. next() продолжает его после этой точки. Две служебные надписи — наблюдение порядка, а не требование помещать echo в рабочую выгрузку.

Это модель остановленного исполнителя, а не отдельного потока. Между yield генератор выполняет обычный PHP-код в том же запросе. Вызов медленного HTTP-клиента внутри него продолжит блокировать процесс. Экономия памяти возникает лишь тогда, когда источник и потребитель тоже работают постепенно. Уже загруженный большой массив останется в памяти.

Запись yield: значение, ключ, пустое значение и return

ЗаписьЧто получает читательЗачем
yield $price;Очередное значение с автоматическим целым ключомПоследовательность цен
yield $sku => $price;Явный ключ и значениеЦена с артикулом
yield;null с автоматическим ключомТочка ожидания входа через send
$reply = yield $request;Наружу request, позже внутрь replyПошаговый обмен сообщениями
return $summary;Окончание, итог через getReturn()Число обработанных строк

Любое yield в теле делает эту функцию генераторной, даже если конкретная ветка не исполнится. Нельзя использовать yield вне функции. Ключ и значение могут быть выражениями; для составных выражений применяйте скобки, чтобы граница yield была очевидна. Пустой yield не означает конец: наличие элемента проверяет valid(). Тип возврата генераторной функции описывает объект обхода — например Generator, Iterator или Traversable, а не тип отдельного товара.

Артикул становится ключом, цена — значениемСамостоятельный пример
<?php
function prices(): Generator {
    yield 'A-10' => 900;
    yield 'B-20' => 1400;
    return 2;
}
$prices = prices();
foreach ($prices as $sku => $price) {
    echo $sku, ': ', $price, "\n";
}
echo 'Всего: ', $prices->getReturn(), "\n";
Ожидаемый результат
A-10: 900
B-20: 1400
Всего: 2

Двоеточие Generator в объявлении проверяет тип результата вызова prices(): это объект, не массив. foreach не видит итог return 2 как третью цену. getReturn() читает итог после полного обхода. Отдельная карточка ниже разбирает ошибку слишком раннего чтения.

Нет заданий и пропущено измерение — разные состоянияСамостоятельный пример
<?php
declare(strict_types=1);

function pendingOrders(): Generator
{
    if (false) { yield 'Заказ'; }
}
function temperatureReadings(): Generator
{
    yield null;
}
$orders = pendingOrders();
echo $orders->valid() ? "Есть заказ\n" : "Очередь заказов пуста\n";
$readings = temperatureReadings();
if ($readings->valid()) {
    echo $readings->current() === null ? "Измерение есть, значение не получено\n" : "Температура получена\n";
}
Ожидаемый результат
Очередь заказов пуста
Измерение есть, значение не получено

valid отвечает, есть ли текущий элемент. null может быть самим элементом: например, датчик не передал температуру. Проверка только current() === null смешала бы отсутствие записи и запись без измерения. if(false) нужен здесь, чтобы показать функцию-генератор с нулём фактически выданных элементов.

yield from: соединить источники, не потерять ключи и получить итог

Когда выгрузка состоит из локального каталога и дополнительных товаров, внешний генератор может передавать элементы внутреннего через yield from. Источником служит массив либо Traversable, включая другой Generator. Внешнее выполнение ждёт окончания этого источника. Если внутренний генератор делает return, выражение yield from получает этот итог; для обычного массива такого итога нет.

Внешний каталог → внутренняя партия → внешний итог
  1. catalog() выдаёт A-10
  2. yield from extra() передаёт B-20 читателю
  3. extra() возвращает 1 → $extraCount
  4. catalog() возвращает 2 → getReturn() внешнего объекта
Делегировать партию и учесть число её элементовСамостоятельный пример
<?php
function extra() { yield 'B-20'; return 1; }
function catalog() {
    yield 'A-10';
    $extraCount = yield from extra();
    return 1 + $extraCount;
}
$catalog = catalog();
foreach ($catalog as $sku) { echo $sku, "\n"; }
echo 'Всего: ', $catalog->getReturn(), "\n";
Ожидаемый результат
A-10
B-20
Всего: 2

Первые элементы обоих генераторов имеют ключ 0. foreach видит оба, потому что поток не требует уникальных ключей. А преобразование в массив с сохранением ключей может перезаписать позицию.

Не потерять товар при сборке двух партий в массивСамостоятельный пример
<?php
declare(strict_types=1);

function catalog(): Generator
{
    yield from [0 => 'Чай'];
    yield from [0 => 'Кофе'];
}
echo 'С исходными ключами: ', implode(', ', iterator_to_array(catalog())), "\n";
echo 'Полный список: ', implode(', ', iterator_to_array(catalog(), false)), "\n";
Ожидаемый результат
С исходными ключами: Кофе
Полный список: Чай, Кофе

Поток содержит оба товара, но у обоих ключ 0. По умолчанию iterator_to_array сохраняет ключи, и поздняя запись заменяет раннюю. false во втором аргументе создаёт новый список последовательных ключей. Это реальное различие между просмотром потока и его материализацией.

send и throw проходят через цепочку делегирующих генераторов к текущей внутренней точке yield. Это полезно для протоколов, но требует понимать, кто сейчас ждёт вход. Начните с Generator::send и Generator::throw, затем посмотрите передачу ошибки через yield from. Делегирование обычному Iterator не превращает его в принимающий сообщения Generator.

Выдача по ссылке: читатель может изменить данные источника

Обычно читатель получает очередное значение. Когда специально нужен изменяемый обход собственного контейнера, генераторную функцию объявляют с & перед именем, а foreach читателя использует & перед переменной. Эта возможность соединяет две области кода: изменение снаружи влияет на то, что будет дальше внутри. Для обычного экспорта такая связь лишняя.

Общая ячейка вместо независимого значения
  1. $stocks["Чай"] = 2
  2. Генератор выдаёт ссылку на эту ячейку
  3. ++$stock в читателе
  4. $stocks["Чай"] теперь 3; после цикла ссылка удалена unset
При приёмке добавить по одной упаковке каждому остаткуСамостоятельный пример
<?php
declare(strict_types=1);

function &editableStocks(array &$stocks): Generator
{
    foreach ($stocks as &$stock) { yield $stock; }
    unset($stock);
}
$stocks = ['Чай' => 2, 'Кофе' => 5];
foreach (editableStocks($stocks) as &$stock) { ++$stock; }
unset($stock);
foreach ($stocks as $title => $stock) { echo $title, ': ', $stock, " уп.\n"; }
Ожидаемый результат
Чай: 3 уп.
Кофе: 6 уп.

& у функции разрешает выдавать по ссылке, & у параметра связывает исходный массив, & в обоих foreach сохраняет изменяемую связь. unset убирает оставшееся имя-ссылку после цикла. Для обычного пересчёта прямой foreach по массиву проще; такой генератор нужен только если интерфейс изменяемого обхода выбран намеренно.

Это механизм из объяснения ссылок. Он не делает передачу большой структуры автоматически быстрее. Скрытые изменения, удержанные ссылки и невозможность свободно переиспользовать обход усложняют отладку. Если обновлять источник не нужно, возвращайте новые значения обычным yield.

Файл, досрочная остановка и реальная экономия памяти

Практический выигрыш виден при чтении построчного журнала: не загружать весь текст и массив строк одновременно. fopen открывает ресурс, fgets читает одну строку, false означает конец или ошибку чтения. Проверки открытия/ошибок нужны отдельно; finally закрывает открытый файл при завершении или уничтожении запущенного генератора.

Файл → одна строка → потребитель → закрытие
  1. fopen открывает orders.txt
  2. fgets читает строку 41 → yield
  3. Читатель обрабатывает 41; генератор хранит открытый ресурс
  4. После окончания / освобождения генератора finally вызывает fclose
Прочитать журнал по одной строкеСамостоятельный пример
<?php
function orderLines(string $path): Generator {
    $file = fopen($path, 'rb');
    if ($file === false) { throw new RuntimeException('Не открыть журнал'); }
    try {
        while (($line = fgets($file)) !== false) { yield rtrim($line, "\r\n"); }
        if (!feof($file)) { throw new RuntimeException('Ошибка чтения'); }
    } finally {
        fclose($file);
        echo "Файл закрыт\n";
    }
}
foreach (orderLines('orders.txt') as $line) { echo 'Заказ ', $line, "\n"; }
Ожидаемый результат
Заказ 41
Заказ 52
Файл закрыт

Учебный orders.txt входит в скачиваемый пример. Здесь try/finally отвечает именно за закрытие, а не подавление ошибок. В библиотеке служебный echo из finally убирают или заменяют подходящим логом. Обработка огромной отдельной строки всё равно потребует памяти на эту строку.

break не закрывает сохранённый генератор немедленноСамостоятельный пример
<?php
function workItems() {
    try { yield 41; yield 52; }
    finally { echo "Очистка\n"; }
}
$items = workItems();
foreach ($items as $id) { echo $id, "\n"; break; }
echo "Обход прерван, объект ещё сохранён\n";
unset($items);
echo "Ссылка освобождена\n";
Ожидаемый результат
41
Обход прерван, объект ещё сохранён
Очистка
Ссылка освобождена

В этом примере других ссылок и циклов нет: unset освобождает объект и запускает finally. Если другой объект хранит генератор, очистка отложится. Не рассчитывайте на break как на close: у Generator нет публичного close(). Держите владение ресурсами явным.

Генератор, массив, Iterator и Fiber: как выбрать

НужноВыборГраница
Получить товар по произвольному ключу много разМассив / индексПамять на коллекцию; Generator не даёт случайного доступа
Прочитать поток один разФункция с yieldПовторный обход — новый объект и повторная работа источника
Собственная перемотка и самостоятельное управление состояниемКласс IteratorНужно реализовать current/key/next/rewind/valid
Коллекция создаёт новый обход на каждый foreachIteratorAggregate с getIterator()Не возвращать один уже исчерпанный Generator всем читателям
Приостановить глубоко вложенный обычный вызовFiberНужны правила возобновления; не автоматическая параллельность
Два прохода — два независимых генератораСамостоятельный пример
<?php
function tickets() { yield 41; yield 52; }
foreach (tickets() as $id) { echo 'Экран: ', $id, "\n"; }
foreach (tickets() as $id) { echo 'Письмо: ', $id, "\n"; }
Ожидаемый результат
Экран: 41
Экран: 52
Письмо: 41
Письмо: 52

В файле/API повторное создание может означать повторное чтение или запрос, а данные могли измениться. Если нужен один неизменный снимок для двух получателей и объём ограничен, разумно один раз сохранить результат в массив.

Generator — final встроенный класс: наследовать его нельзя, new Generator не поддерживается. Его создаёт вызов функции с yield. Объект реализует Iterator, не обязан хранить готовую последовательность и не сериализуется. Ниже сохранены все девять страниц методов из официального оглавления, включая историческую __wakeup, отсутствующую в проверенном современном runtime. Генераторы появились в PHP 5.5; yield from и итоговый return с getReturn — в PHP 7.0. Примеры руководства рассчитаны на PHP 8.2+.

Следующий шаг: замените два фиксированных артикула чтением своего небольшого файла. Сначала докажите порядок и закрытие файла, затем увеличивайте объём. Сравните собственный Iterator и IteratorAggregate и ошибки файлового ввода.

Диагностика Generator: имя функции, а не снимок результата

В PHP 8.4/8.5 у Generator есть служебный __debugInfo(): его объявление видно в исходном коде PHP, хотя в сохранённом оглавлении руководства отдельной страницы для него нет. Метод без параметров возвращает массив диагностических сведений. Для обычной отладки достаточно var_dump объекта; не стройте формат базы или API на внутреннем составе этой диагностики. Она не исполняет обход и не содержит все выданные записи.

Диагностика не потребляет элемент
  1. Создан orderExport()
  2. __debugInfo() → function = orderExport
  3. Тело ещё не выполнялось
  4. current() → строка Заказ 42
Узнать источник отложенной выгрузки без чтения всех заказовСамостоятельный пример
<?php
declare(strict_types=1);

function orderExport(): Generator
{
    echo "Открыли выгрузку\n";
    yield 'Заказ 42';
}
$export = orderExport();
$info = $export->__debugInfo();
echo 'Источник: ', $info['function'], "\n";
echo 'Первая запись: ', $export->current(), "\n";
Ожидаемый результат
Источник: orderExport
Первая запись: Открыли выгрузку
Заказ 42

__debugInfo — служебная диагностика современных PHP 8.4/8.5. Получение имени не исчерпало поток. current запустил тело, поэтому его сообщение появилось прямо во время вычисления аргумента echo. Не используйте диагностический массив как постоянный формат хранения или API.

После завершения сведения не заменяют итогСамостоятельный пример
<?php
function exportOrders() { yield 41; return 1; }
$export = exportOrders();
foreach ($export as $id) { echo 'Заказ ', $id, "\n"; }
echo $export->__debugInfo()['function'], "\n";
echo 'Всего: ', $export->getReturn(), "\n";
Ожидаемый результат
Заказ 41
exportOrders
Всего: 1

Название функции доступно в диагностике, но число выгруженных заказов читается через договор getReturn(). Это два разных вида информации.

Для совместимости со старым runtime сначала применяют method_exists либо ограничивают минимальную версию проекта. Для исследования стека исполнения пригоден ReflectionGenerator; он рассматривается в справочнике Reflection.

От этикетки к полному договору Generator

Начальный маршрут yield показывает паузу по строкам вывода. Здесь каждое уточнение отвечает на практический вопрос: потеряются ли ключи двух партий, можно ли изменить исходные остатки, когда закроется файл и можно ли повторить обход.

Модель реальной выгрузки
  1. Источник предоставляет очередную запись
  2. Генератор выдаёт ключ и значение
  3. Потребитель печатает либо сохраняет запись
  4. Следующий запрос продолжает тот же стек
  5. return даёт отдельный итог после завершения

Попробуйте. У двух источников товары имеют одинаковые числовые ключи. Сохраните оба в результате.

Разобрать решение

Используйте iterator_to_array($generator, false), если ключи не являются частью договора. Если ключ — уникальный артикул, конфликт нужно обнаружить и обработать отдельно, а не бездумно перенумеровать.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

Generator::currentПрочитать текущее выданное значение, не переходя к следующему.

PHP 5.5+ · Core

Generator::current(): mixed

Прочитать текущее выданное значение, не переходя к следующему.

Параметр / результатЧто означает
ПараметрыНет.
РезультатЗначение текущего yield. Закрытый генератор даёт null; это не заменяет valid().
ВыполнениеПервый вызов запускает тело до yield; ошибки тела могут выйти наружу. Повторный current не продвигает обход.
Текущий артикул остаётся на месте
  1. $items = skus()
  2. current() → A-10
  3. current() → тот же A-10
  4. next() → только теперь B-20
Читать один элемент дваждыСамостоятельный пример
<?php
function skus() { yield 'A-10'; yield 'B-20'; }
$items = skus();
echo $items->current(), "\n", $items->current(), "\n";
$items->next();
echo $items->current(), "\n";
Ожидаемый результат
A-10
A-10
B-20

current — чтение позиции, next — её смена. Полезно сначала проверить товар, затем решать, принимать его или завершать импорт.

Отсутствующее показание не равно отсутствующей строкеСамостоятельный пример
<?php
function readings() { yield null; }
$rows = readings();
echo $rows->valid() ? "Строка есть\n" : "Строк нет\n";
var_dump($rows->current());
$rows->next();
echo $rows->valid() ? "Строка есть\n" : "Строк нет\n";
var_dump($rows->current());
Ожидаемый результат
Строка есть
NULL
Строк нет
NULL

В двух состояниях current() даёт null, но valid() различает их. Нельзя использовать истинность текущего значения как условие обхода: 0, пустая строка и false тоже могут быть данными.

Рядом: Generator::valid, Generator::next, Generator::key. Карта выполнения.

Generator::keyПолучить ключ текущего элемента: например артикул, к которому относится цена.

PHP 5.5+ · Core

Generator::key(): mixed

Получить ключ текущего элемента: например артикул, к которому относится цена.

Параметр / результатЧто означает
ПараметрыНет.
РезультатКлюч текущего yield. Без явного ключа — автоматический целый индекс; у закрытого объекта null.
НюансКлючи потока не обязаны быть уникальными. При сборке массива повторный ключ может заменить старый элемент.
Одна выдача содержит ключ и значение
  1. yield "A-10" => 900
  2. key() → "A-10"
  3. current() → 900
  4. Собираем строку «A-10: 900»
Связать цену с артикуломСамостоятельный пример
<?php
function priceFeed() { yield 'A-10' => 900; }
$feed = priceFeed();
echo $feed->key(), ': ', $feed->current(), "\n";
Ожидаемый результат
A-10: 900

Метод key() тоже способен запустить ещё не начатое тело. key() читает ключ Generator; функция key($array) относится к внутреннему указателю массива и имеет другой объект работы.

Повторный ключ остаётся отдельным событием в потокеСамостоятельный пример
<?php
function updates() { yield 'A-10' => 900; yield 'A-10' => 950; }
$feed = updates();
while ($feed->valid()) {
    echo $feed->key(), ': ', $feed->current(), "\n";
    $feed->next();
}
Ожидаемый результат
A-10: 900
A-10: 950

Поток событий изменения цены хранит оба события в порядке. Это не словарь текущих цен. Если нужен только последний снимок, присваивайте по ключу осознанно; если история — сохраняйте список записей.

Рядом: Generator::current, Generator::valid, Generator::next. Карта выполнения.

Generator::nextПродолжить выполнение после текущего yield до следующего yield или конца.

PHP 5.5+ · Core

Generator::next(): void

Продолжить выполнение после текущего yield до следующего yield или конца.

Параметр / результатЧто означает
ПараметрыНет.
РезультатПолезного возвращаемого значения нет. Новый элемент читают current().
Связь с sendЭффект продвижения соответствует send(null). Входом текущего yield становится null.
Осторожноnext на свежем объекте проходит первый yield и движется дальше: так легко пропустить первый элемент. Исключения тела не скрываются.
Перейти, затем прочитать
  1. current() → 41
  2. next() продолжает тело
  3. current() → 52
  4. Следующий next() завершает обход
Перейти от одного заказа к следующему и обнаружить конецСамостоятельный пример
<?php
declare(strict_types=1);

function orders(): Generator { yield 'A-42'; yield 'A-43'; }
$orders = orders();
echo 'Подготовить: ', $orders->current(), "\n";
$orders->next();
echo 'Подготовить: ', $orders->current(), "\n";
$orders->next();
echo $orders->valid() ? "Остались заказы\n" : "Все заказы выданы\n";
Ожидаемый результат
Подготовить: A-42
Подготовить: A-43
Все заказы выданы

current читает текущую запись, next продолжает после её yield. После второго next источник завершён и valid отвечает false. Сам next ничего не возвращает: полезный результат читается отдельно. Для обычной обработки foreach выполняет эти шаги за вас.

Увидеть, почему первый next пропускает элементСамостоятельный пример
<?php
function orders() { yield 41; yield 52; }
$orders = orders();
$orders->next();
echo $orders->current(), "\n";
Ожидаемый результат
52

Для обычного полного обхода проще foreach. При собственном цикле начните с valid()/current()/rewind(), а next ставьте после обработки.

Рядом: Generator::current, Generator::send, Generator::rewind. Карта выполнения.

Generator::rewindПодготовить генератор к первому элементу; это не перемотка уже пройденной истории.

PHP 5.5+ · Core

Generator::rewind(): void

Подготовить генератор к первому элементу; это не перемотка уже пройденной истории.

Параметр / результатЧто означает
Параметры / результатПараметров нет; возвращаемого значения нет.
До началаИсполняет тело до первого yield включительно.
На первом yieldНичего не меняет; повторный вызов допустим.
После продвиженияВыбрасывает Exception. foreach вызывает rewind при входе, поэтому повторный foreach по начатому объекту может завершиться ошибкой.
Разрешённая подготовка и запрещённая перемотка
  1. Новый объект → rewind → первая позиция
  2. Ещё rewind → та же позиция
  3. next → вторая позиция
  4. rewind → Exception; для повтора создайте новый объект
Повторная подготовка не повторяет побочный эффектСамостоятельный пример
<?php
function requests() { echo "Подготовка\n"; yield 41; yield 52; }
$requests = requests();
$requests->rewind();
$requests->rewind();
echo $requests->current(), "\n";
Ожидаемый результат
Подготовка
41

Повторное rewind на первом yield не выполняет тело с начала. Это позволяет foreach начать работу с уже проверенным первым элементом.

После второго элемента нужен новый обходСамостоятельный пример
<?php
function orders() { yield 41; yield 52; }
$orders = orders();
$orders->rewind();
$orders->next();
try { $orders->rewind(); }
catch (Exception $error) { echo "Создайте новый обход\n"; }
foreach (orders() as $id) { echo $id, "\n"; }
Ожидаемый результат
Создайте новый обход
41
52

Новая функция orders() — новый генератор. Для живого API это также новый запрос и потенциально другой снимок данных; повторяемость источника нужно проектировать отдельно.

Рядом: Generator::next, Generator::valid, Generator::current. Карта выполнения.

Generator::getReturnПолучить окончательный итог после выдачи всех элементов.

PHP 7+ · Core

Generator::getReturn(): mixed

Получить окончательный итог после выдачи всех элементов.

Параметр / результатЧто означает
ПараметрыНет.
РезультатЗначение return после нормального завершения; без явного значения — null.
ОшибкаException, если генератор ещё не завершил возврат, включая новый или прерванный исключением объект.
ВажноgetReturn не прокручивает генератор до конца. Итог не является последним yield.
Данные и итог идут по разным путям
  1. yield 41 → обработка заказа
  2. yield 52 → обработка заказа
  3. return 2 → завершение
  4. getReturn() → число обработанных заказов
Разделить записи и итоговый счётчикСамостоятельный пример
<?php
function exportedOrders() { yield 41; yield 52; return 2; }
$orders = exportedOrders();
foreach ($orders as $id) { echo 'Заказ ', $id, "\n"; }
echo 'Выгружено: ', $orders->getReturn(), "\n";
Ожидаемый результат
Заказ 41
Заказ 52
Выгружено: 2

Читатель получает два заказа; итог 2 доступен отдельно, когда генератор дошёл до return. Не храните return в том же списке, что и заказы.

Слишком раннее чтение результатаСамостоятельный пример
<?php
function job() { yield 'обработать'; return 'готово'; }
$job = job();
try { $job->getReturn(); }
catch (Exception $error) { echo "Итог ещё не готов\n"; }
foreach ($job as $step) { echo $step, "\n"; }
echo $job->getReturn(), "\n";
Ожидаемый результат
Итог ещё не готов
обработать
готово

Проверка valid() false сама по себе не доказывает успешный return после ошибки. Исключения задачи обрабатывают в момент исполнения, а успех фиксируют отдельно.

Рядом: Generator::valid, Generator::next, Generator::throw. Карта выполнения.

Generator::sendПередать значение в текущую точку yield и продолжить генератор.

PHP 5.5+ · Core

Generator::send(mixed $value): mixed

Передать значение в текущую точку yield и продолжить генератор.

Параметр / результатЧто означает
$valueЛюбое PHP-значение; становится результатом текущего выражения yield внутри тела.
РезультатЗначение следующего yield, либо null после завершения. Итог return читают getReturn().
Свежий объектСам доходит до первого yield перед передачей. Если первый запрос нужен снаружи, прочитайте current до send.
ОшибкаИсключения из продолженного тела выходят вызывающему коду; значение надо проверять по своему протоколу.
Двусторонняя связь на конкретном месте
  1. $quantity = yield "Количество?" → current() даёт вопрос
  2. send(3) → $quantity получает 3
  3. Тело считает 3 * 900
  4. Следующий yield → send возвращает 2700
Передать количество обратно в расчёт предложенияСамостоятельный пример
<?php
declare(strict_types=1);

function quote(): Generator
{
    $quantity = yield 'Сколько упаковок чая нужно?';
    if (!is_int($quantity) || $quantity < 1) { throw new InvalidArgumentException('Нужно положительное целое количество'); }
    yield 'Предложение: ' . ($quantity * 25900) . ' коп.';
}
$quote = quote();
echo $quote->current(), "\n";
echo $quote->send(2), "\n";
Ожидаемый результат
Сколько упаковок чая нужно?
Предложение: 51800 коп.

Строка выходит наружу через yield. send(2) продолжает функцию и делает 2 результатом этого yield внутри: $quantity получает 2. Следующее выданное значение возвращается из send. Так устроен двусторонний обмен; обычной однократной калькуляции достаточно функции с параметром.

Получить решение резерва после ответа складаСамостоятельный пример
<?php
function reservation() {
    $stock = yield ['sku' => 'A-10'];
    if (!is_int($stock) || $stock < 0) { throw new InvalidArgumentException('Некорректный остаток'); }
    return $stock >= 2 ? 'Можно резервировать' : 'Недостаточно';
}
$task = reservation();
$request = $task->current();
echo 'Проверяем ', $request['sku'], "\n";
$next = $task->send(5);
echo $next === null && !$task->valid() ? "Других запросов нет\n" : "Нужны дополнительные данные\n";
echo $task->getReturn(), "\n";
Ожидаемый результат
Проверяем A-10
Других запросов нет
Можно резервировать

send возвращает следующее yield, а окончательный return — отдельный getReturn. Здесь источник завершился сразу после решения. Проверка valid отличает конец от законного yield null.

Рядом: Generator::current, Generator::getReturn, Generator::throw. Карта выполнения.

Generator::throwСообщить об ошибке в месте, где генератор ждёт ответ.

PHP 5.5+ · Core

Generator::throw(Throwable $exception): mixed

Сообщить об ошибке в месте, где генератор ждёт ответ.

Параметр / результатЧто означает
$exceptionОбъект Exception или Error, реализующий Throwable.
ПоведениеКак если бы текущий yield выбросил этот объект. catch внутри тела может обработать его и продолжить.
РезультатСледующее выданное значение, либо null при нормальном завершении. Не пойманная ошибка выходит наружу.
Закрытый объектОшибка выбрасывается в вызывающем коде; генератор не перезапускается.
Сбой внешнего сервиса возвращается к ожидающему коду
  1. yield «Запросить доставку»
  2. Клиент получил отказ
  3. throw(new RuntimeException(...)) → catch в генераторе
  4. yield «Ручная проверка» → решение снаружи
Заменить недоступный расчёт ручной проверкойСамостоятельный пример
<?php
function shipping() {
    try { yield 'Запросить доставку'; }
    catch (RuntimeException $error) { yield 'Ручная проверка'; }
}
$task = shipping();
echo $task->current(), "\n";
echo $task->throw(new RuntimeException('Сервис недоступен')), "\n";
Ожидаемый результат
Запросить доставку
Ручная проверка

Сбой не превращается в нулевую цену. Здесь catch предлагает другой явно названный процесс. После второго yield задача ещё приостановлена; следующий next завершит её.

Закрытый генератор уже не обрабатывает сообщениеСамостоятельный пример
<?php
function done() { if (false) { yield; } }
$task = done();
$task->valid();
try { $task->throw(new RuntimeException('Поздний ответ')); }
catch (RuntimeException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
Поздний ответ

После окончания тела точек ожидания нет. Снаружи нужен контроль жизненного цикла задач; поздний сетевой ответ нельзя отправлять в уже завершённую работу.

Рядом: Generator::send, Generator::valid, Generator::getReturn. Карта выполнения.

Generator::validПроверить, есть ли текущий элемент, независимо от его значения.

PHP 5.5+ · Core

Generator::valid(): bool

Проверить, есть ли текущий элемент, независимо от его значения.

Параметр / результатЧто означает
ПараметрыНет.
Результатtrue, пока имеется текущий yield; false для закрытого/исчерпанного генератора.
Первый вызовЗапускает ещё не начатое тело до yield/конца. Поэтому проверка может выполнить ввод-вывод или выбросить ошибку источника.
Не проверяетКорректность данных, успешность задачи и наличие ненулевого значения.
Наличие записи отдельно от её остатка
  1. yield 0 → valid true
  2. Читатель видит остаток 0
  3. next → конец
  4. valid false → больше записей нет
Нулевой остаток нельзя потерятьСамостоятельный пример
<?php
function stocks() { yield 0; yield 4; }
$stocks = stocks();
while ($stocks->valid()) {
    echo 'Остаток: ', $stocks->current(), "\n";
    $stocks->next();
}
Ожидаемый результат
Остаток: 0
Остаток: 4

Условие while ($stocks->current()) остановилось бы на нуле. valid проверяет состояние обхода, а не истинность значения.

Даже проверка наличия записи запускает ленивый источникСамостоятельный пример
<?php
declare(strict_types=1);

function batch(): Generator { echo "Открыли каталог\n"; yield 'Чай'; }
$batch = batch();
echo "Создан отложенный обход\n";
echo $batch->valid() ? "Первый товар доступен\n" : "Каталог пуст\n";
echo $batch->valid() ? "Тот же товар всё ещё доступен\n" : "Каталог пуст\n";
echo $batch->current(), "\n";
Ожидаемый результат
Создан отложенный обход
Открыли каталог
Первый товар доступен
Тот же товар всё ещё доступен
Чай

Первый valid продвигает незапущенный генератор до первого yield. Второй valid не переходит к следующему товару и не повторяет открытие источника. Учитывайте этот побочный эффект, если функция внутри читает файл или выполняет запрос.

Рядом: Generator::current, Generator::next, Generator::getReturn. Карта выполнения.

Generator::__wakeupИсторическая страница о запрете восстановления Generator; современный PHP не предоставляет этот метод.

Историческая страница; метода нет в PHP 8.2/8.5 · Core

Историческая сигнатура: Generator::__wakeup(): void

Историческая страница о запрете восстановления Generator; современный PHP не предоставляет этот метод.

Параметр / результатЧто означает
Параметры в историческом описанииНет.
Официальная страницаОписывает служебный метод, который выбрасывает Exception при восстановлении.
Проверенная современная версияВ PHP 8.5.11 вызов несуществующего __wakeup даёт Error. В исходных объявлениях PHP 8.2.0 и 8.5.11 метода тоже нет. Не используйте эту сигнатуру в новом коде.
Действующий запретserialize($generator) выбрасывает Exception. В очереди храните данные задания и отметку прогресса, а не остановленное исполнение.
ГраницаСериализация обычного массива задания не делает безопасным unserialize чужих объектов; предпочтите проверяемый JSON-контракт.
Сохранить намерение, затем создать новое выполнение
  1. Generator хранит текущий код и локальное состояние
  2. serialize($generator) → Exception
  3. Сохраняем [after_id => 41]
  4. После запуска создаём новый генератор с этим параметром
Хранить закладку выгрузки вместо активного генератораСамостоятельный пример
<?php
function orders() { yield 41; }
$orders = orders();
try { serialize($orders); }
catch (Exception $error) { echo "Генератор не сериализуется\n"; }
echo method_exists($orders, '__wakeup') ? "У этой версии есть исторический метод\n" : "Исторический __wakeup в текущей версии отсутствует\n";
Ожидаемый результат
Генератор не сериализуется
Исторический __wakeup в текущей версии отсутствует

Сериализация активного стека генератора запрещена. Карточка сохраняет историческое имя из полного Manual и явно отделяет его от текущего API. Следующий практический пример сохраняет after_id в JSON и создаёт новый обход.

Сохранить данные продолжения вместо объектаСамостоятельный пример
<?php
function ordersAfter(int $lastId): Generator {
    foreach ([41, 52, 63] as $id) { if ($id > $lastId) { yield $id; } }
}
$checkpoint = json_encode(['after_id' => 41], JSON_THROW_ON_ERROR);
$state = json_decode($checkpoint, true, flags: JSON_THROW_ON_ERROR);
foreach (ordersAfter($state['after_id']) as $id) { echo $id, "\n"; }
Ожидаемый результат
52
63

JSON здесь создан той же программой; при чтении из внешнего хранилища проверяют схему и тип after_id. В реальном импорте надёжный checkpoint записывают после подтверждения обработки; нужны порядок источника и защита от повторного эффекта, а не только номер.

Рядом: Generator::rewind, Generator::getReturn, Generator::current. Карта выполнения.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.