PHP Fibers Explained: Async Programming in PHP Made Simple

PHP Fibers Explained: Async Programming in PHP Made Simple

📅 Monday, July 13, 2026 🕒 18 min

Learn how PHP Fibers work, why they were introduced in PHP 8.1, and how they power modern asynchronous frameworks like Amp and Revolt with practical examples.

Table of Contents

PHP has traditionally followed a simple execution model: start a request, execute code from top to bottom, send a response, and terminate. This approach made the language easy to learn and highly reliable for web applications, but it also meant that long-running operations—such as database queries, HTTP requests, or file I/O—would block the entire execution until they completed.

Over the years, the PHP ecosystem introduced several approaches to asynchronous programming. Event loops, callbacks, promises, and even generators acting as coroutines made non-blocking applications possible, but often at the cost of increased complexity and less readable code. These solutions worked, yet they required frameworks to implement workarounds for limitations in the language itself.

PHP 8.1 introduced Fibers, a low-level language feature designed to solve this problem elegantly. Fibers don't make PHP asynchronous by themselves, nor do they magically execute code in parallel. Instead, they provide the missing building block that allows asynchronous frameworks to suspend and resume execution naturally while preserving the entire call stack.

Today, modern libraries and runtimes such as Amp, Revolt, and even projects like FrankenPHP rely on Fibers to deliver asynchronous programming with code that still looks and feels synchronous. Even if you never instantiate a Fiber directly, understanding how they work will help you better understand the future of PHP development.


Why PHP Needed Fibers

Before Fibers were introduced, PHP applications followed a strictly blocking execution model. Whenever your application performed an operation that required waiting—such as reading a file, querying a database, or calling an external API—the entire process stopped until the operation finished.

Consider the following example:

$user = $database->findUser($id);
$posts = $api->getPosts($user->id);
$image = file_get_contents($avatarUrl);

echo "Done!";

Although the code looks simple, each operation blocks the next one. The API request cannot start until the database query finishes, and the file cannot be downloaded until the API responds.

As developers started building long-running applications, WebSocket servers, and high-performance APIs, this execution model became a significant limitation.

The first solutions relied heavily on callbacks:

fetchUser(function ($user) {
    fetchPosts($user, function ($posts) {
        downloadAvatar($posts, function ($avatar) {
            // ...
        });
    });
});

This style quickly became difficult to read, maintain, and debug—a problem commonly known as Callback Hell.

Promises improved the situation by making asynchronous code more composable:

fetchUser()
    ->then(fn ($user) => fetchPosts($user))
    ->then(fn ($posts) => downloadAvatar($posts));

While this was certainly better, it still forced developers to write code in a different style than traditional synchronous PHP.

Some frameworks even used Generators as coroutines, yielding execution back to an event loop. Although clever, generators were originally designed for iteration, not cooperative multitasking, making the implementation complex and sometimes unintuitive.

Fibers solved this problem at the language level. Instead of forcing frameworks to simulate asynchronous behavior, PHP now provides a native mechanism to pause execution and continue later exactly where it stopped, without losing the current execution context.

This allows asynchronous code to look almost identical to ordinary synchronous code, making modern PHP applications significantly easier to write and maintain.


What Is a Fiber?

A Fiber is a lightweight execution context that can be started, suspended, and resumed manually. Unlike a traditional function call, a Fiber can pause its execution at any point and continue later exactly where it left off.

The most important characteristic of a Fiber is that it preserves the entire call stack while suspended. Local variables, nested function calls, object state, and the current execution position all remain intact until the Fiber is resumed.

Conceptually, a Fiber behaves like an independent flow of execution:

Main Program
     │
     ├─────────────► Fiber starts
     │                  │
     │                  ▼
     │            Execute code
     │                  │
     │                  ▼
     │          Fiber::suspend()
     │                  │
     ◄──────────────────┘
     │
     │  ...other work...
     │
     ├─────────────► Fiber resumes
     │                  │
     │                  ▼
     │          Continue exactly here
     │                  │
     │                  ▼
     │            Fiber finishes

This is fundamentally different from generators, which can only suspend at yield points within their own execution flow. A Fiber preserves the complete execution stack, making it suitable for implementing coroutines, event loops, and asynchronous runtimes.

It's also important to understand what Fibers are not:

  • They are not threads.
  • They do not execute code in parallel.
  • They do not automatically make blocking functions non-blocking.

Instead, Fibers implement cooperative multitasking. A Fiber only pauses when it explicitly chooses to suspend, allowing another Fiber or the main program to continue executing.

This simple capability became the foundation upon which modern asynchronous PHP frameworks are built.


How Fibers Work

A Fiber has a very simple lifecycle. It is created, started, optionally suspended one or more times, resumed whenever necessary, and eventually finishes execution.

The flow looks like this:

Create Fiber
      │
      ▼
Fiber::start()
      │
      ▼
Execute code
      │
      ▼
Fiber::suspend()
      │
      ▼
Return control to caller
      │
      ▼
Fiber::resume()
      │
      ▼
Continue exactly where execution stopped
      │
      ▼
Fiber terminates

The key idea is that calling Fiber::suspend() does not terminate the execution. Instead, it temporarily hands control back to whoever started the Fiber.

When resume() is called later, PHP restores the entire execution context and continues from the exact instruction following Fiber::suspend().

Unlike a normal function, which always runs until it returns or throws an exception, a Fiber can voluntarily yield control multiple times during its lifetime.

This mechanism is what allows asynchronous frameworks to execute thousands of independent tasks while keeping application code readable and sequential.


Creating Your First Fiber

Creating a Fiber is surprisingly straightforward. You instantiate a Fiber object, provide the code to execute, start it, and resume it whenever the execution is suspended.

Here's a minimal example:

$fiber = new Fiber(function (): void {
    echo "Fiber started\n";

    Fiber::suspend();

    echo "Fiber resumed\n";
});

$fiber->start();

echo "Back in the main program\n";

$fiber->resume();

The output is:

Fiber started
Back in the main program
Fiber resumed

Let's examine what happens step by step:

  1. The Fiber is created, but nothing executes yet.
  2. Calling start() begins executing the callback.
  3. The message "Fiber started" is printed.
  4. Fiber::suspend() pauses the Fiber and immediately returns control to the main program.
  5. The main program continues and prints "Back in the main program".
  6. Calling resume() restores the Fiber's execution context.
  7. Execution continues immediately after Fiber::suspend(), printing "Fiber resumed".

This simple example demonstrates the core concept behind Fibers: execution can be paused and later resumed without restarting the function or losing any state.

In real-world applications, frameworks use this mechanism to suspend execution while waiting for network responses, database operations, timers, or other I/O events. Once the operation completes, the Fiber is resumed automatically, allowing your code to continue as if nothing had interrupted it.


Passing Values Between Fibers

One of the most powerful features of Fibers is that they support bidirectional communication. A Fiber can return a value when it suspends, and the caller can send another value back when resuming it.

This makes Fibers much more flexible than a simple pause-and-resume mechanism, allowing them to exchange data with the code that controls them.

Here's a simple example:

$fiber = new Fiber(function (): void {
    $name = Fiber::suspend("What's your name?");

    echo "Hello, {$name}!\n";
});

$question = $fiber->start();

echo $question . PHP_EOL;

$fiber->resume("John");

The output is:

What's your name?
Hello, John!

Here's what happens:

  1. The Fiber starts executing.
  2. Fiber::suspend() returns the string "What's your name?" to the caller and pauses the Fiber.
  3. The main program receives the value and prints it.
  4. Calling resume("John") sends "John" back into the Fiber.
  5. The suspended Fiber::suspend() expression evaluates to "John", which is assigned to $name.
  6. The Fiber continues executing until it finishes.

This communication mechanism is heavily used by asynchronous runtimes. Instead of exchanging simple strings, frameworks typically suspend a Fiber while waiting for an I/O operation and resume it with the operation's result once it's available.

For example, an HTTP client may suspend the current Fiber while waiting for a remote server to respond. Once the response arrives, the event loop resumes the Fiber and provides the response object, making the code appear completely synchronous.


Fiber Lifecycle

A Fiber goes through a well-defined lifecycle from creation to termination. Understanding these states makes it easier to reason about how asynchronous frameworks schedule work.

The lifecycle can be visualized as follows:

Created
    │
    ▼
Started
    │
    ▼
Running
    │
    ▼
Suspended
    │
    ▼
Running
    │
    ▼
Terminated

PHP provides several methods to inspect the current state of a Fiber.

isStarted()

Returns true once the Fiber has been started.

$fiber = new Fiber(fn() => null);

var_dump($fiber->isStarted()); // false

$fiber->start();

var_dump($fiber->isStarted()); // true

isRunning()

Indicates whether the Fiber is currently executing.

This is rarely needed in application code but can be useful when implementing schedulers or debugging asynchronous runtimes.


isSuspended()

Returns true if the Fiber has paused its execution using Fiber::suspend().

$fiber = new Fiber(function (): void {
    Fiber::suspend();
});

$fiber->start();

var_dump($fiber->isSuspended()); // true

isTerminated()

Returns true once the Fiber has finished executing.

$fiber = new Fiber(fn() => print "Done");

$fiber->start();

var_dump($fiber->isTerminated()); // true

Once a Fiber is terminated, it cannot be resumed again. Attempting to do so will throw an exception.

In practice, most developers rarely inspect these states manually. Frameworks such as Amp and Revolt manage the entire Fiber lifecycle internally, exposing a much simpler asynchronous API to application code.


Fibers vs Generators

Because many asynchronous PHP libraries used generators before PHP 8.1, it's common to confuse the two features. Although they both allow execution to pause and continue later, they were designed for entirely different purposes.

Generators were introduced to simplify iteration over large datasets without loading everything into memory. Their primary goal is lazy evaluation, not concurrency.

Fibers, on the other hand, were specifically designed to implement cooperative multitasking.

The differences become clearer in the following comparison:

Feature Generator Fiber
Primary purpose Lazy iteration Cooperative multitasking
Preserves the full call stack ❌ No ✅ Yes
Can suspend arbitrary execution ❌ No ✅ Yes
Supports bidirectional communication Limited ✅ Yes
Suitable for async runtimes Limited ✅ Excellent
Introduced in PHP 5.5 PHP 8.1

Consider a generator:

function numbers(): Generator
{
    yield 1;
    yield 2;
    yield 3;
}

Execution pauses only at each yield, and only within the generator itself.

A Fiber is fundamentally different:

$fiber = new Fiber(function (): void {
    firstFunction();
    secondFunction();
    thirdFunction();

    Fiber::suspend();

    fourthFunction();
});

The Fiber suspends the entire execution context, regardless of how many nested function calls are currently active. When resumed, PHP restores the complete call stack exactly as it was.

This capability is what made modern asynchronous frameworks dramatically simpler after PHP 8.1. Before Fibers, libraries had to build complex abstractions on top of generators to simulate behavior that Fibers now provide natively.


Fibers vs Threads

Perhaps the biggest misconception about Fibers is that they are lightweight threads.

They are not.

Although both can improve responsiveness, they solve completely different problems.

Threads Fibers
Managed by the operating system Managed by PHP
Can execute in parallel Execute cooperatively
May run on multiple CPU cores Always run on the same thread
Require synchronization primitives No synchronization needed
Higher memory overhead Very lightweight

Threads rely on preemptive multitasking. The operating system decides when each thread should pause and another should run, which introduces challenges such as race conditions, deadlocks, and the need for mutexes and locks.

Fibers use cooperative multitasking instead.

A Fiber runs until it explicitly decides to suspend itself:

Fiber::suspend();

At that moment, another Fiber or the main program may continue executing.

Because only one Fiber is executing at a time, there is no simultaneous access to shared memory. This eliminates an entire class of concurrency bugs that are common in multithreaded applications.

It's important to understand, however, that Fibers do not provide parallel execution.

If your application needs to utilize multiple CPU cores for CPU-intensive work, technologies such as multiple PHP processes, process pools, or extensions like parallel are more appropriate.

Fibers excel at a different problem: efficiently handling many I/O-bound tasks—such as HTTP requests, database queries, WebSocket connections, and file operations—while keeping the code simple, readable, and easy to maintain.


How Async Frameworks Use Fibers

Although PHP exposes the Fiber class directly, most developers should never need to create Fibers manually. In practice, they are primarily a building block used by asynchronous frameworks and runtimes.

Libraries such as Amp and Revolt use Fibers to hide the complexity of asynchronous programming behind an API that feels completely synchronous.

Consider a typical application that performs several I/O operations:

  • Query a database
  • Call an external REST API
  • Read from Redis
  • Write to a log file

Traditionally, each operation would block the next one. An asynchronous runtime, however, can suspend the current Fiber whenever an operation would block, allowing other Fibers to continue executing while waiting for the result.

From the developer's perspective, the code remains clean and sequential:

$user = $database->find($id);

$posts = $api->getPosts($user->id);

$avatar = $storage->download($user->avatar);

return [
    'user' => $user,
    'posts' => $posts,
    'avatar' => $avatar,
];

There are no callbacks, promise chains, or explicit state machines. Behind the scenes, the runtime automatically suspends and resumes Fibers whenever an asynchronous operation is encountered.

This is one of the biggest advantages of Fibers: they make asynchronous programming feel like ordinary PHP.

It's also worth noting that Fibers are only one part of the solution. They work together with an event loop, which is responsible for monitoring sockets, timers, file descriptors, and other asynchronous events. While the event loop decides when a Fiber should continue, the Fiber preserves where execution should resume.

Together, these components enable modern PHP runtimes capable of handling thousands of concurrent connections with readable, maintainable code.


Real-World Example

Imagine you're building an API endpoint that returns a user's profile.

To generate the response, your application needs to:

  1. Load the user from the database.
  2. Fetch recent posts from another service.
  3. Retrieve the user's avatar from object storage.
  4. Read cached preferences from Redis.

A traditional PHP application performs these operations sequentially:

Database
    │
    ▼
External API
    │
    ▼
Object Storage
    │
    ▼
Redis
    │
    ▼
Response

Each step waits for the previous one to finish, increasing the total response time.

An asynchronous runtime takes a different approach. Whenever one operation needs to wait for I/O, the current Fiber is suspended so that other work can continue.

Conceptually, the execution looks like this:

Fiber A → Database (waiting...)
           │
           ▼
Fiber B → HTTP API
           │
           ▼
Fiber C → Redis
           │
           ▼
Fiber A resumes
           │
           ▼
Generate response

Even though multiple tasks make progress during the same request, your application code still looks like ordinary synchronous PHP:

$user = $database->find($id);

$posts = $api->recentPosts($user->id);

$preferences = $redis->get("preferences:{$user->id}");

return new UserProfile(
    $user,
    $posts,
    $preferences
);

There's no need to manually suspend or resume Fibers. The asynchronous runtime handles that automatically, allowing you to focus on business logic instead of execution flow.

This is precisely why Fibers became such an important addition to PHP: they enable high-concurrency applications without sacrificing code readability.


Common Mistakes

Even though Fibers are conceptually simple, several misconceptions appear frequently among developers who are learning asynchronous PHP.

Assuming Fibers are threads

This is by far the most common mistake.

Fibers do not execute simultaneously and do not run on multiple CPU cores. They provide cooperative multitasking, not parallel execution.


Expecting automatic performance improvements

Simply wrapping code inside a Fiber will not make it faster.

If the code performs blocking operations using traditional APIs, it will continue to block. Fibers only become useful when combined with asynchronous libraries capable of suspending execution instead of blocking.


Creating Fibers everywhere

Just because you can create Fibers manually doesn't mean you should.

Most applications should let frameworks manage Fiber creation, scheduling, and lifecycle automatically.


Forgetting to resume a suspended Fiber

Once a Fiber calls Fiber::suspend(), it remains paused indefinitely until someone calls resume().

A suspended Fiber that is never resumed will never finish executing.


Ignoring exceptions

Exceptions thrown inside a Fiber don't disappear.

If they aren't handled within the Fiber itself, they propagate back to the caller when the Fiber is resumed.

Always treat error handling exactly as you would in synchronous code.


Thinking Fibers replace asynchronous libraries

Fibers are a language feature—not an asynchronous framework.

They provide the execution model, but you still need an event loop and non-blocking libraries to build scalable asynchronous applications.


Best Practices

Although Fibers are a low-level feature, following a few guidelines will help you write cleaner and more maintainable asynchronous applications.

Let frameworks manage Fibers

In most projects, you should never instantiate Fiber directly.

Frameworks such as Amp and Revolt already provide higher-level abstractions that handle scheduling, cancellation, error propagation, and resource management.


Write code as if it were synchronous

One of the biggest benefits of Fibers is readability.

Instead of restructuring your code around callbacks or promise chains, keep your business logic simple and sequential whenever possible.


Use asynchronous libraries consistently

Mixing blocking and non-blocking APIs usually defeats the purpose of using Fibers.

If your application adopts an asynchronous runtime, prefer libraries specifically designed to work with it.


Keep Fibers focused

A Fiber should represent a single logical unit of work.

Avoid placing unrelated responsibilities inside the same execution context, as this makes debugging and maintenance more difficult.


Handle exceptions normally

Fibers preserve the call stack, so exceptions remain predictable.

Use try/catch where appropriate instead of relying on framework-specific error handling.


Understand that Fibers are infrastructure

Perhaps the most important best practice is recognizing the role Fibers play within the PHP ecosystem.

They are not intended to replace ordinary PHP programming. Instead, they provide the low-level execution primitive that enables modern asynchronous runtimes.

As a result, most PHP developers will benefit from understanding how Fibers work, while rarely needing to interact with the Fiber class directly. That balance of knowledge is often what distinguishes developers who simply use asynchronous frameworks from those who truly understand how they operate internally.


Frequently Asked Questions

Are Fibers available in every PHP version?

No. Fibers were introduced in PHP 8.1. If your application runs on PHP 8.0 or earlier, the Fiber class is not available.


Do Fibers make PHP asynchronous?

Not by themselves.

Fibers provide the ability to pause and resume execution, but they don't automatically make blocking operations asynchronous. They need to be combined with an event loop and non-blocking libraries to deliver true asynchronous behavior.


Are Fibers faster than normal PHP code?

Not necessarily.

Fibers are not a performance optimization for CPU-intensive tasks. Their primary benefit is improving concurrency, allowing applications to handle many I/O-bound operations more efficiently without blocking the entire process.

For ordinary scripts and traditional web requests, Fibers usually provide little or no performance advantage.


Do Fibers execute code in parallel?

No.

Only one Fiber executes at a time within a PHP process. Execution switches between Fibers only when one voluntarily suspends itself.

If you need true parallel execution across multiple CPU cores, you'll need multiple processes or technologies such as the parallel extension.


Should I create Fibers manually?

Usually not.

Unless you're developing an asynchronous framework, an event loop, or another low-level library, it's generally better to let frameworks such as Amp or Revolt manage Fibers for you.

Most developers will use Fibers indirectly without ever instantiating the Fiber class.


Can Fibers replace threads?

No.

Fibers and threads solve different problems.

Threads provide parallelism, allowing multiple tasks to run simultaneously on different CPU cores. Fibers provide cooperative concurrency, allowing tasks to pause and resume efficiently within a single thread.

Many high-performance applications combine both approaches when appropriate.


Do Laravel and Symfony use Fibers?

The frameworks themselves don't rely on Fibers for their traditional request lifecycle.

However, the PHP ecosystem increasingly uses Fibers in asynchronous components, servers, and runtimes that integrate with these frameworks. As asynchronous PHP continues to evolve, Fibers are becoming an essential part of the underlying infrastructure.


Are Fibers replacing Promises?

Not exactly.

Promises and Fibers solve different problems and often complement each other.

Promises represent the eventual result of an asynchronous operation, while Fibers provide the execution model that allows asynchronous code to be written in a natural, sequential style. Many modern frameworks combine both internally to deliver clean APIs.


Conclusion

For years, asynchronous programming in PHP required complex abstractions built on callbacks, promises, or even generators acting as coroutines. While these approaches worked, they often made code harder to read, maintain, and debug.

Fibers changed that.

Rather than introducing another asynchronous framework, PHP 8.1 added a fundamental language feature that allows execution to be suspended and resumed while preserving the entire call stack. This seemingly simple capability became the missing building block that modern asynchronous runtimes had been waiting for.

Today, libraries such as Amp and Revolt use Fibers to make asynchronous programming feel almost indistinguishable from traditional synchronous PHP. Developers can write clean, linear code while the runtime transparently handles scheduling, event loops, and non-blocking I/O behind the scenes.

Even if you never create a Fiber yourself, understanding how they work gives you a deeper understanding of modern PHP architecture. It helps explain how high-performance servers, asynchronous HTTP clients, WebSocket applications, and other concurrent systems are built using familiar PHP syntax.

As the language continues to evolve, Fibers are likely to remain one of the most important additions to PHP's execution model—not because they change how you write everyday code, but because they enable an entirely new generation of tools and frameworks to do so.

If you're aiming to become a stronger PHP developer, especially at a senior level, Fibers are well worth understanding. They're no longer just an advanced language feature—they're part of the foundation of modern PHP.