memran / marwa-eventloop
PHP Async Event Loop with coroutines, promises, and async I/O
Requires
- php: >=8.2
Requires (Dev)
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
README
A PHP Fiber based async event loop library for PHP 8.2+. Build high-performance asynchronous PHP applications with coroutines, promises (Task), non-blocking I/O, timers, and signal handling.
Keywords: PHP async, PHP event loop, PHP Fiber, PHP coroutine, PHP promise, async PHP, non-blocking I/O, asynchronous PHP, PHP concurrency, PHP timers, PHP streams
Requirements
Requirements
- PHP 8.2+
Installation
composer require memran/marwa-eventloop
Usage
<?php require_once __DIR__ . '/vendor/autoload.php'; use Marwa\Eventloop\AsyncEventLoop; use Marwa\Eventloop\AsyncIO; $loop = new AsyncEventLoop(); // Run async coroutine $task = $loop->async(function () use ($loop) { echo "Task started\n"; // Sleep asynchronously yield AsyncIO::sleep(1, $loop); echo "Task completed\n"; return "Result"; }); // Handle result $task->then(function ($result) { echo "Success: $result\n"; })->catch(function ($e) { echo "Error: " . $e->getMessage() . "\n"; }); // Stop after 5 seconds $loop->addTimer(5, function () use ($loop) { $loop->stop(); }); $loop->run();
Features
Tasks (Promises)
$task = new Task(); $task->resolve($value); // or $task->reject(new \Exception('Error')); // Chain handlers $task->then(function ($value) { return $value * 2; })->catch(function ($e) { echo "Error: " . $e->getMessage() . "\n"; }); // Await result (inside coroutine) $result = $task->await();
Coroutines
$loop->async(function () use ($loop) { // Use yield to await Tasks $result = yield someAsyncOperation(); return $result; });
Timers
// One-time timer (3 seconds) $loop->addTimer(3.0, function () { echo "Timer fired\n"; }); // Periodic timer (every 1 second) $loop->addTimer(1.0, function () { echo "Every second\n"; }, true); // Timeout $loop->addTimeout(5.0, function () { echo "Operation timed out\n"; });
Streams
// Read stream $loop->addReadStream($stream, function ($stream) { $data = fread($stream, 1024); // process data }); // Write stream $loop->addWriteStream($stream, function ($stream) { fwrite($stream, "data"); }); // Remove stream $loop->removeStream($stream);
Signals
$loop->addSignal(SIGINT, function () use ($loop) { echo "Shutting down...\n"; $loop->stop(); });
Async Utilities
// Async sleep yield AsyncIO::sleep(1.5, $loop); // Async file read $content = yield AsyncIO::readFile('file.txt', $loop); // Timeout wrapper $task = AsyncIO::timeout($originalTask, 5.0, $loop);
Why This Library?
Why Is It Important?
PHP has traditionally been synchronous with blocking I/O. This PHP Fiber based event loop brings asynchronous, non-blocking I/O to PHP using PHP 8.2+ native Fibers - a coroutine implementation that enables writing concurrent code without callbacks.
Traditional PHP:
// Blocking - page loads slowly $data = fetchApiData(); processData($data);
With Marwa Event Loop:
// Non-blocking - handles multiple requests concurrently $loop->async(function() { $data = yield fetchApiData(); return processData($data); });
Use Cases
- High-performance web servers - Handle thousands of concurrent connections
- Real-time applications - WebSockets, chat systems, live updates
- Microservices - Multiple I/O operations without thread overhead
- API gateways - Aggregate data from multiple sources concurrently
- Background workers - Process tasks asynchronously without blocking
- File I/O operations - Non-blocking file reads/writes
- External API calls - Fetch multiple APIs simultaneously
What Can Be Done?
| Feature | Description |
|---|---|
| Concurrent Tasks | Run multiple async operations in parallel |
| Non-blocking I/O | Handle streams, sockets, files without blocking |
| Timers & Intervals | Schedule delayed and periodic tasks |
| Graceful Shutdown | Handle signals (SIGINT, SIGTERM) properly |
| Timeout Handling | Prevent operations from hanging indefinitely |
| Promise/Task Chain | Compose async operations like JavaScript promises |
| Coroutines | Write async code using synchronous-looking syntax |
Comparison
| Approach | Performance | Complexity | PHP Version |
|---|---|---|---|
| PHP Fiber Event Loop | High | Low | 8.2+ |
| ReactPHP | High | Medium | 7.1+ |
| Swoole | Very High | High | 7.4+ |
| Traditional (sync) | Low | Low | Any |
Real-World Example
// Fetch data from multiple APIs concurrently $loop = new AsyncEventLoop(); $apis = [ 'users' => 'https://api.example.com/users', 'posts' => 'https://api.example.com/posts', 'comments' => 'https://api.example.com/comments', ]; $tasks = []; foreach ($apis as $name => $url) { $tasks[$name] = $loop->async(function() use ($url) { return yield fetchAsync($url); // Non-blocking HTTP request }); } // All requests run concurrently $results = awaitAll($tasks); // Response time: ~100ms instead of ~300ms (sequential)
API
AsyncEventLoop
async(callable $coroutine): Task- Run a coroutinedefer(callable $callback)- Schedule callback for next tickaddTimer(float $interval, callable $callback, bool $periodic = false): int- Add timeraddTimeout(float $interval, callable $callback): int- Add timeoutcancelTimer(int $timerId): bool- Cancel a timeraddReadStream($stream, callable $callback)- Add read streamaddWriteStream($stream, callable $callback)- Add write streamremoveStream($stream)- Remove streamaddSignal(int $signal, callable $callback)- Add signal handlerrun()- Start the event loopstop()- Stop the event loopgetMetrics(): array- Get performance metrics
Task
resolve(mixed $value)- Resolve the taskreject(Throwable $exception)- Reject the taskthen(callable $onFulfilled): self- Add fulfillment handlercatch(callable $onRejected): self- Add rejection handlerawait(): mixed- Get the result (throws if rejected)isFulfilled(): bool- Check if resolvedisRejected(): bool- Check if rejectedisPending(): bool- Check if still pending
Testing
Run tests with PHPUnit:
composer test
Or run directly:
vendor/bin/phpunit
Static Analysis
Run PHPStan:
composer phpstan
Or run directly:
vendor/bin/phpstan analyse
License
MIT