revolt/event-loop
Revolt is a rock-solid event loop for concurrent PHP 8.1+ apps using fibers. It enables non-blocking I/O with synchronous code, serving as a minimal, shared scheduler base for libraries like Amp and ReactPHP.
Install the package with Composer:
composer require revolt/event-loop
Requires PHP 8.1+ (fibers are mandatory). Begin with the official docs at revolt.run—especially the Suspend & Resume guide, which explains how to block a fiber without blocking the entire process. Start with EventLoop::run() to bootstrap your event loop and use EventLoop::getSuspension() inside a fiber to suspend execution until resumed (e.g., after I/O completes).
First use case: run a simple non-blocking timer in a fiber:
EventLoop::run(function (): void {
EventLoop::defer(fn() => fwrite(STDOUT, "Hello after tick\n"));
$suspension = EventLoop::getSuspension();
EventLoop::delay(1000, fn() => $suspension->resume());
$suspension->suspend(); // Pauses *this fiber* only
});
Fiber-local context: Use FiberLocal to carry request-scoped data (e.g., request ID, user context) across suspensions without passing it explicitly:
$context = new FiberLocal();
$context->set('requestId', uniqid());
// Later, even across suspensions:
$id = $context->get('requestId');
Suspending & resuming manually: Encapsulate async operations (HTTP, DB, streams) by creating a suspension, initiating the operation, and resuming in a callback:
function waitForFile(string $path, int $timeoutMs = 1000): ?string {
$suspension = EventLoop::getSuspension();
$fd = fopen($path, 'r');
if ($fd === false) throw new RuntimeException("Failed to open $path");
$timer = EventLoop::delay($timeoutMs, fn() => $suspension->resume(null));
$watcher = EventLoop::onReadable($fd, fn() => [
$data = stream_get_contents($fd),
$suspension->resume($data),
EventLoop::cancel($timer),
EventLoop::cancel($watcher),
fclose($fd),
]);
return $suspension->suspend();
}
Cooperative concurrency: Run multiple I/O tasks concurrently without threads:
EventLoop::run(static function (): void {
$tasks = [];
foreach (['https://api-a.com', 'https://api-b.com'] as $url) {
$tasks[] = (function () use ($url): void {
// ... simulate async HTTP call with suspension ...
})();
}
// All tasks suspend and yield control, letting others proceed
EventLoop::run(static fn() => EventLoop::getSuspension()->suspend());
});
Integration with libraries: Libraries like Amp, ReactPHP, or custom HTTP clients can use revolt/event-loop as their underlying scheduler—no need to wrap promises or callbacks if targeting PHP 8.1+. Focus on Suspension and FiberLocal for low-level async primitives.
EventLoop::onSignal(SIGINT, fn() => ...) to log pending suspensions via debug helpers (e.g., EventLoop::getSuspension()->getFiber()->getTraceAsString()).EventLoop::run(): Running the loop recursively (e.g., calling EventLoop::run() inside EventLoop::run()) leads to subtle bugs. Use defer(), delay(), or onReadable() instead to chain logic.UnhandledThrowable—always guard resumption or use flags ($resumed = false; if (!$resumed) { $resumed = true; $suspension->resume(); }).EventLoop::setErrorHandler() to log errors during shutdown where error contexts might otherwise be lost.EventLoop::getType(), getIdentifiers(), isEnabled() to debug driver selection. Need higher performance with many file descriptors? Install ext-uv or ext-ev and explicitly enable them.unset() when done to release memory (especially in long-running processes); FiberLocal holds references until explicitly unset or fiber ends.onStop() or explicit shutdown hooks.How can I help you explore Laravel packages today?