Backend development

PHP Fibers in 2026: Asynchronous Programming Without ReactPHP or Third-Party Frameworks

Ruslan Ismailov Published 12 min read
P

Introduction: The History of Asynchronous PHP

PHP was created as a synchronous language for handling HTTP requests: one request — one thread — one response. For a long time, asynchronous programming in PHP was exclusively associated with third-party solutions: ReactPHP, Amp, Swoole, or RoadRunner. These tools did their job well, but required learning foreign abstractions, non-trivial environment setup, and abandoning familiar synchronous libraries.

PHP 8.1 (November 2021) introduced Fibers — a cooperative multitasking primitive built directly into the language core. By 2024–2026, the ecosystem around Fibers had matured significantly: debugging tools improved, stable abstractions over the raw API appeared, and the Laravel framework began using Fibers in its internal mechanisms. In this article, we'll explore how Fibers work, learn to write concurrent code without third-party dependencies, and understand where this approach has its limits.

What Is a Fiber: The Execution Model

A Fiber is a lightweight mechanism for cooperative context switching within a single PHP process. Unlike system threads, Fibers do not execute in parallel: only one fiber is active at any given moment. Switching happens explicitly — via a call to Fiber::suspend().

Let's look at the key differences from related concepts:

  • Generators — also allow pausing function execution via yield, but cannot transfer control to arbitrary external code outside the generator. A Fiber can be suspended from anywhere in the call stack.
  • Coroutines — conceptually close to Fibers; in PHP, Fibers are essentially full symmetric coroutines with their own call stack.
  • Threads (pthreads/parallel) — true parallelism with separate memory stacks and locks. Fibers run in a single thread and do not require shared-memory synchronization.
  • async/await (as in JS/Dart) — Fibers are a low-level building block on top of which async/await syntactic sugar can be implemented.

The Fiber lifecycle goes through four states: createdrunningsuspendedterminated. A Fiber is started with start(), suspended from within via suspend(), resumed from outside via resume(), and terminates when the callback function finishes execution.

Fiber API: Full Breakdown with Examples

Basic Example

<?php

$fiber = new Fiber(function (): void {
    $value = Fiber::suspend('first suspension');
    echo "Resumed with: {$value}\n";

    Fiber::suspend('second suspension');
    echo "Fiber completed\n";
});

// Start the Fiber; execution runs until the first suspend()
$result1 = $fiber->start();
echo "Suspended with: {$result1}\n"; // "first suspension"

// Resume, passing a value into the Fiber
$result2 = $fiber->resume('hello');
echo "Suspended with: {$result2}\n"; // "second suspension"

// Finish
$fiber->resume();
echo "Is terminated: " . ($fiber->isTerminated() ? 'yes' : 'no') . "\n";

The method Fiber::suspend($value) is called from inside the fiber and passes a value outward (it becomes the return value of start() or resume()). The value passed into resume($value) from outside becomes the return value of Fiber::suspend() inside the fiber.

Getting a Result via getReturn()

<?php

$fiber = new Fiber(function (): int {
    Fiber::suspend();
    return 42;
});

$fiber->start();
$fiber->resume();

if ($fiber->isTerminated()) {
    echo $fiber->getReturn(); // 42
}

The getReturn() method throws a FiberError exception if the fiber has not yet terminated — this is important to keep in mind when building schedulers.

Practical Patterns

A Simple Task Scheduler Using Fibers

The central pattern when working with Fibers is the cooperative scheduler. It maintains a queue of fibers and resumes them in turn until all have finished.

<?php

class FiberScheduler
{
    /** @var Fiber[] */
    private array $queue = [];

    public function add(callable $callback): void
    {
        $this->queue[] = new Fiber($callback);
    }

    public function run(): void
    {
        // Start all fibers
        foreach ($this->queue as $fiber) {
            $fiber->start();
        }

        // Keep looping while there are suspended fibers
        while (true) {
            $active = array_filter(
                $this->queue,
                fn(Fiber $f) => $f->isSuspended()
            );

            if (empty($active)) {
                break;
            }

            foreach ($active as $fiber) {
                $fiber->resume();
            }
        }
    }
}

// Usage
$scheduler = new FiberScheduler();

$scheduler->add(function (): void {
    echo "Task 1: step A\n";
    Fiber::suspend();
    echo "Task 1: step B\n";
    Fiber::suspend();
    echo "Task 1: step C\n";
});

$scheduler->add(function (): void {
    echo "Task 2: step A\n";
    Fiber::suspend();
    echo "Task 2: step B\n";
});

$scheduler->run();
// Task 1: step A
// Task 2: step A
// Task 1: step B
// Task 2: step B
// Task 1: step C

Concurrent HTTP Requests with curl_multi and Fibers

The real benefit of Fibers becomes apparent when integrating with non-blocking I/O. Below is an example of concurrent HTTP requests using curl_multi without any third-party libraries:

<?php

function asyncGet(string $url): Fiber
{
    return new Fiber(function () use ($url): string {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
        ]);

        $mh = curl_multi_init();
        curl_multi_add_handle($mh, $ch);

        do {
            $status = curl_multi_exec($mh, $running);
            if ($running) {
                curl_multi_select($mh, 0.01); // non-blocking select
                Fiber::suspend(); // yield control to the scheduler
            }
        } while ($running && $status === CURLM_OK);

        $response = curl_multi_getcontent($ch);
        curl_multi_remove_handle($mh, $ch);
        curl_multi_close($mh);
        curl_close($ch);

        return $response;
    });
}

$urls = [
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/2',
    'https://httpbin.org/uuid',
];

$fibers = array_map('asyncGet', $urls);

// Start all fibers
foreach ($fibers as $fiber) {
    $fiber->start();
}

// Run the event loop
while (array_filter($fibers, fn($f) => !$f->isTerminated())) {
    foreach ($fibers as $fiber) {
        if ($fiber->isSuspended()) {
            $fiber->resume();
        }
    }
}

// Collect results
foreach ($fibers as $i => $fiber) {
    $body = $fiber->getReturn();
    echo "Response {$i}: " . substr($body, 0, 80) . "...\n";
}

All three requests execute concurrently within a single PHP process, using no extensions beyond the built-in curl.

Integrating Fibers with Laravel

Starting with Laravel 9 (released alongside PHP 8.1), the framework has been progressively integrating Fibers into its mechanisms. By 2026, the most significant integration points look like this:

  • Laravel Concurrency — the laravel/concurrency package, introduced in Laravel 11, provides a Concurrency::run() facade that uses Fibers under the hood to execute closures concurrently within a single worker.
  • Queue Workers — Horizon and the built-in queue worker optionally use Fibers to process multiple jobs without spawning additional processes.
  • HTTP ClientHttp::pool() in Laravel uses Guzzle promises, which can be transitioned to a cooperative model when Fibers are available.
<?php

use Illuminate\Support\Facades\Concurrency;

// Laravel 11+: concurrent execution of three tasks
[$users, $orders, $stats] = Concurrency::run([
    fn() => DB::table('users')->count(),
    fn() => DB::table('orders')->where('status', 'pending')->get(),
    fn() => Cache::remember('stats', 60, fn() => computeStats()),
]);

echo "Users: {$users}, Pending orders: " . $orders->count();

If you want to extend the behavior and write your own scheduler on top of Laravel, it's convenient to register it in the service container and inject it via the constructor. Fibers work seamlessly with Laravel's DI container since they do not require changes to the framework's global state.

Performance: Fibers vs Synchronous Code vs Swoole

It's important to understand clearly that Fibers do not speed up CPU-bound tasks. The gain is achieved exclusively on I/O-bound operations — network requests, database queries, file reads — by overlapping wait times.

Approximate results for 100 HTTP requests to an external API (latency ~200ms each):

  • Synchronous PHP — ~20 seconds (requests are sequential).
  • PHP Fibers + curl_multi — ~0.8–1.2 seconds (concurrent, single process).
  • Swoole Coroutines — ~0.5–0.7 seconds (native coroutines with a C-level event loop).
  • ReactPHP — ~0.6–0.9 seconds (async promises on top of libuv/event).

Fibers fall behind Swoole for workloads with thousands of simultaneous connections, because Swoole's event loop is written in C and optimized at the kernel level. However, for most business applications the difference is negligible, and the advantage of Fibers is zero dependencies and full compatibility with a standard PHP FPM environment.

Limitations and When Fibers Are Not the Right Choice

Fibers are a powerful tool, but not a silver bullet. Here are situations where a different approach is preferable:

  • CPU-intensive computations — encryption, image rendering, parsing large files. True parallelism is needed here: the parallel extension or offloading the task to a separate process or service.
  • Blocking extensions — if you use synchronous PDO, mysqli, or file_get_contents without stream wrappers, a Fiber will be blocked on I/O just like regular code. Fibers do not automatically make blocking code non-blocking.
  • Complex debugging — the call stack when working with Fibers can be non-linear, making error tracing in Xdebug more difficult. The situation has improved by 2026, but still requires attention.
  • Static analysis — PHPStan and Psalm support Fibers, but typing the signatures of suspend/resume remains non-obvious for junior developers.
  • Legacy libraries — if a third-party library uses register_shutdown_function or global error handlers, behavior inside a Fiber may be unexpected.

What's Next: The Future of Async PHP in 2026

PHP continues to evolve toward more ergonomic concurrent programming. Several relevant trends as of 2026:

  • PHP 8.4 and beyond — the RFC tracker includes discussions of native async/await syntax built on Fibers, an improved scheduler in the standard library, and a built-in event loop at the core level.
  • Amphp v3 — the Amp library has been completely rewritten on top of Fibers and provides a rich set of primitives (Channel, DeferredFuture, EventLoop) without requiring Swoole.
  • Laravel Reverb and WebSockets — Laravel's official WebSocket server, Reverb, uses Fibers under the hood, demonstrating that the ecosystem is ready for production use.
  • FrankenPHP — a modern PHP server built in Go with worker-mode support, where Fibers become a key tool for handling multiple requests within a single worker.
  • Standardization of primitives — the PHP community is increasingly discussing adding a basic scheduler and synchronization primitives (Mutex, Channel) to the SPL.

"Fibers are not a replacement for Swoole in high-load scenarios. They are a way to write clear, concurrent code in standard PHP that runs on any hosting environment without additional extensions." — the prevailing consensus in the PHP community as of 2025–2026.

Conclusion

PHP Fibers, introduced in version 8.1, have evolved by 2026 from an experimental feature into a mature tool suitable for production use. They enable cooperative multitasking without ReactPHP, Swoole, or other heavy dependencies — using only standard PHP and its built-in extensions.

Key takeaways from this article:

  1. Fibers are cooperative coroutines with their own call stack; they don't create parallelism, but eliminate idle time on I/O.
  2. The API is straightforward: new Fiber(callable), start(), Fiber::suspend(), resume(), getReturn().
  3. Combining Fibers with curl_multi delivers real performance gains of 10–20x on network-bound tasks compared to sequential code.
  4. Laravel actively integrates Fibers through the Concurrency facade and server-side components.
  5. Fibers are not suited for CPU-bound tasks and do not automatically make blocking I/O non-blocking.

If you're a PHP developer and haven't added Fibers to your toolkit yet — now is the perfect time to start. Standard PHP is capable of far more than most people assume.

Technologies

Tags

Ruslan Ismailov

Senior Web / Backend Developer. Senior web/backend developer with 9 years of experience. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservices, CI/CD. More about me →