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

Дерево руководства
Глава 65 · От первого yield к управлению обходом
Generator: выдача по одному элементу и полный справочник методов
От двух артикулов и первого yield до потока файлов, обмена сообщениями, делегирования и каждого метода Generator.
Получать позиции по одной: зачем нужен генератор
Два артикула: от вызова функции к текущему элементу
Функция productCodes описывает выдачу. Её вызов создаёт Generator, а foreach просит значения по одному.
- 1productCodes()↓
Создаёт объект; тело ещё не выполняется.
- 2foreach просит элемент↓
Тело доходит до 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 печатает её.
- productLabels() создаёт Generator; тело ещё не исполнено
- foreach просит элемент → yield выдаёт Чай — 25900 коп.
- В $label приходит эта строка → echo печатает её с префиксом Печать
- Следующий шаг продолжает тело → Кофе — 45000 коп.
- Конец функции завершает обход
<?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 получает этот итог; для обычного массива такого итога нет.
catalog()выдаёт A-10yield from extra()передаёт B-20 читателюextra()возвращает 1 →$extraCountcatalog()возвращает 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 читателя использует & перед переменной. Эта возможность соединяет две области кода: изменение снаружи влияет на то, что будет дальше внутри. Для обычного экспорта такая связь лишняя.
<?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 закрывает открытый файл при завершении или уничтожении запущенного генератора.
- fopen открывает orders.txt
- fgets читает строку 41 → yield
- Читатель обрабатывает 41; генератор хранит открытый ресурс
- После окончания / освобождения генератора 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 убирают или заменяют подходящим логом. Обработка огромной отдельной строки всё равно потребует памяти на эту строку.
<?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 |
| Коллекция создаёт новый обход на каждый foreach | IteratorAggregate с 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 на внутреннем составе этой диагностики. Она не исполняет обход и не содержит все выданные записи.
- Создан orderExport()
- __debugInfo() → function = orderExport
- Тело ещё не выполнялось
- 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 показывает паузу по строкам вывода. Здесь каждое уточнение отвечает на практический вопрос: потеряются ли ключи двух партий, можно ли изменить исходные остатки, когда закроется файл и можно ли повторить обход.
- Источник предоставляет очередную запись
- Генератор выдаёт ключ и значение
- Потребитель печатает либо сохраняет запись
- Следующий запрос продолжает тот же стек
- return даёт отдельный итог после завершения
Попробуйте. У двух источников товары имеют одинаковые числовые ключи. Сохраните оба в результате.
Разобрать решение
Используйте iterator_to_array($generator, false), если ключи не являются частью договора. Если ключ — уникальный артикул, конфликт нужно обнаружить и обработать отдельно, а не бездумно перенумеровать.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
Generator::currentПрочитать текущее выданное значение, не переходя к следующему.
PHP 5.5+ · Core
Generator::current(): mixedПрочитать текущее выданное значение, не переходя к следующему.
| Параметр / результат | Что означает |
|---|---|
| Параметры | Нет. |
| Результат | Значение текущего yield. Закрытый генератор даёт null; это не заменяет valid(). |
| Выполнение | Первый вызов запускает тело до yield; ошибки тела могут выйти наружу. Повторный current не продвигает обход. |
- $items = skus()
- current() → A-10
- current() → тот же A-10
- 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. |
| Нюанс | Ключи потока не обязаны быть уникальными. При сборке массива повторный ключ может заменить старый элемент. |
- yield "A-10" => 900
- key() → "A-10"
- current() → 900
- Собираем строку «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 и движется дальше: так легко пропустить первый элемент. Исключения тела не скрываются. |
- current() → 41
- next() продолжает тело
- current() → 52
- Следующий 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 выполняет эти шаги за вас.
<?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 по начатому объекту может завершиться ошибкой. |
- Новый объект → rewind → первая позиция
- Ещё rewind → та же позиция
- next → вторая позиция
- 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. |
- yield 41 → обработка заказа
- yield 52 → обработка заказа
- return 2 → завершение
- 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. |
| Ошибка | Исключения из продолженного тела выходят вызывающему коду; значение надо проверять по своему протоколу. |
$quantity = yield "Количество?"→ current() даёт вопросsend(3)→ $quantity получает 3- Тело считает
3 * 900 - Следующий 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 при нормальном завершении. Не пойманная ошибка выходит наружу. |
| Закрытый объект | Ошибка выбрасывается в вызывающем коде; генератор не перезапускается. |
- yield «Запросить доставку»
- Клиент получил отказ
- throw(new RuntimeException(...)) → catch в генераторе
- 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/конца. Поэтому проверка может выполнить ввод-вывод или выбросить ошибку источника. |
| Не проверяет | Корректность данных, успешность задачи и наличие ненулевого значения. |
- yield 0 → valid true
- Читатель видит остаток 0
- next → конец
- 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-контракт. |
- Generator хранит текущий код и локальное состояние
- serialize($generator) → Exception
- Сохраняем [after_id => 41]
- После запуска создаём новый генератор с этим параметром
<?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. Карта выполнения.
Источники и обсуждения
- PHP: language.generators
- PHP: language.generators.overview
- PHP: language.generators.syntax
- PHP: language.generators.comparison
- PHP: class.generator
- Generator в PHP 8.4: фактический состав класса
- Generator в PHP 8.5.11
- Обсуждение: Почему первое значение не увидели перед send; итог return и элемент yield различаются. — обсуждение проблемы
- Обсуждение: send продвигает генератор: важны точное место входа и следующий yield. — обсуждение проблемы
- Обсуждение: Делегирование и повторный обход зависят от вида и состояния источника. — обсуждение проблемы
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


