nunomaduro/pokio
Pokio is a minimal async API for PHP. Run closures concurrently using PCNTL forking and shared-memory IPC via FFI, then await results like promises. Built for internal tooling/perf work (e.g., Pest). Not production-safe; use at your own risk.
Installation:
composer require nunomaduro/pokio
Requires PHP 8.3+ and automatically falls back to sequential execution if PCNTL or FFI are unavailable.
First Use Case:
Replace a blocking sleep() or sequential I/O operation with parallel execution:
$promiseA = async(function () {
sleep(2);
return 'Task A';
});
$promiseB = async(function () {
sleep(2);
return 'Task B';
});
[$resultA, $resultB] = await([$promiseA, $promiseB]);
// Both tasks complete in ~2 seconds (not 4)
Where to Look First:
async(): Wrap any function or closure to run asynchronously.await(): Block until a promise (or array of promises) resolves.then(), catch(), and finally() for async workflows.Use Case: Speed up CLI scripts with API calls, DB queries, or file operations.
$apiCalls = [
async(fn() => Http::get('api/users')),
async(fn() => Http::get('api/posts')),
];
[$users, $posts] = await($apiCalls);
Laravel Integration:
$promise = async(function () {
return DB::table('users')->where('active', true)->get();
});
$activeUsers = await($promise);
Pattern: Use catch() or try/catch for graceful failures.
$promise = async(fn() => Http::get('api/unreachable'))
->catch(fn(Throwable $e) => ['error' => $e->getMessage()]);
$result = await($promise);
Global Fallback:
try {
await($promise);
} catch (Throwable $e) {
Log::error('Async task failed:', ['error' => $e]);
return fallbackData();
}
Use Case: Transform or depend on async results sequentially.
$promise = async(fn() => fetchData())
->then(fn($data) => processData($data))
->then(fn($processed) => saveToDB($processed));
await($promise);
Pattern: Use finally() for cleanup (e.g., DB transactions, file locks).
$promise = async(function () {
DB::beginTransaction();
// ... operations ...
})->finally(function () {
DB::commit(); // or rollBack()
});
await($promise);
Shortcut: Call a promise directly to get its resolved value.
$promise = async(fn() => 1 + 1);
$result = $promise(); // int(2)
Use Case: Return promises from async closures to chain async operations.
$promise = async(function () {
return async(fn() => fetchUser(1));
});
$user = await($promise); // awaits the nested promise
Advanced: Run multiple Artisan commands in parallel (e.g., migrations + reports).
$promises = [
async(fn() => Artisan::call('migrate')),
async(fn() => Artisan::call('db:seed')),
];
await($promises);
Use Case: Speed up test suites with parallel test execution.
async(fn() => test('User creation', fn() => /* ... */));
async(fn() => test('User deletion', fn() => /* ... */));
await([$test1, $test2]);
Artisan::command() methods for parallelized CLI logic.$promise = async(fn() => debugData())
->then(fn($data) => debug($data));
await($promise);
PCNTL/FFI are unavailable.if (!extension_loaded('pcntl')) {
$this->markTestSkipped('PCNTL not available');
}
State Isolation:
Xdebug Conflicts:
xdebug.mode=coverage), forking works as expected.Long-Running Tasks:
ulimit).Error Bubbling:
catch() or await blocks.await($promise->catch(fn(Throwable $e) => logError($e)));
FFI/PCNTL Dependencies:
if (!Pokio::isSupported()) {
throw new RuntimeException('Pokio not supported in this environment');
}
Shared Resources:
finally() to close connections:
$promise = async(function () {
$conn = DB::connection()->getPdo();
// ... use $conn ...
})->finally(fn() => DB::disconnect());
File Handles:
Serialization:
__serialize().Windows Support:
PCNTL/FFI, which are Unix-only).popen) on Windows.Memory Usage:
Check Support:
if (!Pokio::isSupported()) {
echo "Pokio not available (falling back to sequential).";
}
Log Promises:
$promise = async(fn() => fetchData())
->then(fn($data) => Log::debug('Resolved:', ['data' => $data]));
await($promise);
Inspect Child Processes:
ps aux | grep php to monitor child processes during execution.Timeouts:
$promise = async(fn() => sleep(10))
->then(fn() => 'Done', 5000); // Reject if >5s
await($promise);
Xdebug Debugging:
Pokio\ForkManager to implement custom process handling (e.g., custom IPC).2
How can I help you explore Laravel packages today?