
September 07, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
PHP Fibers for concurrent code solve a narrow but real problem: your script must wait on slow I/O without freezing the entire process. Fibers arrived in PHP 8.1 and remain a first-class primitive in PHP 8.5. They are not threads. They do not spawn parallel CPU work. They let one request pause at a known point and resume later while other Fibers run. On a production Laravel application, that pattern matters when you aggregate several HTTP APIs, payment gateways, or document services inside one controller action.
What are PHP Fibers and how do they enable concurrent code?
A Fiber is a lightweight, stackful coroutine built into the PHP runtime. You create it with new Fiber(callable). The callable runs until it calls Fiber::suspend() or finishes. Control returns to the caller. The caller can later invoke $fiber->resume() to continue exactly where execution stopped.
This is cooperative concurrency. Nothing is preempted. Each Fiber must yield explicitly. That differs from OS threads, where the scheduler interrupts work. It also differs from pcntl_fork(), which clones entire processes and carries heavy overhead on shared hosting.
Fibers sit below most async libraries. ReactPHP, Amp, and FrankenPHP build event loops on top of non-blocking I/O. Fibers give those loops a readable control-flow model. Instead of chaining callbacks, you write straight-line code that suspends at wait points. I've used this pattern when a legal-tech portal needed three external lookups before rendering a booking summary.
The official PHP manual documents the Fiber class, its states, and error modes. Read it before wiring Fibers into production. The class is final. You extend behaviour through wrappers, not inheritance.
How Fiber states map to your code
A Fiber moves through distinct states: Fiber::class reports them via getStatus(). You will see RUNNING, SUSPENDED, TERMINATED, and BOMBED after an uncaught exception. Checking status before resume() prevents calling a dead Fiber.
- Created — instantiated but
start()not yet called. - Running — executing inside the Fiber callable.
- Suspended — paused at
Fiber::suspend(), waiting forresume(). - Terminated — callable returned or threw; no further resumes allowed.
On shared Apache + PHP-FPM hosts common in Nepal, Fibers still obey the single-request model. Each HTTP request gets one worker. Fibers help structure waits inside that worker. They do not replace PHP-FPM pool tuning or queue workers for heavy background jobs.
How do you create, suspend, and resume a Fiber in PHP 8.5?
Start with the smallest working example. PHP 8.5 ships Fibers unchanged from 8.1, but current projects should run PHP 8.3 or PHP 8.5 with Composer 2.10. Laravel 13 requires PHP 8.3 minimum. Symfony 8.1 needs PHP 8.4.1. Match your runtime before copying snippets.
Minimal suspend and resume cycle
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): string {
$received = Fiber::suspend('first yield');
return 'done with: ' . $received;
});
$value = $fiber->start();
echo $value . PHP_EOL;
$result = $fiber->resume('second payload');
echo $result . PHP_EOL;
Running this prints first yield, then done with: second payload. The argument to suspend() becomes the return value of start() or the previous resume(). The argument to resume() becomes the value returned by suspend() inside the Fiber. Data flows both directions.
Passing values through the suspend boundary
Think of suspend() as a two-way channel. On a client project aggregating Khalti, eSewa, and Stripe status checks, each gateway call can suspend with a request ID. The scheduler resumes with the HTTP response body. Your Fiber code reads like synchronous PHP even though execution interleaves.
Building a simple Fiber scheduler
Raw Fibers need a scheduler loop. Without one, you manually resume each Fiber. A minimal scheduler keeps a queue and rotates through suspended Fibers until all terminate.
<?php
declare(strict_types=1);
final class FiberQueue
{
/** @var Fiber[] */
private array $pending = [];
public function enqueue(Fiber $fiber): void
{
$this->pending[] = $fiber;
}
public function run(): void
{
while ($this->pending !== []) {
$fiber = array_shift($this->pending);
if ($fiber->isTerminated()) {
continue;
}
if ($fiber->isSuspended()) {
$fiber->resume();
} elseif (!$fiber->isStarted()) {
$fiber->start();
}
if ($fiber->isSuspended()) {
$this->pending[] = $fiber;
}
}
}
}
This round-robin loop is naive. Production schedulers integrate timers, I/O readiness, and priority. Libraries like Revolt and Amp v3 ship tested schedulers. Reinventing one makes sense only for learning or a very small integration surface.
- Wrap each async task in a Fiber callable.
- Enqueue all Fibers before calling
run(). - Suspend inside the task whenever you wait on external I/O.
- Resume Fibers from the scheduler when the wait completes.
- Collect return values from terminated Fibers for the response.
Validate Fiber code with PHPStan level 9. Generic Fiber return types confuse static analysers unless you annotate carefully.
When should you use PHP Fibers instead of queues or async runtimes?
Fibers fit I/O-bound concurrency inside a single request. They do not help CPU-bound work like image processing or PDF generation. For that, use Laravel queues, Symfony Messenger, or a dedicated worker process.
Choose Fibers when several independent HTTP calls must complete before you render a page. A booking portal might fetch availability, exchange rates, and SMS gateway balance in parallel. Each call takes 200–800 ms. Sequential execution wastes time. Fibers plus non-blocking sockets cut wall-clock latency without spawning threads.
Skip Fibers when the task can run asynchronously after the response. Payment reconciliation, email digests, and report exports belong in a queue. I've seen teams add Fiber schedulers to controllers that should have dispatched a job. That couples request latency to downstream API reliability.
| Approach | Best for | Parallelism | Complexity |
|---|---|---|---|
| PHP Fibers | Multiple I/O waits in one request | Cooperative, single thread | Medium — needs scheduler |
| Laravel Queues / Redis | Background jobs after HTTP response | Multi-process workers | Low — framework built-in |
| RoadRunner / FrankenPHP | Long-lived workers, gRPC, websockets | Multi-request per process | High — infra change |
pcntl_fork() | CLI scripts on Linux | True process fork | High — not on PHP-FPM web |
For persistent workers and gRPC services, read the guide on gRPC in PHP with RoadRunner. RoadRunner keeps PHP alive between requests. Fibers still help inside each request, but the deployment model differs entirely from standard PHP-FPM.
On eCommerce projects like Nepal Gift Card, checkout calls payment APIs sequentially by default. Fibers or a proper async HTTP client reduce perceived checkout time when three gateways must be queried. Always keep idempotent retry logic outside the Fiber layer. Network failures still happen.
How do PHP Fibers compare to generators and ReactPHP?
PHP generators, covered in depth in generators for streaming large data sets, yield values to a caller one at a time. They are stackless. You cannot inject a value back into a generator mid-execution the way resume() feeds suspend(). Generators excel at lazy iteration. Fibers excel at structured concurrency with bidirectional communication.
ReactPHP predates Fibers and uses an event loop with promise chains. Callback nesting was the old pain. Fibers let ReactPHP and Amp write async code that reads synchronously. You still need non-blocking stream wrappers. Blocking file_get_contents() inside a Fiber freezes everything. That is the most common production mistake I encounter.
Cache hot aggregation results with Redis in Laravel. Fibers reduce wait time on cold paths. Redis eliminates repeated external calls entirely. Use both layers where each earns its place.
What are common mistakes when writing PHP Fibers for concurrent code?
Blocking I/O inside a Fiber defeats the purpose. Standard cURL and file_get_contents() block the thread. Every other Fiber waits. Use Amp's HTTP client, ReactPHP sockets, or curl_multi wrapped in suspend points. Test with k6 load testing to confirm latency drops under realistic concurrency.
Nested Fiber calls and exception handling
Only one Fiber runs at a time per thread. Calling Fiber::suspend() from nested callbacks works if you are inside an active Fiber context. Calling it from plain code throws FiberError. Wrap Fiber bodies in try/catch. Uncaught exceptions mark the Fiber as BOMBED. The scheduler must handle that state or the whole queue stalls.
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): void {
try {
$data = Fiber::suspend('waiting');
if ($data === false) {
throw new RuntimeException('upstream failed');
}
} catch (Throwable $e) {
error_log($e->getMessage());
throw $e;
}
});
$fiber->start();
Mixing Fibers with global state
PHP superglobals, static properties, and Laravel facades are request-scoped under PHP-FPM. Fibers in the same request share that scope. Mutating a static counter inside one Fiber affects others in the same request. Prefer passing state through suspend/resume values or constructor injection. This mirrors lessons from strict typing with PHP enums: explicit data beats hidden mutation.
Expecting true parallelism on shared hosting
Fibers interleave work on one CPU core. Four Fibers waiting on HTTP do not use four cores. They overlap wait time. CPU-heavy tasks still need separate processes. On a typical Ubuntu 24 server running PHP-FPM 8.4, pool size controls concurrency across requests. Fibers control concurrency inside one request. See Ubuntu server setup for PHP apps for worker sizing guidance.
Payment integrations like the eSewa integration guide show sequential callback flows. Refactoring those callbacks into Fibers needs careful timeout and signature verification ordering. Never resume a Fiber with unvalidated gateway data.
Log Fiber scheduler metrics during development. Use a JSON formatter to inspect aggregated API payloads before they hit your Blade view. Misformatted merge logic fails silently when one Fiber returns null.
Testing Fiber-based code
PHPUnit runs each test in isolation. You can unit-test Fiber callables by starting and resuming them directly. Integration tests should assert wall-clock improvement with microtime(true) boundaries. Add coverage gates as described in code coverage gates in CI. Fiber branches are easy to miss when tests mock HTTP at the facade layer only.
For enterprise systems needing audited concurrency design, enterprise application development teams should document scheduler behaviour in runbooks. Future maintainers will not guess why suspend points exist.
PHP 8.4 property hooks and other language features from PHP 8.4 new features do not replace Fibers. They solve different problems. Fibers address wait overlap. Property hooks address accessor boilerplate.
Serialization deserves caution. Objects holding active Fiber instances cannot serialize safely. Persist job payloads as plain arrays. This overlaps with security lessons from PHP serialization vulnerabilities. Never serialize live Fibers into session or cache storage.
When building custom API development services, expose concurrency limits in your OpenAPI docs. Clients should know whether your endpoint aggregates upstream calls or returns cached snapshots. Transparency prevents timeout mismatches on their side.
Adventure booking platforms like Adventure Third Pole Trek pull supplier availability from multiple REST endpoints. Fibers or async HTTP cut dashboard load time when operators refresh during peak season. Pair with testing and optimization before peak traffic hits.
The PHP RFC for Fibers explains the original motivation: full-stack interruptible functions without rewriting the engine. The manual page at php.net Fiber class reference remains the authoritative API source. Amp's documentation at amphp.org shows production-grade Fiber schedulers built on Revolt event loops.
Key Takeaways
- PHP Fibers for concurrent code use cooperative suspend/resume in one OS thread — not parallel CPU execution.
- Always pair Fibers with non-blocking I/O; blocking cURL inside a Fiber stalls every other Fiber in the request.
- Use queues for post-response work; reserve Fibers for overlapping I/O waits inside a single HTTP lifecycle.
- Pass state through suspend/resume values instead of static properties shared across Fibers.
- Prefer Amp or ReactPHP schedulers over hand-rolled loops unless you have a very small, tested use case.
- Run PHP 8.3+ on Laravel 13 projects and validate Fiber edge cases with static analysis and load tests.
People Also Ask
Are PHP Fibers the same as JavaScript async/await?
They solve a similar readability problem but work differently. JavaScript async/await desugars to Promises with engine support. PHP Fibers are explicit coroutines you suspend and resume manually or through a scheduler library. There is no native await keyword in PHP as of 8.5.
Do PHP Fibers work with Laravel out of the box?
Laravel does not ship a Fiber scheduler in core. You can wrap Fiber logic inside a service class and call it from a controller. Most Laravel apps still use queues, Horizon, and synchronous HTTP clients. Amp integrates with Laravel via community packages for async HTTP where needed.
Can PHP Fibers replace multi-threading?
No. Fibers never run two CPU-bound tasks simultaneously on one core. For CPU parallelism, use multiple PHP-FPM workers, queue consumers, or an external service. Fibers overlap I/O wait time, which is often the bottleneck in API aggregation endpoints.
Which PHP version introduced Fibers?
Fibers landed in PHP 8.1. They are stable in PHP 8.3, 8.4, and 8.5. Laravel 13 requires PHP 8.3 minimum. Symfony 8.1 requires PHP 8.4.1. Check your production runtime before deploying Fiber-based code.
Ship concurrent PHP with the right tool for each layer
PHP Fibers for concurrent code give you a native primitive for structured I/O overlap inside one request. They are not a replacement for queues, Redis caching, or PHP-FPM scaling. Use them where independent network waits dominate response time. Pair them with non-blocking clients, explicit error handling, and load tests that prove latency gains.
If you are modernizing a Laravel or custom PHP platform and need help choosing between Fibers, queues, and long-lived workers, review the custom software development services or contact us for a practical architecture review. Start with one aggregation endpoint, measure wall-clock time before and after, then expand only where data supports it.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

