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

Site Process Laravel Package

consolidation/site-process

Consolidation Site Process is a PHP process runner for local and remote site commands. It extends Symfony Process with site-alias support, enabling SSH, Docker, and other transports so tools like Drush can execute commands against configured environments safely and consistently.

Deep Wiki
Context7

Getting Started

consolidation/site-process is a library, not an application — you install it to use from your own command/tool code.

composer require consolidation/site-process

It pulls in symfony/process and pairs with consolidation/site-alias (the alias format is @site.env, typically stored in drush/sites/*.site.yml). There is no binary to run; the entry points are:

  1. Consolidation\SiteProcess\SiteProcess — the Symfony\Component\Process\Process subclass. It is the object you call run(), start(), wait(), getOutput(), getExitCode() on.
  2. Consolidation\SiteProcess\SiteProcessFactory — creates a SiteProcess from a SiteAliasInterface, a command array, arguments, and options. Most applications never instantiate SiteProcess directly.
  3. Consolidation\SiteProcess\Transport\* — how a command reaches its target (LocalTransport, SshTransport, etc.). This is what makes a local alias run locally and a remote alias run through ssh.
  4. Consolidation\SiteProcess\ProcessManagerAwareInterface / ProcessManagerAwareTrait — the standard way to give a command class access to a factory.

First use case — run drush status against an alias and let the package decide whether that means locally or over SSH:

use Consolidation\SiteAlias\SiteAliasManager;
use Consolidation\SiteProcess\SiteProcessFactory;

$aliasManager = new SiteAliasManager();
$aliasManager->addSearchPath(__DIR__ . '/drush/sites');

$alias   = $aliasManager->get('@prod');
$factory = new SiteProcessFactory();
$process = $factory->create($alias, ['drush', 'status', '--fields=name,status']);

$process->run();

if (!$process->isSuccessful()) {
    fwrite(STDERR, $process->getErrorOutput());
    exit($process->getExitCode());
}

echo $process->getOutput();

If @prod has a host/user, the package builds an ssh invocation; if it is a local alias (or @self), it runs the command directly. Your calling code does not branch on that.

Look first at SiteProcess::create() / SiteProcessFactory::create() and at the transport selected for the alias — that is where 90% of surprises come from.

Implementation Patterns

Alias as the target, not as a string you interpolate

The core pattern is: never build ssh user@host 'cmd' yourself. Build a SiteProcess from an alias and let the transport do the wrapping.

$process = $factory->create($alias, ['git', 'status', '--porcelain']);
$process->run();

The alias supplies host, user, port, root/path, and any SSH options. This is also how you get consistent behavior across local and remote in tests: point the same code at @self and it runs locally.

ProcessManagerAwareTrait in command classes

For anything larger than a script, follow the Drush convention: your command class uses the trait, and a factory is injected once.

use Consolidation\SiteProcess\ProcessManagerAwareTrait;

class DeployCommand
{
    use ProcessManagerAwareTrait;

    public function deploy($aliasName)
    {
        $alias = $this->siteAliasManager()->get($aliasName);

        $this->processManager()
            ->create($alias, ['composer', 'install', '--no-dev'])
            ->mustRun();

        $this->processManager()
            ->create($alias, ['drush', 'cache:rebuild'])
            ->mustRun();
    }
}

mustRun() throws Symfony\Component\Process\ProcessFailedException on non-zero exit — good default for steps that must succeed.

Passing commands: array first, string only when you mean a shell

create() expects the command as an array (['ls', '-la', '/tmp']). Passing a single string tends to be interpreted as a shell command and reintroduces quoting problems, especially once it is wrapped in an SSH command. Use arrays and let Symfony/the transport escape each argument. Reserve a shell string for cases that genuinely need pipes or redirection.

Per-process options

Set behavior on the returned process object before running:

$process
    ->setWorkingDirectory('/var/www/app') // maps to cd on the remote side
    ->setTimeout(null)                    // long-running deploy
    ->setTty(true)                        // interactive / progress output
    ->setRealtime(true);                  // echo output as it arrives

setRealtime(true) is the one you want for streaming logs/rsync progress; combine with start() + wait() and a callback when you need to inspect lines as they arrive:

$process->start(function ($type, $buffer) {
    echo $buffer;
});
$process->wait();

One abstraction for local and remote workflows

A typical workflow is: resolve the alias, run a sequence of commands, short-circuit on failure, and treat the whole thing as one unit.

foreach ([['git','pull'], ['composer','install'], ['drush','updb','-y']] as $cmd) {
    $p = $factory->create($alias, $cmd);
    $p->setTimeout(null)->run();
    if (!$p->isSuccessful()) {
        throw new \RuntimeException(sprintf(
            "%s failed (exit %d): %s",
            implode(' ', $cmd),
            $p->getExitCode(),
            $p->getErrorOutput()
        ));
    }
}

Custom transports for non-SSH targets

If your environment is Docker, Kubernetes, or a jump host, that is the extension point: implement TransportInterface (the methods that build the command and/or feed stdin) and use it instead of the default SSH transport for the alias. This keeps your command code identical while the delivery mechanism changes.

Gotchas and Tips

  • The alias needs a search path. new SiteAliasManager() won't find drush/sites/*.site.yml unless you addSearchPath() (or hand it a root). A missing alias usually shows up as an exception from get(), not a process failure.
  • Exit codes can be the SSH exit code. If the SSH wrapper fails (DNS, auth, host key), you get a non-zero code that has nothing to do with your command. Check getErrorOutput() for ssh: connect/Permission denied before blaming the remote command.
  • Quoting is the #1 source of "works locally, breaks remotely". Arrays are escaped argument-by-argument; strings are handed to a shell and then re-quoted inside SSH. Prefer arrays, and if you must use a string, test it against a real remote alias.
  • setWorkingDirectory() semantics differ by side. Locally it is Symfony's cwd. Remotely it becomes a cd in the remote shell. The path must exist on the remote host; there is no fallback.
  • Environment variables don't cross SSH automatically. Locally, setEnv() affects the child. Remotely, you generally need to export them inside the command (['sh', '-c', 'export FOO=bar; ./deploy.sh']). Do not assume setEnv() is propagated.
  • TTY/PTY is not free. setTty(true) allocates a pseudo-terminal (and on the remote side, typically requires script/ssh -t). It merges stdout and stderr, which breaks clean output capture. Use it only for interactive commands; use plain capture for anything you parse.
  • Realtime output requires a callback or start()/wait(). Setting setRealtime(true) and then calling run() gives you buffered output; the streaming behavior is tied to the callback form.
  • setTimeout(null) is often required. Deploys, composer install, and rsync routinely exceed Symfony's default timeout; otherwise you get a ProcessTimedOutException mid-run.
  • Debug by printing the command line, not by guessing. $process->getCommandLine() shows the fully wrapped invocation the transport built. Copying that into a shell is the fastest way to reproduce a failure.
  • Wire a PSR-3 logger. setLogger() on the process gives you the constructed command and output in your application logs; extremely helpful when the failing step is buried in a multi-command workflow.
  • Pin versions — this package is old. The last release was May 2020, so it targets the Symfony 4/5 Process API. On modern Symfony/PHP 8+, expect deprecations and possibly breakage; constrain symfony/process in your composer.json and run a smoke test (@self alias, ['echo', 'hi']) after upgrades.
  • No retries, no orchestration. This is a thin wrapper; implement retry/backoff, parallelism (start() several processes, then wait()), and rollback logic in your own layer.
  • Testing tip. Because local aliases use the same code path, unit tests can point at @self and assert on exit codes and output without SSH. For fully hermetic tests, inject a fake factory that returns stub Process objects.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package