Laravel · Php

PHP 8 Fibers — A Field Guide

PHP 8.1 Fibers explained: a low-level primitive for pausing and resuming execution with values in both directions, and why they're the building block async libraries use rather than something most app code touches directly.

John Kihiu12 min read

Fibers landed in PHP 8.1 with almost no fanfare, and that's appropriate — they're not a feature application code is meant to reach for directly. A Fiber is a primitive: a way to pause execution in the middle of a function call and resume it later, passing values in both directions each time. What makes that interesting isn't writing raw Fibers yourself, it's that they gave the userland async ecosystem — ReactPHP, Amphp, Revolt — a way to build coroutine-style APIs without hand-rolled generators or callback pyramids under the hood.

How a Fiber works

A Fiber wraps a callable. Calling $fiber->start() begins executing it on a separate call stack; inside that callable, Fiber::suspend($value) pauses execution and hands $value back out to whoever is holding the Fiber object. The caller resumes it later with $fiber->resume($value), and that value becomes the return value of the Fiber::suspend() call inside the fiber — execution picks up exactly where it left off, with full local variable state intact.

PHP · A BARE FIBER
$fiber = new Fiber(function (string $name): void {
    echo "starting for {$name}\n";
    $reply = Fiber::suspend("waiting on {$name}");
    echo "resumed with: {$reply}\n";
});

$startValue = $fiber->start('checkout');   // runs until suspend(), returns "waiting on checkout"
echo $startValue . "\n";

$fiber->resume('payment confirmed');        // resumes the fiber, prints "resumed with: payment confirmed"

That bidirectional exchange is the key difference from a Generator. A Generator's yield can receive a value via send(), technically, but Generators can only suspend from the top-level function that created them — they can't suspend from inside a function they call several layers deep. A Fiber can: any code running inside it, at any call depth, can call Fiber::suspend(), and the whole call stack pauses and resumes as a unit. That's what makes Fibers usable for wrapping blocking-looking code (a database query, an HTTP call) so it reads like synchronous code but actually yields control to an event loop underneath.

Why app code rarely touches Fibers directly

Writing raw Fiber code is bookkeeping-heavy — you need something driving the suspend/resume cycle, typically an event loop that knows when the thing a fiber suspended on (a socket becoming readable, a timer firing) is actually ready, and resumes the right fiber at the right time with the right value. That driver is what libraries like Revolt (the event loop underneath Amphp and, increasingly, ReactPHP) provide. In practice you write code against Amphp's or ReactPHP's async APIs, and Fibers are the mechanism making await-like syntax possible in plain PHP without a language-level keyword.

PHP · WHAT LIBRARIES BUILD ON TOP
// Illustrative Amphp-style usage — this is the layer most app code
// actually writes; the Fiber suspend/resume cycle happens inside
// the library's event loop, not in your call site.
use function Amp\async;
use function Amp\delay;

$futures = [
    async(fn() => file_get_contents('https://api.example.com/a')),
    async(fn() => file_get_contents('https://api.example.com/b')),
];

[$a, $b] = \Amp\Future\awaitAll($futures);
Fibers are concurrency, not parallelism

A Fiber doesn't run on another thread or process — it's still single-threaded, cooperative multitasking. Nothing runs "at the same time"; execution just switches between fibers whenever one voluntarily suspends. CPU-bound work still blocks everything else exactly as it would without Fibers at all.

Where this actually matters in practice

If you're running a standard PHP-FPM app — a request comes in, a process handles it, the process returns and is reused for the next request — Fibers change nothing about how you write your code. The place they matter is long-running PHP processes: a Swoole or Amphp-based server, a queue worker doing many concurrent outbound HTTP calls, a WebSocket server juggling thousands of open connections in one process. In those contexts, Fibers (via the libraries built on them) let you avoid spinning up a thread or process per connection, which is the traditional PHP answer to concurrency and does not scale past a few hundred concurrent connections without heavy resource cost.

Uncaught exceptions inside a Fiber don't disappear

An exception thrown inside a Fiber's callable propagates out through $fiber->resume() or $fiber->start() — whichever call was driving it at the time — not silently. If you're writing code that manages Fibers directly, wrap resume calls in try/catch, or a failure inside one fiber can crash the loop driving all of them.

Wrapping up

Fibers are infrastructure, not an API most PHP developers will call directly. Their value is that they gave PHP a real suspend-and-resume primitive at the language level, which meant async libraries no longer had to fake concurrency with generators and callback chains that made stack traces unreadable and error handling fragile. If you're building a typical request-response Laravel app on PHP-FPM, you can ignore Fibers entirely and nothing changes. If you're writing a long-running worker or evaluating Amphp, ReactPHP, or Swoole for a high-concurrency service, Fibers are the reason those tools got noticeably better ergonomics in the last few years.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.