nunomaduro/pokio
Pokio is a simple async API for PHP 8.3+ using pcntl forks and FFI shared memory to run closures concurrently and await results. Falls back to sequential execution if extensions aren’t available. Experimental/unsafe; intended for internal use, not production.
Installation:
composer require nunomaduro/pokio
Requires PHP 8.3+ and optionally pcntl/ffi extensions for true parallelism (falls back to sequential execution if unavailable).
First Use Case: Run two blocking tasks concurrently (e.g., API calls or file operations) without waiting sequentially:
use function Pokio\async;
use function Pokio\await;
$promise1 = async(function () {
sleep(2); // Simulate I/O delay
return 'Task 1 result';
});
$promise2 = async(function () {
sleep(1); // Faster task
return 'Task 2 result';
});
[$result1, $result2] = await([$promise1, $promise2]);
// Outputs after ~2 seconds (not 3)
Where to Look First:
async() (creates promises) and await() (blocks until resolution).then(), catch(), finally() for chaining (similar to JavaScript Promises).Pokio\supportsConcurrency() to verify if pcntl/ffi are available.Parallelizing I/O-Bound Tasks:
$promises = collect(range(1, 5))->map(fn ($i) =>
async(fn () => fetchDataFromApi($i))
)->toArray();
$results = await($promises);
Chaining Promises (Like JavaScript):
$promise = async(fn () => fetchUser(1))
->then(fn ($user) => async(fn () => fetchPosts($user->id)))
->then(fn ($posts) => formatPosts($posts))
->catch(fn (Throwable $e) => logError($e));
Non-Blocking User Prompts:
$promise = async(fn () => prompt('Enter name:'));
$name = await($promise);
Artisan Command Integration:
use Pokio\{async, await};
protected function handle(): void
{
$tasks = collect($this->arguments['files'])->map(fn ($file) =>
async(fn () => processFile($file))
);
await($tasks);
$this->info('All files processed concurrently!');
}
Batch Processing:
Use async + await to parallelize file operations, database queries, or external API calls in Laravel migrations or console commands.
$files = glob('storage/*.log');
$promises = array_map(fn ($file) => async(fn () => compressFile($file)), $files);
await($promises);
Error Handling:
Leverage catch() or try/catch with await to handle failures gracefully:
try {
$result = await(async(fn () => riskyOperation()));
} catch (Throwable $e) {
$this->error("Failed: {$e->getMessage()}");
}
Resource Cleanup:
Use finally() to ensure cleanup (e.g., closing files, releasing locks):
$promise = async(fn () => processLargeFile())
->finally(fn () => cleanupTempFiles());
await($promise);
Laravel Service Providers:
Bind Pokio\Pokio to the container for dependency injection:
$this->app->singleton(Pokio::class, fn () => new Pokio());
Testing:
Mock async/await in unit tests by replacing global functions:
// In tests/CreatesApplication.php
$this->app->singleton('Pokio\async', fn () => fn ($closure) => new MockPromise($closure));
Performance Tuning:
$batchSize = 10;
$batches = array_chunk($tasks, $batchSize);
foreach ($batches as $batch) {
await($batch);
}
Pokio\supportsConcurrency() to dynamically adjust logic based on environment.Shared State:
Avoid sharing mutable state between processes (use FFI shared memory sparingly; prefer returning data via promises).
Process Isolation:
// ❌ Risky: Static state leaks
async(fn () => static::$counter++);
// ✅ Safe: Local state
async(fn () => $localVar = 1);
SIGINT). Use pcntl_signal() in parent if needed.FFI/PCNTL Dependencies:
if (!Pokio\supportsConcurrency()) {
$this->warn('Pokio running sequentially (no PCNTL/FFI).');
}
pcntl/ffi. Use only for local/CI environments or ensure sequential fallback is acceptable.Resource Limits:
tmpfile() or explicit paths.Debugging:
stderr or dedicated log files for child process output:
async(fn () => file_put_contents('storage/logs/child.log', 'Debug info'));
Serialization:
Pokio\Exceptions\SerializationException if data can’t be serialized.Error Handling:
catch chain or wrapper:
function safeAsync(callable $closure): mixed {
return async($closure)->catch(fn (Throwable $e) => reportError($e));
}
Progress Tracking:
then() to track progress:
$promise = async(fn () => longRunningTask())
->then(fn () => $this->info('Task started'))
->finally(fn () => $this->info('Task completed'));
Configuration:
POKIO_MAX_PROCESSES environment variable to limit concurrency:
export POKIO_MAX_PROCESSES=4
Testing:
async/await in tests to avoid actual process spawning:
$mockPokio = new class {
public function async(callable $closure) {
return new class($closure) implements \Pokio\Promise {
public function __invoke() { return $this->closure(); }
// Implement other Promise methods...
};
}
};
Extension Points:
Pokio\Promise for domain-specific behavior.Pokio\FFI\SharedMemory directly for advanced IPC (e.g., large data sharing).Performance:
booted event).Security:
Laravel-Specific:
Process component for hybrid async/sync workflows.catch() or try/catch.FFI shared memory may fail with EINVAL. Fallback to sequential execution or use sysvshm.class CustomPromise implements
How can I help you explore Laravel packages today?