Enterprise-grade subprocess execution for the MonkeysLegion v2 framework.
Secure command building • sync/async execution • process pools • pipelines • real-time output streaming • signal handling • PHP 8.4 property hooks • PHPStan Level 9
composer require monkeyscloud/monkeyslegion-processRequirements: PHP ≥ 8.4 · ext-pcntl
use MonkeysLegion\Process\Process;
// Run a command synchronously
$process = new Process(['ls', '-la']);
$result = $process->run();
echo $result->output(); // stdout
echo $result->exitCode(); // 0
var_dump($result->successful); // true (property hook)use MonkeysLegion\Process\ProcessBuilder;
$result = ProcessBuilder::create('npm', 'install')
->withCwd('/app')
->withEnv(['NODE_ENV' => 'production'])
->withTimeout(120)
->withIdleTimeout(30)
->run();
if ($result->failed) {
echo $result->errorOutput();
}use MonkeysLegion\Process\CommandLine;
// Injection-safe — arguments are escaped automatically
$cmd = CommandLine::create('git')
->addArg('commit')
->addOption('-m', 'Initial commit')
->addIf($signed, '--gpg-sign');
$process = new Process($cmd->toArray());
$process->mustRun(); // Throws on failure// Static factory for raw shell commands
$process = Process::fromShellCommandline('cat data.csv | grep error | wc -l');
$result = $process->run();
// Or via builder
$result = ProcessBuilder::shell('ls -la | grep .php')->run();// Clear buffers during long-running processes
$process->clearOutput();
$process->clearErrorOutput();
// Disable capture entirely (saves memory for high-output processes)
$process = new Process(['mysqldump', 'mydb']);
$process->disableOutput();
$process->run();
// Re-enable when needed
$process->enableOutput();
// Config getters
$process->getWorkingDirectory(); // ?string
$process->getEnv(); // ?array
$process->getTimeout(); // ?float
$process->getIdleTimeout(); // ?float$process = new Process(['vendor/bin/phpunit']);
$process->start();
// Do other work...
$result = $process->wait();$process = new Process(['npm', 'run', 'build']);
$process->run(function (string $type, string $data): void {
echo "[{$type}] {$data}";
});use MonkeysLegion\Process\Enum\OutputType;
$process = new Process(['tail', '-f', '/var/log/app.log']);
$process->setTimeout(null)->start();
foreach ($process as [$type, $data]) {
if ($type === OutputType::Stderr) {
break;
}
echo $data;
}
$process->stop();use MonkeysLegion\Process\InputStream;
// String input
$process = new Process(['wc', '-l']);
$process->setInput("line 1\nline 2\nline 3");
$result = $process->run();
echo $result->output(); // 3
// Streaming input
$input = new InputStream();
$input->write("chunk 1\n");
$input->write("chunk 2\n");
$input->close();
$process = new Process(['cat'], input: $input);
$process->run();use MonkeysLegion\Process\Pool\ProcessPool;
$results = ProcessPool::create(maxConcurrent: 3)
->add('assets', ['npm', 'run', 'build'])
->add('tests', ['vendor/bin/phpunit'])
->add('lint', ['vendor/bin/phpstan', 'analyse'])
->wait();
if ($results->successful) {
echo "All passed!\n";
} else {
foreach ($results->failures() as $name => $result) {
echo "{$name} failed: {$result->errorOutput()}\n";
}
}
// Per-result access
$results->get('tests')->output();ProcessPool::create()
->add('a', ['echo', 'hello'])
->add('b', ['echo', 'world'])
->onComplete(function (string $name, ProcessResult $result): void {
echo "{$name} finished with code {$result->exitCode()}\n";
})
->wait();use MonkeysLegion\Process\Pipeline\ProcessPipeline;
$result = ProcessPipeline::create()
->pipe(['cat', 'access.log'])
->pipe(['grep', '-i', 'error'])
->pipe(['wc', '-l'])
->run();
echo $result->output(); // Final output from wc
echo $result->finalOutput; // Same (property hook)
var_dump($result->successful); // true if all stages passed
// Per-stage debugging
$stage0 = $result->stage(0);
echo $stage0->exitCode();use MonkeysLegion\Process\PhpProcess;
$process = new PhpProcess('<?php echo PHP_VERSION; ?>');
$result = $process->run();
echo $result->output(); // e.g. "8.4.1"use MonkeysLegion\Process\Enum\Signal;
$process = new Process(['sleep', '60']);
$process->start();
// Send SIGTERM
$process->signal(Signal::SIGTERM);
// Or by number
$process->signal(15);use MonkeysLegion\Process\Exception\ProcessTimedOutException;
$process = new Process(['sleep', '30']);
$process->setTimeout(5); // Kill after 5s total
$process->setIdleTimeout(2); // Kill after 2s with no output
try {
$process->run();
} catch (ProcessTimedOutException $e) {
echo $e->isIdleTimeout() ? 'Idle timeout' : 'General timeout';
}use MonkeysLegion\Process\Exception\{
ProcessException,
ProcessFailedException,
ProcessTimedOutException,
ProcessSignaledException,
};
try {
$result = $process->mustRun();
} catch (ProcessFailedException $e) {
echo $e->getProcessExitCode();
echo $e->getProcessOutput();
echo $e->getProcessErrorOutput();
} catch (ProcessTimedOutException $e) {
echo $e->getTimeout();
} catch (ProcessSignaledException $e) {
echo $e->getSignal()?->label();
}
// Or check result manually
$result = $process->run();
$result->throwIfFailed();use MonkeysLegion\Process\Testing\{FakeProcess, FakeProcessFactory, PendingProcessFake};
// Quick fakes
$fake = FakeProcess::success('build complete');
$fake = FakeProcess::failure('error occurred', exitCode: 1);
// Factory with pattern matching
$factory = FakeProcessFactory::make()
->fake('npm *', (new PendingProcessFake())
->withOutput('installed 42 packages')
->withExitCode(0))
->fake('phpstan *', (new PendingProcessFake())
->withErrorOutput('Found 3 errors')
->withExitCode(1));
$result = $factory->create(['npm', 'install'])->run();
echo $result->output(); // "installed 42 packages"
// Assertions
$factory->assertRan('npm *');
$factory->assertNotRan('rm *');use MonkeysLegion\Process\ExecutableFinder;
$php = ExecutableFinder::findPhp(); // /usr/bin/php
$node = ExecutableFinder::find('node'); // /usr/local/bin/node
$git = ExecutableFinder::find('git', '/usr/bin/git'); // With fallbackThe package uses PHP 8.4 property hooks for live state access:
$process->isRunning; // bool (hook)
$process->isSuccessful; // bool (hook)
$process->statusLabel; // string (hook)
$result->successful; // bool (hook)
$result->failed; // bool (hook)
$poolResults->successful; // bool (hook) — all passed
$poolResults->failed; // bool (hook) — any failed
$poolResults->count; // int (hook)
$pipelineResult->successful; // bool (hook)
$pipelineResult->finalOutput; // string (hook)use MonkeysLegion\Process\Provider\ProcessProvider;
// Register in your container
$factory = ProcessProvider::register($config['process'] ?? []);
// Use factory
$process = $factory->create(['git', 'status']);
$result = $process->run();process {
default_timeout = ${PROCESS_TIMEOUT:-60}
idle_timeout = ${PROCESS_IDLE_TIMEOUT:-0}
default_cwd = ${PROCESS_CWD:-}
pool {
max_concurrent = ${PROCESS_POOL_MAX:-5}
}
env {
inherit = true
}
}
MIT © 2026 MonkeysCloud Team