Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

PHP Fibers for Concurrent Code

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.

PHP Fiber Concurrent Execution ModelSingle PHP Process — One OS ThreadMain Scriptstarts FibersFiber AAPI call waitFiber BDB query waitScheduler / Event Loopresume when I/O completesCooperative — each Fiber must call Fiber::suspend()
PHP Fibers for concurrent code run cooperatively inside one OS thread, switching at explicit suspend points during I/O waits.

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 for resume().
  • 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.

Fiber Suspend / Resume SequenceCaller (Main)Fiber Callablestart()suspend('token-A') returns to callerresume('response-data')return final resultValues cross at every boundarysuspend arg → caller; resume arg → fiber
Each PHP Fiber suspend and resume exchanges data between the caller scheduler and the fiber callable body.

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.

  1. Wrap each async task in a Fiber callable.
  2. Enqueue all Fibers before calling run().
  3. Suspend inside the task whenever you wait on external I/O.
  4. Resume Fibers from the scheduler when the wait completes.
  5. 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.

ApproachBest forParallelismComplexity
PHP FibersMultiple I/O waits in one requestCooperative, single threadMedium — needs scheduler
Laravel Queues / RedisBackground jobs after HTTP responseMulti-process workersLow — framework built-in
RoadRunner / FrankenPHPLong-lived workers, gRPC, websocketsMulti-request per processHigh — infra change
pcntl_fork()CLI scripts on LinuxTrue process forkHigh — 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.

Choosing a Concurrency StrategyNeed concurrency?After HTTP response?Inside one request?Use QueueI/O-bound waits?Use FibersCPU work→ QueueYesYes
Decision flow for PHP Fibers for concurrent code versus Laravel queues and long-lived async runtimes.

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.

Parallel API Aggregation with FibersLaravel ControllerFiber 1Payment APIFiber 2SMS GatewayFiber 3FX Rates APISchedulerRound-robinMerge responses → JSON viewWall time ≈ slowest API, not sum of all threeGotcha: blocking cURL here kills all FibersUse non-blocking HTTP client only
Real-world PHP Fibers for concurrent code pattern: three payment and lookup APIs aggregated inside one Laravel request.

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

PHP Fibers are lightweight, stackful coroutines built into the runtime since PHP 8.1. You create one with new Fiber(callable), pause at I/O with Fiber::suspend(), and continue with resume(). They overlap slow waits inside one OS thread without spawning threads.

No. Fibers run cooperatively on a single thread and one CPU core. Four Fibers waiting on HTTP overlap wall-clock time; they do not execute CPU work in parallel across cores.

Use Fibers when several independent HTTP calls must complete before you render a page. Use Laravel queues or Redis workers for post-response jobs like email digests, payment reconciliation, and report exports.

Instantiate new Fiber with a callable, call start() to run until the first suspend(), then resume() with a payload. Values passed to suspend() return to the caller; values passed to resume() appear inside the Fiber at the suspend point. PHP 8.5 ships the same Fiber API as 8.1. Match your runtime to framework requirements: Laravel 13 needs PHP 8.3 minimum, Symfony 8.1 needs PHP 8.4.1, and Composer 2.10 is the current toolchain baseline on active projects.

A Fiber reports status through getStatus() and helper methods like isSuspended() and isTerminated(). States include Created before start(), Running during execution, Suspended after Fiber::suspend(), Terminated when the callable finishes, and BOMBED after an uncaught exception. Check status before resume() because a terminated or bombed Fiber cannot be resumed. Production schedulers skip or handle dead Fibers so one failure does not stall the entire queue waiting on external APIs.

They address 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 like Amp or ReactPHP. There is no native await keyword in PHP as of 8.5. You still need non-blocking I/O wrappers; otherwise your Fiber code reads synchronously but behaves like blocking PHP underneath, which defeats the concurrency benefit entirely.

Laravel does not ship a Fiber scheduler in core. You can wrap Fiber logic inside a service class and invoke it from a controller, but most production Laravel applications still rely on queues, Redis-backed jobs, and standard HTTP clients for background work. Fibers fit narrow I/O aggregation inside one request, such as querying Khalti, eSewa, and Stripe status before rendering a checkout summary. Validate merged results before passing them to Blade, and keep idempotent retry logic outside the Fiber layer because network failures still occur.

Generators are stackless and yield values one way to a caller; you cannot inject values back mid-execution the way resume() feeds suspend(). Generators excel at lazy iteration over large datasets. Fibers excel at structured concurrency with bidirectional communication during I/O waits. ReactPHP predates Fibers and uses an event loop with promise chains. Fibers let ReactPHP and Amp write async code that reads like straight-line PHP, but you must pair them with non-blocking stream wrappers instead of blocking file_get_contents() or standard cURL calls.

Raw Fibers require something to rotate through suspended tasks. Without a scheduler you manually start and resume each Fiber. A minimal queue loops while pending Fibers exist: start new ones, resume suspended ones, and re-enqueue anything still waiting. Production schedulers integrate timers, I/O readiness, and priority. Libraries like Revolt and Amp v3 ship tested schedulers. Hand-rolling a round-robin loop suits learning or very small integrations, but reinventing scheduler logic in a Laravel controller rarely pays off when Amp already handles edge cases you will hit under load.

Calling blocking I/O inside a Fiber. Standard cURL and file_get_contents() block the thread, so every other Fiber in the same request waits frozen. Use Amp HTTP client, ReactPHP sockets, or curl_multi wrapped at explicit suspend points. Another frequent error is expecting true parallelism on shared hosting: Fibers interleave waits on one worker, they do not replace PHP-FPM pool tuning. Test with k6 load testing to confirm latency actually drops, and run PHPStan level 9 because generic Fiber return types confuse static analysers without careful annotations.

Technically yes, but it defeats the purpose. Blocking calls freeze the entire thread until the remote server responds, so no other Fiber in that request can progress during the wait. The article treats this as the most common production mistake encountered on real client projects. Replace blocking clients with Amp HTTP client, ReactPHP sockets, or curl_multi integrated at suspend points. On a legal-tech portal aggregating three external lookups before a booking summary, non-blocking wrappers are what turn sequential 200 to 800 millisecond calls into overlapping waits instead of additive delay.

Yes, within the single-request model each HTTP request still gets one PHP-FPM worker. Fibers help structure concurrent I/O waits inside that worker; they do not turn one worker into multiple parallel processes. On shared Apache plus PHP-FPM hosts common in Nepal, pool size controls concurrency across requests while Fibers control concurrency inside one request. They will not replace queue workers for heavy background jobs like PDF generation or image processing. CPU-bound tasks still need separate processes through Laravel queues, Symfony Messenger, or dedicated worker pools sized on Ubuntu 24 with PHP-FPM 8.4.

Wrap Fiber callables in try/catch blocks. Uncaught exceptions mark the Fiber as BOMBED via getStatus(), and a scheduler that ignores that state can stall the whole queue. Only one Fiber runs at a time per thread; calling Fiber::suspend() from plain code outside an active Fiber context throws FiberError. Nested suspend calls from callbacks work when you remain inside an active Fiber. Log scheduler metrics during development and inspect aggregated API payloads with a JSON formatter before they reach your view, because misformatted merge logic fails silently when one Fiber returns null.

No. Objects holding active Fiber instances cannot serialize safely, and persisting live Fibers into session or cache storage creates the same class of risk as careless PHP serialization elsewhere in the application. Store job payloads and cached aggregation results as plain arrays instead. On Laravel projects, pair Fiber-based cold-path aggregation with Redis caching through Laravel cache so repeated requests skip external calls entirely. Fibers reduce wait time on uncached paths; Redis eliminates repeated upstream hits. Use both layers where each earns its place rather than treating Fibers as a substitute for proper cache design.

Fibers arrived in PHP 8.1 and remain a first-class primitive in PHP 8.5. Laravel 13 requires PHP 8.3 minimum, so any supported Laravel 13 runtime already includes Fibers. Current projects should run PHP 8.3 or PHP 8.5 with Composer 2.10. Symfony 8.1 on the same server needs PHP 8.4.1, so mixed-framework infrastructure must match the highest requirement before copying Fiber snippets from tutorials. PHP 8.4 property hooks and other language additions solve different problems; they do not replace Fibers for overlapping I/O waits inside a single HTTP lifecycle on production applications.

Share this article

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.

Quick Contact Options
Choose how you want to connect me: