Flyron is a PHP package for asynchronous programming using Fibers and process-based concurrency, specially designed for Laravel applications.
Run the following command to pull in the latest version:
composer require jobmetric/flyronCopy the config file from vendor/jobmetric/flyron/config/config.php to config folder of your Laravel application and rename it to flyron.php
Run the following command to publish the package config file:
php artisan vendor:publish --provider="JobMetric\Flyron\FlyronServiceProvider" --tag="flyron-config"You should now have a config/flyron.php file that allows you to configure the basics of this package.
- Build
asyncflows without losing code readability. Fibers + Promises keep your code straightforward while managing waiting (I/O, network, timers). - Offload heavy or isolated work to safe background
processes(AsyncProcess) with signed payloads, optional encryption, and PID tracking. - Not every async need fits queues. Sometimes you want to execute a
Closureright now, in a separate process, without defining a new Job class. Flyron covers that gap.
Key choices:
- Use
Async + Promisefor cooperative, in-process concurrency (I/O-bound, step-wise logic). - Use
AsyncProcessfor isolated or long-running tasks (heavy CPU, independent lifecycle, separation of failure).
use JobMetric\Flyron\Facades\Async;
$promise = Async::run(fn (int $x) => $x + 1, [41]);
$value = $promise->run(); // 42use JobMetric\Flyron\Facades\AsyncProcess;
$pid = AsyncProcess::run(function () {
file_put_contents(storage_path('app/report.txt'), 'done');
}, [], [
'label' => 'make-report',
'timeout' => 30,
]);Important:
- Set a valid
APP_KEY(payloads are signed). For extra security, enableencryptioninconfig/flyron.php. - Manage PID and payload folders via built-in console commands and scheduling.
Async runs your callback inside a Fiber and returns a Promise. Promise supports then, map, tap, recover, finally, cancel, withTimeout.
use JobMetric\Flyron\Concurrency\Promise;
$result = Promise::from(fn () => 10)
->tap(fn ($v) => info('Tap: '.$v))
->map(fn ($v) => $v + 5)
->then(fn ($v) => Promise::from(fn () => $v * 2))
->finally(fn () => info('Done'))
->run(); // 30Timeout and cooperative cancellation:
use JobMetric\Flyron\Facades\Async;
use JobMetric\Flyron\Concurrency\CancellationToken;
use RuntimeException;
$token = new CancellationToken();
$p = Async::run(function () use ($token) {
for ($i = 0; $i < 10_000; $i++) {
\JobMetric\Flyron\Async::checkpoint($token, 'Operation cancelled');
}
return 'ok';
}, [], 200, $token);
$token->cancel();
try {
$p->run();
} catch (RuntimeException $e) {
// timed out or cancelled
}Helpers:
Async::delay(int $ms)— cooperative sleep.Async::checkpoint(?CancellationToken $token, string $message = 'Operation cancelled.')— throw if cancelled.
use JobMetric\Flyron\Concurrency\Await;
use JobMetric\Flyron\Concurrency\Promise;
$values = Await::all([
Promise::from(fn () => 1),
Promise::from(fn () => 2),
]); // [1, 2]
$first = Await::race([
Promise::from(function () { usleep(10_000); return 'late'; }),
Promise::from(fn () => 'early'),
]); // 'early'
$one = Await::any([
Promise::from(fn () => throw new \RuntimeException('x')),
Promise::from(fn () => 'ok'),
]); // 'ok'
$settled = Await::allSettled([
Promise::from(fn () => 7),
Promise::from(fn () => throw new \RuntimeException('bad')),
]);
$value = Await::until(function () {
static $i = 0; $i++;
return $i >= 3 ? 'ready' : null;
}, 200, 10);Run a serialized Closure in a separate PHP process. Payloads are HMAC-signed with APP_KEY (and can be encrypted). A PID file is written so you can list/clean/kill processes.
use JobMetric\Flyron\Facades\AsyncProcess;
$pid = AsyncProcess::run(function () {
file_put_contents(storage_path('app/report.txt'), 'done');
}, [], [
'label' => 'make-report',
'cwd' => base_path(),
'env' => ['MY_FLAG' => '1'],
'timeout' => 30, // seconds (Symfony Process)
'idle_timeout' => null, // optional
'disable_output' => true,
]);Options:
cwd,env,timeout,idle_timeout,disable_output,label
Security and stability:
- Payloads are signed with
APP_KEY(HMAC-SHA256). If tampered, execution fails. - Optional encryption (
aes-256-gcm) is supported. - Payload path is restricted to
storage/flyron/payloads.
Concurrency throttle:
flyron.process.max_concurrency— limit concurrent background processes (0means unlimited)flyron.process.throttle_mode—rejectorwaitflyron.process.throttle_wait_max_seconds,flyron.process.throttle_wait_interval_ms
-
async(callable $callback, array $args = [], ?int $timeout = null, ?CancellationToken $token = null): Promise- Same as
Async::run(...)
- Same as
-
async_process(callable $callback, array $args = [], array $options = []): ?int- Same as
AsyncProcess::run(...)
- Same as
Facades:
Async→ Fiber/Promise runtimeAsyncProcess→ background process launcher
The package ships two global helpers (autoloaded via Composer files), designed for the most common use-cases with minimal ceremony.
Signature:
async(callable $callback, array $args = [], ?int $timeout = null, ?\JobMetric\Flyron\Concurrency\CancellationToken $token = null): \JobMetric\Flyron\Concurrency\PromiseParameters:
callable $callback— Your function/closure to run inside a Fiber.array $args— Positional arguments passed to the callback.?int $timeout— Optional timeout in milliseconds; best-effort cancellation.?CancellationToken $token— Optional cooperative cancellation token.
Returns:
Promise<T>— A promise you canrun(), chain (then,map,tap,recover,finally),cancel(), or wrap withwithTimeout.
Examples:
// 1) Quick compute
$p = async(fn (int $x) => $x + 1, [41]);
$result = $p->run(); // 42
// 2) With timeout + cooperative cancellation
use JobMetric\Flyron\Concurrency\CancellationToken;
$token = new CancellationToken();
$p = async(function () use ($token) {
for ($i = 0; $i < 10_000; $i++) {
\JobMetric\Flyron\Async::checkpoint($token);
}
return 'ok';
}, [], 200, $token);
$token->cancel();
try { $p->run(); } catch (\RuntimeException $e) { /* cancelled or timed out */ }
// 3) Chaining
$p = async(fn () => 10)
->map(fn ($v) => $v + 5)
->then(fn ($v) => \JobMetric\Flyron\Concurrency\Promise::from(fn () => $v * 2));
$value = $p->run(); // 30Notes and pitfalls:
timeoutis best-effort; long CPU-bound work cannot be preempted. UseCancellationToken+Async::checkpointin loops.- You can compose transformations before calling
run(). The Fiber actually starts whenrun()(oreagerStart()) is invoked.
Signature:
async_process(
callable $callback,
array $args = [],
array $options = [] // ['cwd'?, 'env'?, 'timeout'?, 'idle_timeout'?, 'disable_output'?, 'label'?]
): ?intParameters:
callable $callback— A closure to be serialized and executed in a separate PHP process.array $args— Arguments passed to the closure.array $options— Process options:cwd: ?string— Working directory.env: ?array— Extra environment variables.timeout: ?float— Seconds (Symfony Process timeout).idle_timeout: ?float— Seconds of inactivity before kill (optional).disable_output: ?bool— Defaults totrue.label: ?string— A human-readable label stored in the PID metadata.
Returns:
?int— The spawned process PID ornullif not available on the platform.
Examples:
// 1) Fire-and-forget background task
$pid = async_process(function () {
file_put_contents(storage_path('app/report.txt'), 'done');
}, [], ['label' => 'make-report', 'timeout' => 30]);
// 2) Pass args and environment
$pid = async_process(function (string $path) {
file_put_contents($path, getenv('FLAG') ?: 'no-flag');
}, [storage_path('app/out.txt')], ['env' => ['FLAG' => 'yes']]);Important:
- Payloads are signed with
APP_KEY(HMAC-SHA256). If the payload gets tampered, execution is rejected. - You can enable encryption (e.g.,
aes-256-gcm) viaconfig('flyron.process.encryption_enabled'). - Payload files live under
storage/flyron/payloadsand are cleaned byflyron:process-clean --payloadsper TTL. - Concurrency throttle is controlled via
config('flyron.process.max_concurrency')and related settings.
Output handling:
- Any
echo/buffered output produced by your closure is captured byflyron:execand appended to a log file next to the payload (e.g.,<payload>.json.log). For structured results, prefer writing to your own files or storage.
php artisan flyron:process-list— list tracked processes with status.php artisan flyron:process-clean {--payloads}— remove dead PID files; with--payloads, remove old payloads by TTL.php artisan flyron:process-kill {pid}— kill a Flyron-managed process (PID file must exist).php artisan flyron:optimize— clean stale PID files (maintenance).php artisan flyron:exec {payload_path}— internal; used by AsyncProcess.
return [
'php_path' => env('FLYRON_PHP_PATH', PHP_BINARY),
'artisan_path' => env('FLYRON_ARTISAN_PATH', base_path('artisan')),
'schedule' => [
'enabled' => env('FLYRON_SCHEDULE_ENABLED', true),
'environments' => env('FLYRON_SCHEDULE_ENVIRONMENTS') ? explode(',', env('FLYRON_SCHEDULE_ENVIRONMENTS')) : [],
'process_clean' => [
'enabled' => env('FLYRON_SCHEDULE_PROCESS_CLEAN', true),
'frequency' => env('FLYRON_SCHEDULE_PROCESS_CLEAN_FREQUENCY', 'hourly'),
'payloads' => env('FLYRON_SCHEDULE_PROCESS_CLEAN_PAYLOADS', true),
],
'process_optimize' => [
'enabled' => env('FLYRON_SCHEDULE_PROCESS_OPTIMIZE', false),
'frequency' => env('FLYRON_SCHEDULE_PROCESS_OPTIMIZE_FREQUENCY', 'weekly'),
],
],
'process' => [
'encryption_enabled' => env('FLYRON_PROCESS_ENCRYPTION', false),
'encryption_cipher' => env('FLYRON_PROCESS_CIPHER', 'aes-256-gcm'),
'payload_ttl_seconds' => env('FLYRON_PROCESS_PAYLOAD_TTL', 86400),
'max_concurrency' => env('FLYRON_PROCESS_MAX_CONCURRENCY', 0),
'throttle_mode' => env('FLYRON_PROCESS_THROTTLE_MODE', 'reject'),
'throttle_wait_max_seconds' => env('FLYRON_PROCESS_THROTTLE_WAIT_MAX', 30),
'throttle_wait_interval_ms' => env('FLYRON_PROCESS_THROTTLE_WAIT_INTERVAL', 200),
],
];Scheduling:
- You can enable periodic
process-cleanandoptimizewith flexible frequencies (method strings likedaily,hourlyAt:15, or a raw cron expression).
- Offload CPU-heavy work to
AsyncProcessor queues. UsePromise/Fiberfor I/O-bound or step-wise operations. - Use
Async::checkpoint($token)inside long loops to make cancellation responsive. - Ensure a valid
APP_KEY; in production, consider enabling payload encryption. - Schedule
flyron:process-cleanto keep PID/payload folders tidy.
This package includes unit and feature tests covering: Promise/Await/Async, Traits, Facades, Helpers, ServiceProvider (bindings and scheduling), and Console Commands.
Run tests:
php vendor/bin/phpunitThank you for considering contributing to Flyron! See the CONTRIBUTING.md for details.
The MIT License (MIT). Please see License File for more information.