Fiber Throws Where The Fiber Is

<?php

// Illustrates: $fiber->throw($exception) resumes the fiber and raises the
// exception exactly where Fiber::suspend() paused it, not in the caller's scope.

$fiber = new Fiber(function (): void {
    echo "fiber: starting
";

    try {
        $value = Fiber::suspend('paused, waiting for input');
        echo "fiber: resumed normally with '{$value}'
";
    } catch (RuntimeException $e) {
        // This ordinary try/catch, wrapped around suspend(), catches the
        // exception injected by Fiber::throw() -- no special API needed.
        echo "fiber: caught injected exception: '{$e->getMessage()}'
";
    }

    echo "fiber: finishing
";
});

echo "main: getCurrent() outside any fiber is: ";
var_dump(Fiber::getCurrent());

$suspendedValue = $fiber->start();
echo "main: fiber suspended with '{$suspendedValue}'
";

// This does NOT throw here, in main's scope. It resumes the fiber and the
// exception surfaces at the suspend() call above, inside the fiber's body.
$fiber->throw(new RuntimeException('something went wrong'));

echo "main: fiber finished? " . ($fiber->isTerminated() ? 'yes' : 'no') . "
";

// Starting an already-started (and now terminated) fiber is not a silent
// no-op: it throws a FiberError.
try {
    $fiber->start();
} catch (FiberError $e) {
    echo "main: caught FiberError: '{$e->getMessage()}'
";
}

Fiber is the generator’s sibling that nobody writes tips about, so here is one.

$fiber->throw($exception) does not throw in the caller’s own scope. It resumes the suspended fiber and raises the exception exactly at the Fiber::suspend() call that paused it, as if that call itself had thrown.

That means an ordinary try/catch written around the suspend() call inside the fiber’s body will catch it, with no special API needed on the fiber’s side.

Also note that Fiber::getCurrent() returns null outside of any fiber, and that starting an already-started fiber throws a FiberError, not a silent no-op.

See Also

PHP Features

Last updated: 14 July 2026