Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

React Child Process Promise Laravel Package

wyrihaximus/react-child-process-promise

Promise-based wrapper around ReactPHP's child process component. Spawn child processes and handle their stdout, stderr, exit codes, and streams through promises, making asynchronous command execution simpler in ReactPHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require wyrihaximus/react-child-process-promise

This pulls in react/child-process, react/event-loop, react/promise, and (transitively) symfony/process. The package itself is a thin promise shim — the real behavior lives in React\ChildProcess\Process, so read its docs alongside this one.

Where to look first: the README, then src/ (there is essentially one class). Skim vendor/react/child-process/src/Process.php because that is where escaping, exit codes, and stream handling actually happen.

Minimal usage — run a command and get its output as a promise:

<?php
require 'vendor/autoload.php';

use React\EventLoop\Factory;
use WyriHaximus\React\ChildProcess\Promise\Process;

$loop = Factory::create();

$process = new Process($loop, 'printf "hello"');
$process->run()->then(
    function ($output) { echo "OUT: {$output}\n"; },
    function (\Throwable $e) { echo "ERR: {$e->getMessage()}\n"; }
);

$loop->run();

(Constructor argument order and the exact run() signature vary by version — confirm against src/Process.php before copying this into production.)

First real use case: an Artisan command or long-lived queue worker that shells out to ffmpeg, git, backup scripts, or npm builds and wants to fan several out in parallel instead of blocking one at a time. This package is not a fit for HTTP request handlers — Laravel's Process facade already covers synchronous calls.

Implementation Patterns

Fan-out and gather. The main value is running N processes concurrently and joining the results:

use function React\Promise\all;

$promises = [];
foreach ($files as $file) {
    $cmd = 'convert ' . escapeshellarg($file) . ' -resize 50% ' . escapeshellarg($file . '.thumb.jpg');
    $promises[$file] = (new Process($loop, $cmd))->run();
}

all($promises)->then(
    function (array $outputs) { /* keyed by $file */ },
    function (\Throwable $e) { /* first rejection wins */ }
);

Pipelines. Chain then() to feed one command's output into the next, or return another promise from a callback to flatten it.

Streaming large output. If the wrapper only exposes a collected-output promise, do not use it for multi-megabyte output — reach the underlying React\ChildProcess\Process, attach a stdout listener, and write to a file or stream to the client. Aggregating into a string is a memory landmine.

Timeouts. react/child-process has none. Wrap it:

$timer = $loop->addTimer(30, function () use ($process) {
    $process->terminate(); // SIGTERM; escalate to SIGKILL in a follow-up timer
});
$process->run()->then(
    fn ($out) => $loop->cancelTimer($timer),
    fn ($e)   => $loop->cancelTimer($timer)
);

Laravel integration. Bind the loop as a singleton in a service provider, and bind a ProcessFactory that injects the loop plus defaults (cwd, env, logger, timeout). Controllers stay synchronous; the async path lives in Artisan commands or custom queue workers you start with $loop->run().

Error handling. Always pass both handlers. Log the full command string, exit code, and stderr — the rejection message from a shim is often just an exit code, which is useless alone.

Retries/backoff. Catch the rejection, $loop->addTimer($delay, fn () => retry()), and cap attempts.

Concurrency caps. Chunk the input (array_chunk($files, 8)) and await each batch; each promise is a real OS process, not a coroutine.

Gotchas and Tips

  • The loop must run. Promise callbacks only fire while $loop->run() is active. There is no wait(), and you cannot await unless you add a coroutine bridge like Recoil. A common first bug is “my promise never resolves” — the loop was never started.
  • Never block inside a then(). A synchronous sleep(), DB query, or file_get_contents on a URL stalls the loop and every sibling process with it.
  • Output is buffered in memory. Collecting stdout as a string will OOM on chatty commands. Stream or redirect to a file for anything large.
  • Shell quoting is on you. The command is one string handed to the shell. escapeshellarg() every interpolated value, or you have a command-injection vulnerability waiting to happen — especially if any part comes from user input.
  • Windows behaves differently. cmd vs sh semantics, proc_open quirks, and terminate() behavior all diverge. Test if win is in your deployment matrix.
  • Non-zero exit rejects, but check what you get back. If stderr isn't included in the rejection, wrap the call so failures carry stderr — otherwise debugging production failures is guesswork.
  • Orphaned children. terminate() sends SIGTERM to the immediate child; shell wrappers like sh -c "long; task" can leave grandchildren running. Prefix with exec where possible, and consider process groups.
  • ext-pcntl. Signal-based termination on Unix generally needs it. Confirm your images have it.
  • Promise API version drift. Last release was Dec 2020, targeting react/promise v2. If your app pulls v3, otherwise()/done() are gone. Verify compatibility in composer.lock.
  • Unmaintained risk. 12 stars, zero dependents, no release since 2020. Pin the exact version, and seriously consider writing the ~50-line wrapper yourself directly over react/child-process — you'd gain streaming, timeouts, and stderr control, and lose a dependency that may not track ReactPHP releases.
  • Inherited environment. A command that works in your shell may fail under a queue worker because PATH or env vars differ. Pass cwd and env explicitly.
  • Debugging. Log the resolved command string and PID; run the identical command synchronously through Symfony's Process to isolate whether the failure is the command or the async plumbing; use ps aux to check for leaked processes after tests.
  • Extension points. Prefer a decorator (or a Laravel-bound factory) around Process that adds logging, timeouts, and env defaults, rather than subclassing — it keeps you one swap away from replacing the shim with your own implementation.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle