react/event-loop
ReactPHP’s core event loop for evented I/O. Provides a shared LoopInterface so async libraries interoperate on a single loop with one run() call. Includes timers, future ticks, stream and signal watchers, plus multiple backend implementations.
Install via Composer:
composer require react/event-loop
The Loop class is your entry point — it provides static methods to interact with the global event loop instance. Start with simple timer usage to understand control flow:
use React\EventLoop\Loop;
Loop::addTimer(1.0, function () {
echo "One second later\n";
});
// Script exits automatically after timers complete (v1.2+ autorun)
For more control, explicitly get and run the loop:
$loop = Loop::get();
$loop->addTimer(1.0, fn() => echo "Done\n");
$loop->run(); // Required if you disable autorun or need explicit lifecycle
Use Loop::addReadStream()/addWriteStream() to handle non-blocking I/O — e.g., accept connections on a socket or write partial HTTP responses.
DI Integration: Inject LoopInterface into services instead of using global Loop calls for testability and modularity.
class Worker {
public function __construct(private LoopInterface $loop) {}
public function start() {
$this->loop->addPeriodicTimer(2, fn() => $this->doWork());
}
}
$worker = new Worker(Loop::get());
Interoperability: Libraries using react/event-loop integrate seamlessly because they all target LoopInterface. Avoid creating multiple loop instances — use Loop::get() consistently.
Stream Handling: Build async servers/utilities with addReadStream() and addWriteStream(). Always removeReadStream()/removeWriteStream() after closing the stream to prevent leaks.
Signal Handling (Unix only): Use Loop::addSignal() with ext-pcntl for graceful shutdowns:
Loop::addSignal(SIGTERM, fn() => Loop::stop());
Hybrid Libraries: Write reusable async components (e.g., HTTP clients) that accept an optional LoopInterface parameter, defaulting to Loop::get() if none provided.
Autorun Caveats: Loop automatically runs at script end — but if you throw an exception before timers fire, use Loop::stop() in an exception handler to prevent unexpected execution.
Loop Selection: The default StreamSelectLoop works everywhere but scales poorly with thousands of streams. For high concurrency, install and prefer ext-uv (via composer require/react/uv) — Loop::get() auto-selects available backends (ExtUvLoop > ExtEvLoop > ExtEventLoop > StreamSelectLoop).
Timer Precision: On PHP < 7.3, timers use wall-clock time — system time adjustments affect them. Use ExtUvLoop or ExtEvLoop for monotonic timing if precision matters.
Stream Blocking: Always set streams to non-blocking mode: stream_set_blocking($stream, false) before adding them to addReadStream()/addWriteStream().
Memory Leaks: Failing to cancelTimer() or remove streams/timers can keep the loop alive indefinitely. Use Loop::addTimer(0, ...) in cleanup to schedule deferred teardown.
Windows Compatibility: addSignal() requires ext-pcntl, which isn’t available on Windows. Guard signal registration with function_exists('pcntl_signal').
Testing Tip: Mock LoopInterface and use $loop->futureTick() to force code execution before script exit in unit tests — or manually call $loop->stop() after assertions.
v3 Preview: The main branch is v3 (PSR-18-compatible design). For production today, prefer the 1.x branch — it’s stable and actively supported. Check the Changelog for migration notes when upgrading.
How can I help you explore Laravel packages today?