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

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

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

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

Глава 66 · От первого переключения к протоколу задач

Fiber: пауза, продолжение, состояния и все методы

Как данные выходят через suspend и возвращаются через resume; зачем нужен управляющий код, где находится итог и какие ошибки нельзя скрывать.

Пауза посреди задачи: зачем нужен Fiber

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

Ответ возвращается ровно в место suspend

Задача просит цену A-10. Управляющий код получает запрос и передаёт ответ 900 обратно.

  1. start(«A-10»)

    Артикул попадает в параметр callback $sku.

  2. suspend(запрос)

    Задача сохраняет стек; start возвращает запрос наружу.

  3. resume(900)

    Управляющий код передаёт ответ в ожидающую задачу.

  4. $price = 900

    Выражение suspend возвращает ответ; тело продолжает работу.

  5. return → getReturn()

    Окончательная подпись читается отдельно от значений пауз.

Fiber не создаёт поток и сам не выполняет сетевой запрос. В примере ответ заранее подготовлен; порядок продолжения задаёт управляющий код.

Для расчёта доставки требуется расстояние до покупателя. Покажем исполнителя, который сообщает о начале, отдаёт вопрос наружу и после ответа 15 км возвращает 300 руб. Управляющий код успевает выполнить свой шаг между вопросом и продолжением.

Начните с обычной функции и анонимной функции. new Fiber(function () { ... }) сохраняет действие в объекте $calculation. ->start() запускает его. Внутренний Fiber::suspend(...) отдаёт вопрос наружу и приостанавливает стек. ->resume(15) продолжает выполнение: выражение suspend внутри получает 15 в $kilometres. После return результат читается отдельным ->getReturn().

Один расчёт, два направления данных
  1. new Fiber сохраняет callback в $calculation
  2. start запускает расчёт; suspend отдаёт вопрос о расстоянии
  3. Главный код получает вопрос и готовит товары
  4. resume(15) → внутри $kilometres = 15
  5. return формирует 300 руб.; getReturn получает готовую подпись
Расчёт доставки ждёт ответ о расстоянииСамостоятельный пример
<?php
declare(strict_types=1);

$calculation = new Fiber(function () {
    echo "Расчёт доставки начат\n";
    $kilometres = Fiber::suspend('Нужно расстояние до покупателя');
    return 'Доставка: ' . ($kilometres * 20) . ' руб.';
});
echo $calculation->start(), "\n";
echo "Главный код может подготовить товары\n";
$calculation->resume(15);
echo $calculation->getReturn(), "\n";
Ожидаемый результат
Расчёт доставки начат
Нужно расстояние до покупателя
Главный код может подготовить товары
Доставка: 300 руб.

new Fiber хранит callback, start начинает его. suspend отдаёт запрос наружу и сохраняет место выполнения. resume(15) передаёт 15 внутрь как результат suspend. getReturn читает итог после завершения. Цена 20 руб./км — учебное правило; сетевого ожидания и параллельного потока здесь нет.

Три разных значения: старт, обмен во время паузы и итог

Пусть задача получает артикул tea, просит цену, а затем возвращает подпись. Аргументы start попадают в параметры callback. Значение suspend уходит наружу как результат start/resume/throw. Вход resume попадает внутрь как результат suspend. Окончательный return callback читают getReturn; start и resume вместо него возвращают null.

Путь tea, запроса price и числа 25900 копеек
  1. start("tea") → параметр $sku
  2. Fiber::suspend(["price_for" => $sku]) → start вернул запрос
  3. resume(25900) → выражение suspend внутри вернуло 25900 → $priceKopecks
  4. return $sku . ": " . $priceKopecks → callback завершён; resume вернул null
  5. getReturn() → строка «tea: 25900 коп.»
Аргумент запуска, ответ при продолжении и итог — разные каналыСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function (string $sku) {
    $priceKopecks = Fiber::suspend(['price_for' => $sku]);
    return $sku . ': ' . $priceKopecks . ' коп.';
});
$request = $task->start('tea');
echo 'Нужна цена: ', $request['price_for'], "\n";
$nextRequest = $task->resume(25900);
echo $nextRequest === null && $task->isTerminated() ? "Новых запросов нет: расчёт завершён\n" : "Задача ещё ждёт\n";
echo 'Итог: ', $task->getReturn(), "\n";
Ожидаемый результат
Нужна цена: tea
Новых запросов нет: расчёт завершён
Итог: tea: 25900 коп.

tea идёт в параметр callback; массив price_for выходит через suspend; 25900 идёт обратно через resume; окончательная строка получается через getReturn. resume сам не возвращает return callback. Проверка isTerminated отличает завершение от паузы с null.

null как результат start/resume не доказывает окончание: задача могла вызвать suspend(null). Используйте isSuspended/isTerminated и известный договор задачи. У тела без явного return нормальный итог тоже null.

Карта состояний и допустимые переходы

СостояниеЧто можно сделатьФлаги started / suspended / running / terminated
Создан, не запущенstart(args)false / false / false / false
ВыполняетсяВнутри может вызвать suspend; повторный start/resume недопустимtrue / false / true / false
Приостановленresume(value) или throw(error)true / true / false / false
Завершён returngetReturn; повторный запуск невозможенtrue / false / false / true
Завершён ошибкойОбработать ошибку снаружи; успешного getReturn нетtrue / false / false / true

Флаг isRunning означает, что начатый вызов start/resume/throw ещё не вернулся. Он не равен «именно этот Fiber сейчас находится на вершине вложенных вызовов»: родитель, вызвавший дочерний Fiber, может считаться running, пока выполняется ребёнок. Текущий объект точно показывает getCurrent. Флаги — сведения о жизненном цикле, а не проверка успешности бизнес-операции.

Пауза без запроса всё ещё отличается от завершенияСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () {
    Fiber::suspend();
    return 'Накладная готова';
});
$task->start();
echo $task->isSuspended() ? "Накладная ждёт продолжения\n" : "Нет паузы\n";
$task->resume();
echo $task->isTerminated() ? "Задача завершена\n" : "Задача ещё выполняется\n";
echo $task->getReturn(), "\n";
Ожидаемый результат
Накладная ждёт продолжения
Задача завершена
Накладная готова

suspend без аргумента отдаёт null. Завершающий resume тоже возвращает null, поэтому значение само по себе не является состоянием Fiber. isSuspended и isTerminated отвечают на разные вопросы. getReturn допустим только после нормального завершения.

Вложенная функция остаётся обычной функцией

Ключевой выигрыш Fiber — пауза может находиться глубоко в стеке вызовов. Функция loadStock возвращает int, а вызывающая её renderStock возвращает string; им не нужно возвращать Generator и писать yield from на каждом уровне. Когда loadStock приостанавливается, сохраняется вся эта цепочка. Вне Fiber такой вызов suspend выдаст FiberError.

main → Fiber → renderStock → loadStock → назад по той же цепочке
  1. start → renderStock("A-10")
  2. renderStock вызывает loadStock("A-10")
  3. loadStock вызывает suspend → наружу уходит артикул; оба обычных вызова ждут
  4. resume(5) → loadStock возвращает 5
  5. renderStock возвращает «A-10: 5» → getReturn читает подпись
Два уровня обычных вызовов вокруг паузыСамостоятельный пример
<?php
function loadStock(string $sku): int {
    $stock = Fiber::suspend($sku);
    if (!is_int($stock) || $stock < 0) { throw new UnexpectedValueException('Неверный остаток'); }
    return $stock;
}
function renderStock(string $sku): string { return $sku . ': ' . loadStock($sku); }
$task = new Fiber(fn() => renderStock('A-10'));
echo 'Запрошен ', $task->start(), "\n";
$task->resume(5);
echo $task->getReturn(), "\n";
Ожидаемый результат
Запрошен A-10
A-10: 5

fn() => — короткая анонимная функция; callback запускается без аргументов. Проверка защищает договор loadStock: внешний ответ не обязан быть правильным. Пример не подключается к складу; он показывает точное место, куда попадёт ответ адаптера.

Приостановка допустима и в callback, который вызван внутренней функцией PHP, например array_map, или в методе Iterator во время foreach. Это не разрешает блокирующий ввод-вывод «само собой»: библиотека должна знать, как получить готовый ответ и возобновить именно эту задачу.

Пауза внутри callback array_mapСамостоятельный пример
<?php
$task = new Fiber(function () {
    return array_map(function ($sku) { return Fiber::suspend($sku); }, ['A-10', 'B-20']);
});
echo $task->start(), "\n";
echo $task->resume(5), "\n";
$task->resume(8);
echo implode(', ', $task->getReturn()), "\n";
Ожидаемый результат
A-10
B-20
5, 8

array_map получает результат callback после каждого возобновления и складывает его в итоговый массив. Снаружи два ответа подставлены последовательно. Этот пример не делает два сетевых запроса параллельными.

Две задачи и маленький управляющий цикл

Планировщик решает, какую приостановленную задачу продолжить. Ниже учебный диспетчер с готовыми ответами: сначала запускает обе задачи, затем выдаёт каждой ответ. Очерёдность видна в выводе; ожидания сети и таймеров здесь нет. Так проще понять обязанности event loop до подключения настоящей библиотеки.

Управляющий код обслуживает два независимых ожидания
  1. start задачи «Склад» → ждёт
  2. start задачи «Доставка» → ждёт
  3. resume «Склад» с 5 → результат «Склад: 5»
  4. resume «Доставка» с 300 → результат «Доставка: 300»
Чередование двух задач с готовыми ответамиСамостоятельный пример
<?php
function createTask(string $name): Fiber {
    return new Fiber(function () use ($name) {
        $reply = Fiber::suspend($name);
        return $name . ': ' . $reply;
    });
}
$tasks = ['Склад' => createTask('Склад'), 'Доставка' => createTask('Доставка')];
$replies = ['Склад' => 5, 'Доставка' => 300];
foreach ($tasks as $task) { echo 'Ожидает ', $task->start(), "\n"; }
foreach ($tasks as $name => $task) {
    if ($task->isSuspended()) { $task->resume($replies[$name]); }
    echo $task->getReturn(), "\n";
}
Ожидаемый результат
Ожидает Склад
Ожидает Доставка
Склад: 5
Доставка: 300

use ($name) сохраняет имя в анонимной функции. Реальный диспетчер дополнительно хранит тайм-ауты, ошибки, отмену, ограничения числа задач и соответствие ответа запросу. Этот короткий цикл не заменяет промышленный event loop.

У каждой задачи свой стек и сохранённые локальные значения. Тысячи приостановленных задач могут удерживать много объектов, буферов и открытых соединений. Ограничивайте количество одновременных операций. Если нужен только последовательный поток записей, Generator проще и не требует протокола возобновления целого стека.

Отмена, finally и версия PHP

Отмену можно выразить исключением, отправленным через throw в приостановленную задачу. Это кооперативный протокол: задача должна отдавать управление, а обработчик должен договориться, что означает эта ошибка. Нельзя таким способом прервать произвольный бесконечный цикл, который сейчас работает. finally удобно освобождает ресурс при выходе, но не должен быть единственной гарантией критического бизнес-действия.

Отмена завершает ожидание и выполняет очистку
  1. start → задача ждёт
  2. throw(RuntimeException) → исключение на suspend
  3. finally освобождает ресурс
  4. Ошибку получает вызывающий код; getReturn не даёт успех
Отмена расчёта освобождает локальный ресурсСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () {
    try { Fiber::suspend('Ожидаем расстояние'); }
    finally { echo "Ресурс расчёта освобождён\n"; }
});
$task->start();
try { $task->throw(new RuntimeException('Покупатель отменил расчёт')); }
catch (RuntimeException $error) { echo $error->getMessage(), "\n"; }
echo $task->isTerminated() ? "Продолжать задачу больше нельзя\n" : "Задача ещё жива\n";
Ожидаемый результат
Ресурс расчёта освобождён
Покупатель отменил расчёт
Продолжать задачу больше нельзя

throw вводит исключение в приостановленное место. Оно раскручивает стек, выполняется finally, затем исключение выходит в вызывающий код. Такая отмена кооперативная: она не прервёт произвольный бесконечный участок, который не отдал управление.

Fiber доступен с PHP 8.1 без отдельной установки. Класс final; наследовать, клонировать или сериализовать остановленную задачу нельзя. До PHP 8.4 переключение Fiber во время деструктора запрещалось. Начиная с 8.4 правила изменились; при сборке циклов деструктор может выполняться в отдельном служебном Fiber. Подробный пример деструктора и сборщика показывает эту границу. Не привязывайте запрос, пользователя или транзакцию к предположению, что любой деструктор выполняется в исходном рабочем контексте.

Следующий шаг: пройдите контракты start/resume/suspend/getReturn и научитесь различать данные промежуточной паузы и окончательный результат. Затем применяйте готовую асинхронную библиотеку по её договору. Для фоновой обработки вне срока HTTP-запроса изучите архитектуру задач и повторы и ограничения запросов.

Чем Fiber помогает и что остаётся планировщику

Fiber сохраняет обычный стек вызовов; это позволяет loadStock вернуть int после паузы без yield from на каждом уровне. Но кто получит данные и когда вызовет resume, решает внешний код. Event loop — цикл, который ждёт готовности операций и продолжает нужные задачи. Сам new Fiber не создаёт ни сеть, ни отдельный процесс.

Нужен результатПервый выбор
Обойти каталог по одному товаруGenerator
Приостановить вложенные обычные вызовыFiber плюс управляющий код
Выполнить блокирующую тяжёлую работу параллельноОтдельный процесс/подходящая инфраструктура
Много HTTP-ожиданийНеблокирующий транспорт и event loop; Fiber может упростить запись

Попробуйте. В примере расчёта передайте 0 км, затем отдельно 15 км.

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

Создайте новый Fiber на каждый независимый расчёт: завершённый экземпляр нельзя запустить заново. Получите 0 и 300 руб. Если ноль невозможен по договору, добавьте проверку внутрь callback до вычисления.

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

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

Fiber::__constructСоздать приостанавливаемую задачу из callable, не выполняя её сразу.

PHP 8.1+ · Core

new Fiber(callable $callback)

Создать приостанавливаемую задачу из callable, не выполняя её сразу.

Параметр / результатСмысл
$callbackФункция, Closure или другой допустимый callable. Аргументы придут позже из start.
Результат выражения newНовый объект Fiber; сам конструктор не возвращает результат callback.
ОшибкиНедопустимый callable даёт TypeError; ошибки тела возникают уже при start/resume.
ВерсияPHP 8.1+. final-класс; отдельное расширение не требуется.
Сохранить действие, затем явно выполнить
  1. function объявляет действие
  2. new Fiber(action) сохраняет его
  3. isStarted() → false
  4. start() выполняет тело
Создать задачу расчёта, затем отдельно её запуститьСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () { echo "Рассчитываем доставку\n"; });
echo "Задача добавлена в список\n";
echo $task->isStarted() ? "Уже выполнялась\n" : "Расчёт ещё не начался\n";
$task->start();
Ожидаемый результат
Задача добавлена в список
Расчёт ещё не начался
Рассчитываем доставку

Конструктор Fiber получает callable, но не запускает его. Это позволяет собрать список задач до первого выполнения. Тело начинает start, как видно по последней строке.

Использовать обычную именованную функциюСамостоятельный пример
<?php
function makeLabel(string $sku): string { return 'Товар ' . $sku; }
$task = new Fiber('makeLabel');
$task->start('A-10');
echo $task->getReturn(), "\n";
Ожидаемый результат
Товар A-10

Строка makeLabel указывает на заранее объявленную функцию; не принимайте произвольное имя обработчика от посетителя. Эта простая функция не приостанавливается: для неё обычный makeLabel("A-10") практичнее. Пример показывает допустимый callable, а не пользу оборачивания любого вызова в Fiber.

Рядом: Fiber::start, Fiber::isStarted, Fiber::getReturn. Карта состояний.

Fiber::startПервый и единственный запуск Fiber с аргументами его callback.

PHP 8.1+ · Core

Fiber::start(mixed ...$args): mixed

Первый и единственный запуск Fiber с аргументами его callback.

Параметр / результатСмысл
$argsНоль или больше аргументов callback в том же порядке. Его типы/обязательные параметры продолжают действовать.
РезультатЗначение первого suspend, либо null при нормальном завершении callback.
ОшибкиПовторный start даёт FiberError. Ошибка тела до паузы выходит из start; неверные аргументы могут дать TypeError/ArgumentCountError.
Не являетсяАвтоматическим запуском в фоне и способом прочитать окончательный return.
Аргументы идут в тело, значение паузы — обратно
  1. start(41) → $orderId = 41
  2. suspend("Проверить 41")
  3. start возвращает строку запроса
  4. После resume окончательный return читается отдельно
Передать номер заказа при запускеСамостоятельный пример
<?php
$task = new Fiber(function ($orderId) { Fiber::suspend('Проверить ' . $orderId); });
echo $task->start(41), "\n";
$task->resume();
Ожидаемый результат
Проверить 41

start передаёт вход callback. Сам номер заказа не обязан храниться в глобальной переменной: он принадлежит конкретному запуску задачи.

Повторный запуск — ошибка состоянияСамостоятельный пример
<?php
$task = new Fiber(function () { return 'готово'; });
var_dump($task->start());
echo $task->getReturn(), "\n";
try { $task->start(); }
catch (FiberError $error) { echo "Нужен новый Fiber\n"; }
Ожидаемый результат
NULL
готово
Нужен новый Fiber

Для новой независимой обработки создайте новый объект. Даже если первый callback сразу завершился без suspend, этот Fiber уже использован.

Рядом: Fiber::__construct, Fiber::resume, Fiber::getReturn. Карта состояний.

Fiber::resumeПродолжить только приостановленный Fiber и передать ему ответ.

PHP 8.1+ · Core

Fiber::resume(mixed $value = null): mixed

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

Параметр / результатСмысл
$valueЗначение для текущего выражения Fiber::suspend внутри задачи; по умолчанию null.
РезультатЗначение следующего suspend либо null при нормальном завершении. Итог callback — в getReturn.
Ошибка состоянияFiberError для нового, выполняющегося или уже завершённого Fiber.
Ошибка задачиНеперехваченное исключение из продолженного тела выходит из resume.
Ответ возвращается на сохранённую строку
  1. suspend("цена") → ждёт
  2. resume(900) → $price = 900
  3. Тело делает return $price * 2
  4. resume возвращает null; getReturn возвращает 1800
Продолжить расчёт суммы после получения ценыСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () {
    $priceKopecks = Fiber::suspend('Нужна цена упаковки чая');
    return $priceKopecks * 2;
});
echo $task->start(), "\n";
$task->resume(25900);
echo 'Две упаковки: ', $task->getReturn(), " коп.\n";
Ожидаемый результат
Нужна цена упаковки чая
Две упаковки: 51800 коп.

resume передаёт 25900 в приостановленный suspend как его результат. Код после паузы использует полученную цену. Окончательная сумма находится в getReturn; учебный ответ передан локально, сеть не выполнялась.

Не возобновлять незапущенную задачуСамостоятельный пример
<?php
$task = new Fiber(function () { Fiber::suspend(); });
try { $task->resume(); }
catch (FiberError $error) { echo "Сначала start\n"; }
$task->start();
if ($task->isSuspended()) { $task->resume(); }
echo $task->isTerminated() ? "Готово\n" : "Ждёт\n";
Ожидаемый результат
Сначала start
Готово

Проверка isSuspended пригодна управляющему коду. Она не заменяет обработку ошибок самого callback.

Рядом: Fiber::suspend, Fiber::start, Fiber::isSuspended. Карта состояний.

Fiber::throwВозобновить ожидание исключением вместо успешного ответа.

PHP 8.1+ · Core

Fiber::throw(Throwable $exception): mixed

Возобновить ожидание исключением вместо успешного ответа.

Параметр / результатСмысл
$exceptionОбъект Exception или Error. Он будет выброшен из текущего suspend внутри Fiber.
РезультатЗначение следующего suspend, либо null при нормальном завершении после обработки ошибки.
Если не пойманоИсключение выходит в вызывающий throw код; Fiber становится завершённым с ошибкой.
СостояниеТребуется приостановленный Fiber. Иначе FiberError, а не доставка вашего исключения в тело.
Отказ сервиса попадает в catch ожидающей задачи
  1. Задача suspend → ожидает цену
  2. Снаружи throw(RuntimeException)
  3. Внутри catch выбирает ручной расчёт
  4. Следующий suspend отдаёт «Проверить вручную»
Обработать отказ в том месте, где ожидался ответСамостоятельный пример
<?php
$task = new Fiber(function () {
    try { Fiber::suspend('Запросить цену'); }
    catch (RuntimeException $error) { Fiber::suspend('Проверить вручную'); }
});
echo $task->start(), "\n";
echo $task->throw(new RuntimeException('Тайм-аут')), "\n";
$task->resume();
Ожидаемый результат
Запросить цену
Проверить вручную

catch может создать новый этап ожидания. Поэтому после throw задача не обязательно завершена — это определяется тем, что сделало тело.

Необработанная ошибка возвращается наружуСамостоятельный пример
<?php
$task = new Fiber(function () { Fiber::suspend(); });
$task->start();
try { $task->throw(new RuntimeException('Доставка недоступна')); }
catch (RuntimeException $error) { echo $error->getMessage(), "\n"; }
var_dump($task->isTerminated());
try { $task->getReturn(); }
catch (FiberError $error) { echo "Успешного результата нет\n"; }
Ожидаемый результат
Доставка недоступна
bool(true)
Успешного результата нет

Отмена и ошибка не являются обычным return. Не пытайтесь считать getReturn успешным только по isTerminated().

Рядом: Fiber::resume, Fiber::getReturn, Fiber::isTerminated. Карта состояний.

Fiber::getReturnПрочитать окончательный return callback после нормального завершения.

PHP 8.1+ · Core

Fiber::getReturn(): mixed

Прочитать окончательный return callback после нормального завершения.

Параметр / результатСмысл
ПараметрыНет.
РезультатЗначение return callback; при нормальном выходе без значения — null.
FiberErrorДо запуска, во время ожидания/работы либо после окончания исключением.
Не делаетНе запускает задачу, не возобновляет её и не ждёт сеть.
Итог уже вычислен, осталось прочитать
  1. start → callback считает 900 + 300
  2. return 1200 → нормальное окончание
  3. start вернул null
  4. getReturn → 1200
Сумма находится в getReturn, а не в startСамостоятельный пример
<?php
$task = new Fiber(function () { return 900 + 300; });
var_dump($task->start());
echo 'Итого: ', $task->getReturn(), "\n";
echo 'Повторное чтение: ', $task->getReturn(), "\n";
Ожидаемый результат
NULL
Итого: 1200
Повторное чтение: 1200

Повторное чтение не запускает расчёт снова. В этом примере Fiber избыточен для сложения; в составной задаче итог получают тем же методом после последнего ответа.

Решение о наличии доступно только после ответа складаСамостоятельный пример
<?php
$task = new Fiber(function () { $stock = Fiber::suspend(); return $stock > 0; });
$task->start();
try { $task->getReturn(); }
catch (FiberError $error) { echo "Ещё ждём остаток\n"; }
$task->resume(5);
echo $task->getReturn() ? "Можно показать кнопку заказа\n" : "Товар закончился\n";
Ожидаемый результат
Ещё ждём остаток
Можно показать кнопку заказа

До resume решение не вычислено, getReturn даёт FiberError. Ответ 5 позволяет вернуть true и включить действие интерфейса. Булево значение полезно как решение, но не объясняет ошибки сети — их надо обрабатывать отдельно.

Рядом: Fiber::isTerminated, Fiber::resume, Fiber::throw. Карта состояний.

Fiber::isStartedУзнать, был ли Fiber уже запущен, в том числе если он теперь завершён.

PHP 8.1+ · Core

Fiber::isStarted(): bool

Узнать, был ли Fiber уже запущен, в том числе если он теперь завершён.

Параметр / результатСмысл
ПараметрыНет.
Результатfalse до первого запуска, true после начала исполнения.
Не означаетГотовность результата, текущую активность или возможность повторного start.
Факт запуска остаётся после конца
  1. new Fiber → false
  2. start → true
  3. callback завершён → всё ещё true
Не запускать готовый расчёт повторноСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () { return 'Смета доставки: 300 руб.'; });
echo $task->isStarted() ? "Уже запускалась\n" : "Задача новая\n";
$task->start();
echo $task->getReturn(), "\n";
echo $task->isStarted() ? "Повторный start запрещён\n" : "Можно запустить\n";
echo $task->isTerminated() ? "Для нового расчёта создайте новый Fiber\n" : "Расчёт продолжается\n";
Ожидаемый результат
Задача новая
Смета доставки: 300 руб.
Повторный start запрещён
Для нового расчёта создайте новый Fiber

isStarted остаётся true после завершения и не означает «выполняется прямо сейчас». isTerminated сообщает конец. Чтобы пересчитать другой заказ, создают другой Fiber; готовый результат первого можно читать повторно.

Инициализировать список задач ровно один разСамостоятельный пример
<?php
$tasks = [new Fiber(function () { echo "Проверка склада\n"; })];
foreach ([1, 2] as $pass) {
    foreach ($tasks as $task) { if (!$task->isStarted()) { $task->start(); } }
}
Ожидаемый результат
Проверка склада

Второй проход управляющего кода не выполняет повторный start. Для перезапуска самой проверки понадобится новый Fiber, а не изменение флага.

Рядом: Fiber::start, Fiber::isSuspended, Fiber::isTerminated. Карта состояний.

Fiber::isSuspendedПроверить, ждёт ли задача в точке suspend и может ли принять ответ.

PHP 8.1+ · Core

Fiber::isSuspended(): bool

Проверить, ждёт ли задача в точке suspend и может ли принять ответ.

Параметр / результатСмысл
ПараметрыНет.
Результатtrue только для приостановленного Fiber.
ПрактикаПроверка перед resume/throw. Не определяет, какой ответ нужен и не проверяет его данные.
Ответ разрешён только в промежуточном состоянии
  1. Новый → false
  2. start до suspend → true
  3. resume до return → false
Отправлять ответ только ожидающему расчётуСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () { return Fiber::suspend('Нужно расстояние'); });
echo $task->isSuspended() ? "Ждёт ответ\n" : "Ожидание ещё не началось\n";
echo $task->start(), "\n";
echo $task->isSuspended() ? "Можно передать расстояние\n" : "Ответ сейчас не нужен\n";
$task->resume(15);
echo $task->isSuspended() ? "Ждёт дальше\n" : "Ожидание закончено\n";
echo 'Получено: ', $task->getReturn(), " км\n";
Ожидаемый результат
Ожидание ещё не началось
Нужно расстояние
Можно передать расстояние
Ожидание закончено
Получено: 15 км

Созданный Fiber ещё не приостановлен. После suspend он принимает resume/throw, после завершения — уже нет. Поздний ответ нужно сверять с состоянием, а не отправлять в любой сохранённый объект задачи.

Поздний повторный ответ не отправляется в завершённую задачуСамостоятельный пример
<?php
$task = new Fiber(function () { return Fiber::suspend(); });
$task->start();
foreach ([5, 6] as $reply) {
    if ($task->isSuspended()) { $task->resume($reply); }
    else { echo "Повторный ответ пропущен\n"; }
}
echo 'Результат: ', $task->getReturn(), "\n";
Ожидаемый результат
Повторный ответ пропущен
Результат: 5

Это только демонстрация защиты жизненного цикла. Если задача имеет несколько ожиданий, одного isSuspended мало: нужен идентификатор этапа/запроса, иначе второй ответ может попасть в следующее ожидание.

Рядом: Fiber::resume, Fiber::throw, Fiber::getReturn. Карта состояний.

Fiber::isRunningУзнать, выполняется ли вызов входа в Fiber, который ещё не вернулся.

PHP 8.1+ · Core

Fiber::isRunning(): bool

Узнать, выполняется ли вызов входа в Fiber, который ещё не вернулся.

Параметр / результатСмысл
ПараметрыНет.
Результатtrue между входом start/resume/throw и возвратом этого вызова.
Вложенные FiberРодитель может оставаться running, пока дочерний Fiber находится на вершине. Для текущего контекста используйте getCurrent.
Не являетсяИзмерением загрузки CPU или доказательством параллельности.
Наблюдение изнутри и после паузы
  1. В callback getCurrent()->isRunning() → true
  2. suspend отдаёт управление
  3. Снаружи isRunning() → false
Исполнение внутри задачи и пауза снаружи различаютсяСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () {
    echo Fiber::getCurrent()->isRunning() ? "Сейчас готовим запрос доставки\n" : "Задача не выполняется\n";
    Fiber::suspend();
});
$task->start();
echo $task->isRunning() ? "Расчёт продолжает исполняться\n" : "Расчёт приостановлен, управление снаружи\n";
$task->resume();
Ожидаемый результат
Сейчас готовим запрос доставки
Расчёт приостановлен, управление снаружи

Внутри callback текущая задача running. После suspend выполнение вернулось вызывающему коду и задача suspended. Для вложенных Fiber флаг running родителя имеет отдельную тонкость: он не всегда означает, что именно родитель — текущий Fiber, это следующий опыт.

Различить активный родительский стек и текущую вложенную задачуСамостоятельный пример
<?php
$parent = new Fiber(function () {
    $parent = Fiber::getCurrent();
    $child = new Fiber(function () use ($parent) {
        echo $parent->isRunning() ? "Родительский стек ещё активен\n" : "Родитель не активен\n";
        echo Fiber::getCurrent() === $parent ? "Текущий — родитель\n" : "Текущий — дочерний Fiber\n";
    });
    $child->start();
});
$parent->start();
Ожидаемый результат
Родительский стек ещё активен
Текущий — дочерний Fiber

Родитель вызвал start дочернего Fiber, а не собственный suspend. Поэтому наблюдаемый running и идентичность getCurrent отвечают на разные вопросы. Не используйте один флаг как универсальную проверку контекста.

Рядом: Fiber::getCurrent, Fiber::isSuspended, Fiber::start. Карта состояний.

Fiber::isTerminatedУзнать, закончилось ли выполнение, успешно или с исключением.

PHP 8.1+ · Core

Fiber::isTerminated(): bool

Узнать, закончилось ли выполнение, успешно или с исключением.

Параметр / результатСмысл
ПараметрыНет.
Результатtrue после return, конца тела или неперехваченного исключения; иначе false.
Не означаетУспешное выполнение заказа/запроса. Ошибку нужно хранить и обрабатывать отдельно.
Два пути ведут в терминальное состояние
  1. Нормальный return → terminated true + доступен getReturn
  2. Непойманное исключение → terminated true + getReturn бросит FiberError
Прочитать смету только после завершения задачиСамостоятельный пример
<?php
declare(strict_types=1);

$task = new Fiber(function () { return 'Доставка: 300 руб.'; });
echo $task->isTerminated() ? "Смета готова\n" : "Смета ещё не рассчитана\n";
$task->start();
if ($task->isTerminated()) { echo $task->getReturn(), "\n"; }
Ожидаемый результат
Смета ещё не рассчитана
Доставка: 300 руб.

isTerminated проверяет окончание callback. В этом опыте окончание нормальное, поэтому getReturn доступен. При необработанном исключении Fiber также может быть terminated, но успешного результата не будет — это проверяется следующим примером.

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

Отдельный флаг failed хранит смысл завершения. isTerminated сообщает только состояние исполнения. Пользователю надо показать ошибку или результат согласно фактическому исходу, а не считать любой конец успехом.

Рядом: Fiber::getReturn, Fiber::throw, Fiber::isStarted. Карта состояний.

Fiber::getCurrentПолучить объект Fiber, в котором сейчас выполняется код, либо null снаружи.

PHP 8.1+ · Core

Fiber::getCurrent(): ?Fiber

Получить объект Fiber, в котором сейчас выполняется код, либо null снаружи.

Параметр / результатСмысл
ПараметрыНет.
РезультатТекущий Fiber; null в основном коде вне него. ?Fiber означает Fiber или null.
ПрименениеПроверить договор адаптера, привязать локальный контекст задачи по идентичности объекта.
ОсторожноПолучение объекта не даёт права возобновить его из него же: resume требует состояние suspended.
Один вызов в двух контекстах
  1. Основной код → null
  2. Внутри callback → объект текущей задачи
  3. После завершения → основной код снова null
Проверить контекст перед операцией, которая умеет ждатьСамостоятельный пример
<?php
echo Fiber::getCurrent() === null ? "Обычный внешний код\n" : "Есть текущая задача\n";
$task = new Fiber(function () { echo Fiber::getCurrent() instanceof Fiber ? "Внутри Fiber\n" : "Снаружи\n"; });
$task->start();
echo Fiber::getCurrent() === null ? "Обычный внешний код\n" : "Есть текущая задача\n";
Ожидаемый результат
Обычный внешний код
Внутри Fiber
Обычный внешний код

getCurrent возвращает выполняющийся Fiber либо null. До start и после возврата из него текущей задачи нет. Следующий awaitStock использует это, чтобы отказаться от suspend в неподходящем месте.

Понятная ошибка при неверном использовании адаптераСамостоятельный пример
<?php
function awaitStock(string $sku): int {
    if (Fiber::getCurrent() === null) { throw new LogicException('Нужен контекст задачи'); }
    $stock = Fiber::suspend($sku);
    if (!is_int($stock)) { throw new UnexpectedValueException('Нужен целый остаток'); }
    return $stock;
}
try { awaitStock('A-10'); }
catch (LogicException $error) { echo $error->getMessage(), "\n"; }
$task = new Fiber(fn() => awaitStock('A-10'));
$task->start();
$task->resume(5);
echo $task->getReturn(), "\n";
Ожидаемый результат
Нужен контекст задачи
5

Своя ошибка объясняет требование API до вызова suspend. Если нужен синхронный режим, его следует реализовать отдельным ясным путём, а не возвращать фиктивный остаток 0.

Рядом: Fiber::suspend, Fiber::isRunning, Fiber::__construct. Карта состояний.

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

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

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

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

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