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

Process Laravel Package

symfony/process

Symfony Process executes commands in sub-processes, giving PHP apps a clean, portable API for running external programs. It handles input/output streams, timeouts, environment variables, working directories, and exit codes, so you can run shell commands safely and reliably across platforms.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation — pin to the security-fixed line:

    composer require symfony/process:^5.4.54
    

    v5.4.54 is a security release. Every project that shells out (deployments, queue workers, backups, git, rsync, mysqldump, Node/Python tooling) should be on >=5.4.54, or on a patched 6.4/7.x equivalent. No service provider, no config file — Composer autoload is all you need.

  2. First use case — run a command and fail loudly:

    use Symfony\Component\Process\Process;
    use Symfony\Component\Process\Exception\ProcessFailedException;
    
    $process = new Process(['ls', '-la']);
    $process->run();
    
    if (!$process->isSuccessful()) {
        throw new ProcessFailedException($process);
    }
    
    echo $process->getOutput();
    
  3. Where to look first

    • Official docs and the Process class: run(), mustRun(), start(), wait(), getOutput(), getErrorOutput(), isSuccessful(), getExitCode().
    • Process::setEnv() / inheritEnvironmentVariables() — the area that changed in this release.
    • Always pass commands as an array (['git', 'status']), never a single string with interpolation. The array form bypasses the shell entirely, which is your primary injection defence.

Implementation Patterns

1. Run it, stream it, or hold it

// Blocking, buffered
$process = new Process(['php', 'artisan', 'migrate', '--force']);
$process->setTimeout(300);
$process->mustRun(); // throws ProcessFailedException on non-zero exit

// Streaming with the built-in callback — preferred over busy-looping on getOutput()
$process->run(function (string $type, string $buffer) {
    $type === Process::ERR
        ? Log::channel('deploy')->error($buffer)
        : Log::channel('deploy')->info($buffer);
});

Prefer run(callable) or start() + wait(callable) over while ($process->isRunning()) { echo $process->getOutput(); }. The latter re-prints accumulated output and burns CPU; incremental output is delivered via getIncrementalOutput() at best.

2. Environment variables

$process->setEnv([
    'APP_ENV'    => 'staging',
    'DB_HOST'    => config('database.connections.mysql.host'),
    'CONFIG'     => json_encode(['key' => 'value']), // complex data must be pre-encoded
]);

Since 5.4.54, invalid env var names and non-scalar values are silently ignored rather than propagated or stringified. That means:

  • An array value no longer becomes "Array" in the child process — it just vanishes. json_encode() explicitly, always.
  • A malformed key (hyphens, spaces, leading digits) is dropped. Don't rely on the process exiting non-zero to detect this — it won't; the child simply runs without the variable.
// Scrub inherited context when spawning from a web request
$process->inheritEnvironmentVariables(false);
$process->setEnv($allowListedEnvsOnly);

3. Error handling that survives production

try {
    $process->mustRun();
} catch (ProcessFailedException $e) {
    report($e);
    $this->error($process->getErrorOutput());
} catch (ProcessTimedOutException $e) {
    // child was SIGKILLed — assume partial side effects, clean up
}

ProcessFailedException's message embeds full command line and stdout/stderr. If you pass secrets via env or arguments, sanitise before report() — getCommandLine() is a config-leak waiting to happen in Sentry/Bugsnag.

4. Laravel integration

class DeployCommand extends Command
{
    protected $signature = 'deploy:run';

    public function handle(): int
    {
        $process = new Process([base_path('vendor/bin/deployer'), 'deploy']);
        $process->setTimeout(1800);
        $process->setWorkingDirectory(base_path());

        $process->run(fn ($type, $buffer) => $this->output->write($buffer));

        return $process->isSuccessful() ? self::SUCCESS : self::FAILURE;
    }
}

Wrap it in a small service (ProcessRunner) bound in the container so tests can fake it; otherwise every feature test that touches a console command shells out for real. Inject config (timeout, binary paths) rather than calling env() inside the class — env() returns null once config:cache has run.

5. Parallel execution

$pool = collect($queues)->map(function (string $queue) {
    $p = new Process(['php', 'artisan', 'queue:work', "--queue={$queue}", '--stop-when-empty']);
    $p->setTimeout(300);
    $p->start();
    return $p;
});

foreach ($pool as $p) {
    $p->wait(); // collect exit codes afterwards
}

Cap concurrency explicitly. Process does not pool threads; five start() calls are five OS processes. In Octane/Swoole workers, also cap by config('app.max_parallel') or you'll fork-bomb the box under load.

6. Cross-platform commands

$process = PHP_OS_FAMILY === 'Windows'
    ? new Process(['cmd', '/c', 'dir'])       // cmd.exe is now resolved by absolute path (5.4.54)
    : new Process(['ls', '-la']);

5.4.54 fixes the cmd.exe fallback to use an absolute path, so Windows no longer picks up a shadowed cmd.exe from PATH/cwd — relevant on shared CI runners and Windows hosts with a poisoned working directory.


Gotchas and Tips

Upgrade-related (this release)

  1. Upgrade is not optional if you run PHP-FPM/CGI. #65738 Stop leaking CGI/FastCGI request-context vars to subprocesses fixes a real information leak: HTTP_*, REQUEST_URI, auth headers, cookie-derived values previously flowed into child processes. If you were relying on the old inheritEnvironmentVariables(false) + array_filter($_ENV, ...) workaround, you can drop it — but only after pinning ^5.4.54. On older versions, keep the workaround.

  2. Env filtering is now silent. #65739 Ignore invalid env var names and non-scalar env values removes the "Array to string conversion" warning and the malformed-name crash — and with them your only signal that something was wrong. Validate your own env map before setEnv(); a typo'd key means a child process running with defaults.

  3. Windows cmd.exe resolution changed. If you shell out via Process::fromShellCommandline() on Windows, the command wrapper is now resolved from an absolute path. Tests asserting on getCommandLine() output, or scripts that prepended their own cmd.exe to PATH, may behave differently.

Pitfalls

  1. Timeouts kill, they don't ask. setTimeout() sends SIGKILL after SIGTERM on POSIX. A mysqldump or rsync killed mid-flight leaves temp files and locks. Catch ProcessTimedOutException and clean up explicitly; don't assume idempotency.

  2. Never build a command string from user input. Process::fromShellCommandline() re-enables shell metacharacters (;, &&, $(...)). The array constructor does not. If you must interpolate, run values through escapeshellarg() and treat it as a last resort.

  3. getOutput() is cumulative. Calling it in a while ($process->isRunning()) loop re-emits the entire buffer each tick. Use the run(fn ($type, $buffer) => ...) callback or getIncrementalOutput()/getIncrementalErrorOutput() with disableOutput() off.

  4. PTY mode (usePty(true)) merges stdout and stderr and is Linux/macOS-only. Don't enable it for commands whose exit semantics you parse from stderr; do enable it for anything expecting a TTY (ssh, rsync progress bars, interactive prompts).

  5. Working directory defaults to the current process cwd. Under queue:work or Octane that's public/, not the app root. Always setWorkingDirectory(base_path()) for Artisan invocations, or you'll get confusing "command not found" for relative binaries.

  6. env() inside a process-building class breaks under config:cache. Resolve env-dependent values in a config file and inject them; Process wrappers are prime offenders because they're usually built lazily at runtime.

  7. Extension points worth knowing: setInput() for stdin piping, checkTimeout() for long-lived processes you self-poll, Process::setIdleTimeout() (6.x+) to kill hung-but-alive children, and custom Process subclasses when you need a shared command prefix (e.g. php artisan).

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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core