monkeyscloud / monkeyslegion-process
Subprocess execution for MonkeysLegion v2 — async, pools, pipelines, signals, PHP 8.4 property hooks
Package info
github.com/MonkeysCloud/MonkeysLegion-Process
pkg:composer/monkeyscloud/monkeyslegion-process
1.0.0
2026-05-17 04:33 UTC
Requires
- php: ^8.4
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
Suggests
- ext-pcntl: For advanced signal handling and process control
- ext-posix: For advanced process group management
This package is auto-updated.
Last update: 2026-05-17 04:35:05 UTC
README
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
Installation
composer require monkeyscloud/monkeyslegion-process
Requirements: PHP ≥ 8.4 · ext-pcntl
Quick Start
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)
Process Builder (Fluent API)
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(); }
Safe Command Builder
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
Shell Commands (Pipes & Redirects)
// 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();
Output Control
// 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
Async Execution
$process = new Process(['vendor/bin/phpunit']); $process->start(); // Do other work... $result = $process->wait();
Real-Time Output Callback
$process = new Process(['npm', 'run', 'build']); $process->run(function (string $type, string $data): void { echo "[{$type}] {$data}"; });
Iterator Streaming
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();
Stdin Input
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();
Process Pools (Concurrent)
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();
Completion Callback
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();
Pipelines (stdout → stdin chaining)
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();
PHP Subprocess
use MonkeysLegion\Process\PhpProcess; $process = new PhpProcess('<?php echo PHP_VERSION; ?>'); $result = $process->run(); echo $result->output(); // e.g. "8.4.1"
Signals
use MonkeysLegion\Process\Enum\Signal; $process = new Process(['sleep', '60']); $process->start(); // Send SIGTERM $process->signal(Signal::SIGTERM); // Or by number $process->signal(15);
Timeouts
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'; }
Exception Handling
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();
Testing (Fakes)
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 *');
Executable Finder
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 fallback
Property Hooks (PHP 8.4)
The 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)
DI Integration
use MonkeysLegion\Process\Provider\ProcessProvider; // Register in your container $factory = ProcessProvider::register($config['process'] ?? []); // Use factory $process = $factory->create(['git', 'status']); $result = $process->run();
Configuration (config/process.mlc)
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
}
}
License
MIT © 2026 MonkeysCloud Team