PHP Fibers
What Is a Fiber? In simple terms: A fiber is a function that can pause itself in the middle of its work and be resumed later , exactly where it stopped, with all its variables intact. While it is paus
What Is a Fiber?
In simple terms: A fiber is a function that can pause itself in the middle of its work and be resumed later, exactly where it stopped, with all its variables intact. While it is paused, the rest of the program continues. This makes it possible to interleave several tasks — run a bit of task A, then a bit of task B, then continue A — within a single PHP process.
Technically: Fibers (PHP 8.1+) are interruptible functions with their own call stack. A Fiber object wraps a callable. start() begins executing it; inside, Fiber::suspend() pauses the fiber — even from deep inside nested function calls — and returns control to the code that started or resumed it. resume() continues the fiber. Values can be passed in both directions. Fibers provide cooperative concurrency: only one fiber runs at a time, and each one decides when to pause.
Fibers vs Generators
| Generator | Fiber | |
|---|---|---|
| Pausing | yield, only directly inside the generator function | Fiber::suspend(), from any depth of nested calls |
| Caller awareness | Callers must know they are dealing with a generator | Code inside the fiber looks like normal synchronous code |
| Main use | Lazy sequences of values | Building asynchronous frameworks and schedulers |
The Fiber Lifecycle
<?php
$fiber = new Fiber(function (string $greeting): string {
echo "Fiber started with: $greeting\n";
$received = Fiber::suspend('paused once');
echo "Fiber resumed with: $received\n";
return 'done';
});
$value = $fiber->start('hello');
echo "Main received: $value\n";
$fiber->resume('continue');
echo "Fiber returned: " . $fiber->getReturn() . "\n";
var_dump($fiber->isTerminated());
Output
Fiber started with: hello
Main received: paused once
Fiber resumed with: continue
Fiber returned: done
bool(true)
How It Works
Main code Fiber
───────── ─────
$fiber->start('hello') ───────► runs: "Fiber started with: hello"
◄─────── Fiber::suspend('paused once')
"Main received: paused once"
$fiber->resume('continue') ────► suspend() returns 'continue'
runs: "Fiber resumed with: continue"
◄─────── return 'done' (fiber terminated)
$fiber->getReturn() → 'done'
start('hello')runs the fiber's function with'hello'as its argument until the firstFiber::suspend().Fiber::suspend('paused once')pauses the fiber and hands'paused once'back as the return value ofstart().resume('continue')continues the fiber; inside,Fiber::suspend()now returns'continue'.- When the function returns, the fiber is terminated, and
getReturn()provides its return value.
The Fiber API
| Method | Purpose |
|---|---|
new Fiber(callable $callback) | Create a fiber (it does not run yet) |
$fiber->start(...$args) | Start it; returns the value passed to the first suspend() (or null if it finishes) |
Fiber::suspend($value) | Pause the current fiber, sending $value out; returns the value passed to resume() |
$fiber->resume($value) | Continue a suspended fiber |
$fiber->throw($exception) | Resume the fiber by throwing an exception inside it |
$fiber->getReturn() | The return value after termination |
isStarted(), isSuspended(), isRunning(), isTerminated() | Status checks |
Fiber::getCurrent() | The currently running fiber, or null |
Suspending From Deep Inside Nested Calls
The key ability of fibers: code several function calls deep can pause the whole fiber, without the intermediate functions knowing about it:
<?php
function fetchFromApi(string $resource): string
{
// Pretend to wait for a network response
$response = Fiber::suspend("waiting for $resource");
return $response;
}
function buildDashboard(): string
{
$orders = fetchFromApi('/orders');
$stock = fetchFromApi('/stock');
return "Dashboard: $orders, $stock";
}
$fiber = new Fiber(buildDashboard(...));
echo $fiber->start() . "\n";
echo $fiber->resume('12 orders') . "\n";
$fiber->resume('3 items low');
echo $fiber->getReturn() . "\n";
Output
waiting for /orders
waiting for /stock
Dashboard: 12 orders, 3 items low
buildDashboard() reads like ordinary sequential code. The pausing happens inside fetchFromApi(), and the main code (acting as a simple "event loop") decides when to deliver each response. This is how asynchronous libraries hide their complexity: the application code stays simple.
A Tiny Scheduler: Interleaving Tasks
<?php
function task(string $name, int $steps): Closure
{
return function () use ($name, $steps): void {
for ($step = 1; $step <= $steps; $step++) {
echo "$name: step $step\n";
Fiber::suspend(); // give other tasks a turn
}
};
}
$fibers = [
new Fiber(task('Import', 3)),
new Fiber(task('Email', 2)),
];
foreach ($fibers as $fiber) {
$fiber->start();
}
while ($fibers !== []) {
foreach ($fibers as $index => $fiber) {
if ($fiber->isTerminated()) {
unset($fibers[$index]);
continue;
}
$fiber->resume();
}
}
echo "All tasks finished\n";
Output
Import: step 1
Email: step 1
Import: step 2
Email: step 2
Import: step 3
All tasks finished
Step-by-Step Explanation
- Each task runs one step and then suspends, yielding control to the scheduler.
- The scheduler starts all fibers, then resumes each suspended fiber in turn (round-robin).
- When a fiber's function finishes, it becomes terminated and is removed from the list.
- The steps of both tasks are interleaved, although only one runs at any moment.
What Fibers Do Not Do
Fibers are not threads, and they do not make blocking code non-blocking:
- Only one fiber runs at a time; there is no parallel execution on multiple CPU cores.
- A blocking call (
sleep(),file_get_contents()on a slow URL, a PDO query) inside a fiber still blocks the entire process. - Real concurrency requires an event loop with non-blocking I/O, which pauses a fiber while it waits for a network response and resumes it when data arrives.
In practice, application developers rarely use fibers directly. Libraries build on them:
| Library | Purpose |
|---|---|
| Revolt | A shared event loop for PHP |
| AMPHP (v3+) | Asynchronous HTTP clients, servers, databases, and more, using fibers |
| ReactPHP | Event-driven, non-blocking I/O (with fiber-based await() helpers) |
With these, code such as $response = $client->request($request); looks synchronous but lets other requests proceed while waiting.
Common Mistakes
Resuming a Fiber That Is Not Suspended
Effect: FiberError: Cannot resume a fiber that is not suspended (for example, after it has terminated).
Fix: Check isSuspended() before resuming.
Calling Fiber::suspend() Outside a Fiber
Effect: FiberError: Cannot suspend outside of fiber.
Fix: Only suspend from code running inside a fiber.
Expecting Parallelism
Effect: CPU-heavy tasks run no faster, and blocking I/O still blocks.
Fix: Use an async library with non-blocking I/O, or separate processes/queues for parallel work.
Reading getReturn() Too Early
Effect: FiberError when the fiber has not terminated.
Fix: Check isTerminated() first.
Best Practices
- Understand fibers as the foundation of async libraries rather than as an everyday tool.
- Use established libraries (Revolt, AMPHP, ReactPHP) for concurrent I/O.
- Keep fiber-based code free of blocking calls.
- Check fiber state before resuming or reading return values.
- Use queues and worker processes for CPU-heavy or long-running background work.
Official reference: https\://www.php.net/manual/en/language.fibers.php