Contents
- 1 Php fibers explained: practical asynchronous programming
- 2 What fibers really are (and what they are not)
- 3 A tiny mental model
- 4 First contact: a simple fiber example
- 5 Why fibers matter for async PHP
- 6 Fibers vs generators vs threads (quick sanity check)
- 7 Writing async-ish code with raw fibers
- 8 Where real-world PHP suddenly benefits
- 9 Fibers with an event loop: how it feels in practice
- 10 Practical gotchas (from someone who has tripped on them)
- 11 How to introduce fibers into an existing PHP codebase
- 12 Fibers and the human side of PHP work
- 13 Closing: the night, the logs, and the quiet upgrade
Php fibers explained: practical asynchronous programming
There’s a particular kind of night every PHP developer knows.
The office is half-dark, the cleaning staff have already passed through, and you’re still there, watching a terminal that’s happily printing log lines with the speed of a dying turtle. Somewhere in that log is the clue to why your API is slow under load, why your workers pile up, why “just adding another queue worker” didn’t really fix anything.
You run top. PHP is barely touching CPU. Everything is “waiting”.
Waiting on the database.
Waiting on HTTP calls.
Waiting on file I/O.
And you sit there thinking: Why am I wasting a whole process just… waiting?
That’s the moment where PHP Fibers start to make sense.
Not as a buzzword. Not as “PHP trying to be Node”. But as a very practical, very grounded way to do asynchronous programming in PHP without turning your code into callback soup.
Let’s talk about that. Slowly, honestly, and from the perspective of someone who’s been in that chair too many times.
What fibers really are (and what they are not)
First, the blunt truth:
- Fibers do not magically make PHP multi-threaded.
- Fibers do not automatically make your code asynchronous.
- Fibers do not remove the need to think.
What they do give you is a low-level control flow primitive: a way to pause a function and later resume it, with its whole call stack intact.
PHP’s manual calls them full‑stack, interruptible functions. You start a Fiber, run some code, then call Fiber::suspend() to pause it, and later you call resume() from the outside to continue where it left off.
Only one Fiber runs at a time. There is no hidden parallelism. That’s why we call it cooperative multitasking: a Fiber can yield control only when it explicitly decides to.
If that sounds almost boringly simple… good. Boring primitives are usually the ones you can build real systems on.
A tiny mental model
Imagine you have several workers sitting at desks:
- In classic PHP, you have one worker per request, and when they call the database, they just stare at the wall until it answers.
- With Fibers, you still have a single worker (one OS thread), but now they can say:
“I’ll wait on this DB query; while I’m waiting, I’ll work on something else.”
They put a bookmark in the current task (that’s your Fiber), switch desks (another Fiber), and later come back to the original one at the exact place they left.
This “bookmark” is Fiber’s paused stack.
So how does that become async PHP?
You combine three pieces:
- Fibers – pausable functions
- An event loop / scheduler – decides when to pause and resume Fibers
- Non‑blocking I/O – operations that don’t block the whole process while they wait
You can write all of that yourself. But unless your hobby is re‑implementing half of Node, you’ll probably lean on:
- Amp
- ReactPHP
- Revolt
- Swoole
Modern versions of these use Fibers under the hood to make asynchronous PHP look almost like synchronous code.
First contact: a simple fiber example
Let’s get something concrete on the page.
Here’s a minimal example just to get the feeling of starting, suspending, and resuming:
<?php
$fiber = new Fiber(function (): string {
echo "Fiber: start\n";
$value = Fiber::suspend("From fiber: first suspend");
echo "Fiber: resumed with '$value'\n";
return "Fiber done";
});
echo "Main: before start\n";
$result = $fiber->start();
echo "Main: got from suspend -> $result\n";
$return = $fiber->resume("Hello again");
echo "Main: fiber returned -> $return\n";
What happens:
- The Fiber runs until
Fiber::suspend(...). - Control jumps back to the caller (
start()), which returns the value passed tosuspend. - Later, we call
resume("Hello again"), and execution continues inside the Fiber.
No threads. No “real” concurrency. Just a new way to pause and continue work.
Not very async yet, but keep this mental picture. We’ll layer on top of it.
Why fibers matter for async PHP
Okay, let’s connect this to real life.
You probably write code like this every week:
$users = [];
foreach ($ids as $id) {
$users[] = $client->getUser($id); // HTTP or DB
}
If getUser() is synchronous, every call waits for the remote service, one after another. Ten calls, ten full waits. You’re using your process like a person who reads emails one by one, staring at the screen while each one slowly opens.
Now imagine each getUser() internally does:
- start an async HTTP request and
Fiber::suspend()until the response is ready.
With an event loop, while Fiber A is waiting for user #1, we can run Fiber B (user #2), C (user #3), etc., switching between them as I/O completes. One PHP process, many concurrent operations.
The key shift: most of your bugs are still debugged like synchronous code.
No callback nesting, no promises eating promises, no .then().then().then() chains. The code looks blocking, but the libraries secretly suspend and resume Fibers around the slow I/O.
Fibers vs generators vs threads (quick sanity check)
If you’ve used generators (yield) or just heard people arguing about them on Reddit, this question pops up fast.
The difference, in human terms:
-
Generators
- They are about producing sequences of values lazily.
- They don’t carry their own full call stack.
- They only “advance” when you iterate them.
-
Fibers
- They are about control flow.
- Each Fiber has its own stack: you can suspend deep inside nested calls.
- They start and run immediately when you call
start(), and only stop where they suspend or return.
Threads? Different universe:
- Threads let the OS interrupt you at almost any point.
- Multiple threads can touch the same data at the same time.
- You need locks, mutexes, and pain.
Fibers stay in PHP land. Only one is running at a time. You choose the suspension points. Much easier to reason about, especially in a language and community not built around multi-threaded shared-state programming.
Writing async-ish code with raw fibers
Let’s explore a tiny, illustrative pattern.
Imagine you want to simulate concurrent “tasks” without any external library, just to understand the rhythm.
<?php
function asyncTask(string $name, int $steps): Fiber
{
return new Fiber(function () use ($name, $steps) {
for ($i = 1; $i <= $steps; $i++) {
echo "[$name] step $i\n";
// yield control back to the scheduler
Fiber::suspend();
}
return "[$name] done";
});
}
$tasks = [
asyncTask('A', 3),
asyncTask('B', 2),
asyncTask('C', 4),
];
$running = [];
// start all fibers
foreach ($tasks as $fiber) {
$fiber->start();
$running[] = $fiber;
}
// round-robin scheduler
while ($running) {
foreach ($running as $i => $fiber) {
if ($fiber->isTerminated()) {
unset($running[$i]);
continue;
}
$fiber->resume();
}
}
No I/O, no network, just printing. But this feels like concurrency: tasks A, B, and C interleave their steps. One Fiber at a time, but switching often.
Replace those Fiber::suspend() calls with “wait for socket” or “wait for HTTP” from an event loop, and you’re suddenly in async frameworks territory.
This is exactly what libraries like Amp or ReactPHP do, but with a lot more robustness and battle scars.
Where real-world PHP suddenly benefits
If you live in standard request–response Laravel land, you might wonder:
“Do I really care about Fibers?”
It depends what you’re building.
Fibers (and the async frameworks that use them) shine when you have:
-
Many independent I/O-bound operations
- Fetch data from several APIs in parallel
- Query multiple services or databases simultaneously
- Talk to Redis, Kafka, and a microservice within one request
-
Long-lived workers
- WebSocket servers
- Chat servers
- Notification fan-out services
- Real-time dashboards
-
High-throughput queues and job runners
- Process many independent jobs in parallel inside a single process
- Coordinate backpressure and timeouts gracefully
This is exactly where concurrency and asynchronous programming in PHP become business features, not just technical toys.
And if you’re on a platform like Find PHP—trying to hire a PHP developer or present yourself as one—being able to talk concretely about Fibers, event loops, and async I/O is starting to matter. This is the territory of “senior backend engineer” interviews, production performance conversations, and “how do we make this scale without just adding more servers?” debates.
Fibers with an event loop: how it feels in practice
Let’s put this into a more realistic flow.
Say you’re writing a service that:
- takes a user ID,
- fetches profile data from an internal API,
- fetches billing data from another service,
- fetches recommendations from a third service,
…and you don’t want to do those three HTTP calls sequentially.
Using an async library that leverages Fibers (inspired by Amp-style code), you might write something like:
<?php
$profilePromise = async(function () use ($client, $userId) {
return $client->getProfile($userId);
});
$billingPromise = async(function () use ($client, $userId) {
return $client->getBilling($userId);
});
$recommendationsPromise = async(function () use ($client, $userId) {
return $client->getRecommendations($userId);
});
[$profile, $billing, $recommendations] = awaitAll([
$profilePromise,
$billingPromise,
$recommendationsPromise,
]);
return [
'profile' => $profile,
'billing' => $billing,
'recommendations' => $recommendations,
];
Under the hood:
async()wraps the function in a Fiber and schedules it.- When your HTTP client hits I/O (socket read/write), it calls
Fiber::suspend(). - The event loop watches multiple sockets and resumes whichever Fiber becomes ready.
awaitAll()runs the event loop until all promises resolve.
The code still reads top‑to‑bottom. But instead of sitting idle for each external call, you’re interleaving them.
Practical gotchas (from someone who has tripped on them)
As with any new power, Fibers add a few sharp edges. Some of them are technical, some are psychological.
Invisible concurrency
The code looks synchronous.
That’s a blessing for readability. It’s also how you suddenly end up with bugs like:
- “Why is this function yielding in the middle of my transaction?”
- “Why did that log message appear later than I expected?”
Any function that internally uses async I/O can suspend. That means execution might jump somewhere else between two lines that appear sequential.
You still avoid most traditional thread problems, but your mental model needs to accept that “line by line” may interleave with other Fibers at known suspension points.
Error handling across fibers
Exceptions thrown inside a Fiber don’t magically appear in the calling code unless you handle them properly.
If you build your own scheduler, you have to:
- catch exceptions inside each Fiber,
- record them,
- surface them back to the caller or log them.
When using battle-tested libraries, they usually wrap this well. But if you’re doing low-level Fiber work: treat exception paths as first-class citizens, not afterthoughts.
Blocking calls block everything
This is the classic trap:
“I use Fibers, but performance didn’t improve.”
If your I/O calls are still blocking (classic file_get_contents(), PDO without async, sleep()), Fibers can’t help much. If one Fiber calls sleep(10), the whole process sleeps. No other Fiber runs.
Fibers only give you concurrency when paired with non-blocking I/O (or at least partially non-blocking APIs like curl_multi_exec, stream_select, mysqli_poll, etc.).
So when you integrate Fibers into a real system, a lot of the work is choosing—or building—the right I/O layer.
Debugging and observability
One honest pain: debugging asynchronous flows can feel weird at first.
- Stack traces jump between Fibers.
- Logs interleave lines from different tasks.
Invest early in:
- clear logging (include Fiber/task IDs or correlation IDs),
- structured events (“task started”, “task completed”, “suspended for I/O”),
- timeouts and cancellation (async code without strict timeout handling is just a slow bug factory).
It’s tempting to ignore this until production. That temptation is how your future self ends up staring at logs at 1 AM, mumbling unrepeatable words at past you.
How to introduce fibers into an existing PHP codebase
You don’t rewrite everything. That’s how projects die.
You look for good edges—places where concurrency is natural and isolation is clear.
Some candidate zones:
-
Batch APIs
Endpoints that need to talk to many internal services before responding. -
Message consumers
Workers reading from queues (RabbitMQ, Kafka, SQS) that could process multiple messages concurrently in a single long-lived process. -
Integration services
Those annoying little services that glue together several external APIs.
A realistic migration strategy looks like this:
- Start with an isolated service or worker.
- Use a proven async library that already supports Fibers.
- Keep the boundaries synchronous: accept and return data via classic HTTP or queues.
- Write clear metrics: compare throughput and latency before and after.
A small win—say 3× throughput on a queue worker with the same hardware and more predictable tail latencies—can justify bringing this approach into other parts of your system.
And if you’re building a portfolio on Find PHP, real stories like “we used Fibers to turn a blocking queue worker into a concurrent worker with Amp, cutting job latency from 2 minutes to 20 seconds” are far more powerful than just “I know PHP 8.1 features”.
Fibers and the human side of PHP work
We don’t talk enough about the emotional side of this.
Asynchronous programming isn’t just a technical topic. It has a feeling.
- The feeling when you wait for a slow API and you know your whole system is bottlenecked behind it.
- The frustration when you add more and more workers, but your DB graphs look like a horror movie.
- The low, quiet anger when a single blocking call holds up a whole request chain in production.
Fibers, used well, give you something else:
- The quiet satisfaction when you see your event loop ticking, handling dozens of concurrent tasks with one process.
- The relief of not having to dive into threads and locks to get concurrency.
- The pleasure of writing async PHP that still reads top‑to‑bottom, like the language you grew up with, but smarter.
Tools like this shift how we think about PHP.
We stop treating it as just “that thing behind Apache and Nginx that renders pages” and start seeing it as a serious backend platform: concurrent, efficient, capable of handling streaming, websockets, chat, realtime dashboards, high‑throughput workers.
And for a platform that connects PHP developers and companies like Find PHP, this shift matters. We’re not just matching “PHP dev” to “PHP job”. We’re matching people who know how to bend Fibers, event loops, and async tools into something reliable under load, to teams that desperately need that experience.
Closing: the night, the logs, and the quiet upgrade
I keep coming back to that late-night scene.
The screens, the coffee mug with the cooling ring, the log tailing at the bottom of the monitor. Another timeout. Another job stuck. Another “we’ll restart the workers and hope it holds until morning.”
Learning Fibers is not glamorous. It’s not some shiny framework trend that will vanish in a year. It’s more like learning how to use a new tool in a workshop you already know by heart.
You still write PHP.
You still debug with var_dump() sometimes.
You still squint at stack traces.
But now, when your code is waiting, it doesn’t have to be idle.
It can be working on something else.
That small shift—from waiting to working, from blocking to interleaving—can be the difference between a system that barely holds and a system that breathes.
And if, on some quiet Friday night, you find yourself looking at your logs and realizing that the bottleneck is no longer your code just sitting there waiting, but the limits of the services you depend on—that’s a good kind of problem.
It means you’ve given your PHP the ability to use its time well.