Coroutine vs generator vs fiber — what each term means precisely
Concept
Coroutine vs Generator vs Fiber — three related but distinct concepts for cooperative concurrency in PHP.
Generator (PHP 5.5+): A function that can yield values. It pauses at each yield and resumes when the caller calls next() or send(). The "coroutine" communication is one-way (generator produces, caller consumes) unless you use send(). Generators have a single call stack — they can only yield from the generator function itself, not from a function it calls.
Coroutine (computer science term): A function that can pause and resume at arbitrary points, with bidirectional communication. PHP generators are a LIMITED coroutine implementation. They ARE coroutines when you use send(), but the limitation is that yield must appear directly in the generator function (can't pause from a nested call).
Fiber (PHP 8.1+): A full coroutine. Has its OWN call stack (unlike generators). Can be suspended from anywhere in its call stack — from a function that the Fiber calls, not just directly in the Fiber function. Fiber::suspend($value) pauses, $fiber->resume($value) continues.
Key differences:
| Generator | Fiber | |
|---|---|---|
| Own call stack | No | Yes |
| Can suspend from nested call | No | Yes |
| Communication | yield / send() | suspend() / resume() |
| PHP version | 5.5+ | 8.1+ |
ReactPHP / Amp: Async PHP frameworks that use generators/fibers as coroutines to simulate non-blocking I/O in PHP's single-threaded model.
Laravel / Swoole: Octane + Swoole uses fibers for concurrent request handling.
Code Example
<?php
// GENERATOR as coroutine — bidirectional with send()
function accumulator(): \Generator
{
$total = 0;
while (true) {
$value = yield $total; // yield current total, receive next value via send()
if ($value === null) break;
$total += $value;
}
}
$acc = accumulator();
$acc->current(); // init: run to first yield, total = 0
$acc->send(10); // total = 10
$acc->send(20); // total = 30
echo $acc->current(); // 30
// Generator LIMITATION — can't yield from a nested call
function helper(): void
{
// yield 'from helper'; // FATAL ERROR — yield only works in generator functions
}
function generatorFn(): \Generator
{
yield 1;
helper(); // helper runs synchronously — can't suspend
yield 2;
}
// FIBER — full coroutine with its own call stack
function deepHelper(): void
{
echo "In deep helper\n";
\Fiber::suspend('from deep helper'); // can suspend from nested call!
echo "Deep helper resumed\n";
}
$fiber = new \Fiber(function(): void {
echo "Fiber started\n";
deepHelper(); // suspend happens HERE (inside a nested call)
echo "Fiber completed\n";
});
$value = $fiber->start(); // 'from deep helper' — fiber suspended from inside deepHelper!
echo "Main got: {$value}\n"; // 'from deep helper'
$fiber->resume(); // resumes deepHelper, then the fiber
// Output: Fiber started, In deep helper, Main got: from deep helper, Deep helper resumed, Fiber completed
// PRACTICAL: Fiber in async context (simplified)
class AsyncHttp
{
public static function get(string $url): string
{
// In a real async framework: initiate non-blocking request, then suspend
\Fiber::suspend(['type' => 'http_request', 'url' => $url]);
// When fiber is resumed, the HTTP response is the resume value
return \Fiber::getCurrent()->getReturn();
}
}
// Laravel/Swoole use fibers internally for concurrent request handling
// You don't typically create fibers manually in Laravel apps