PendingProcess.php
TLDR
The PendingProcess.php
file is a part of the Illuminate Process component in the Laravel framework. It defines the PendingProcess
class, which represents a process that is about to be executed. It provides methods to specify the command, working directory, timeout, input, output options, and other process-related parameters. It also includes methods to run or start the process and handle fake process results.
Classes
PendingProcess
The PendingProcess
class represents a process that is about to be executed. It contains properties and methods to configure and execute the process. Some of the important properties include:
-
$factory
: The process factory instance. -
$command
: The command to invoke the process. -
$path
: The working directory of the process. -
$timeout
: The maximum number of seconds the process may run. -
$idleTimeout
: The maximum number of seconds the process may go without returning output. -
$environment
: The additional environment variables for the process. -
$input
: The standard input data that should be piped into the command. -
$quietly
: Indicates whether output should be disabled for the process. -
$tty
: Indicates if TTY mode should be enabled. -
$options
: The options that will be passed to "proc_open". -
$fakeHandlers
: The registered fake handler callbacks.
The class provides various methods to configure the process, including command()
, path()
, timeout()
, idleTimeout()
, forever()
, env()
, input()
, quietly()
, tty()
, and options()
. It also provides methods to run or start the process, such as run()
and start()
. Additionally, it includes methods to handle fake process results, such as withFakeHandlers()
, fakeFor()
, resolveSynchronousFake()
, and resolveAsynchronousFake()
.
<?php
namespace Illuminate\Process;
use Closure;
use Illuminate\Process\Exceptions\ProcessTimedOutException;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use LogicException;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessTimedOutException as SymfonyTimeoutException;
use Symfony\Component\Process\Process;
class PendingProcess
{
use Conditionable;
/**
* The process factory instance.
*
* @var \Illuminate\Process\Factory
*/
protected $factory;
/**
* The command to invoke the process.
*
* @var array<array-key, string>|string|null
*/
public $command;
/**
* The working directory of the process.
*
* @var string|null
*/
public $path;
/**
* The maximum number of seconds the process may run.
*
* @var int|null
*/
public $timeout = 60;
/**
* The maximum number of seconds the process may go without returning output.
*
* @var int
*/
public $idleTimeout;
/**
* The additional environment variables for the process.
*
* @var array
*/
public $environment = [];
/**
* The standard input data that should be piped into the command.
*
* @var string|int|float|bool|resource|\Traversable|null
*/
public $input;
/**
* Indicates whether output should be disabled for the process.
*
* @var bool
*/
public $quietly = false;
/**
* Indicates if TTY mode should be enabled.
*
* @var bool
*/
public $tty = false;
/**
* The options that will be passed to "proc_open".
*
* @var array
*/
public $options = [];
/**
* The registered fake handler callbacks.
*
* @var array
*/
protected $fakeHandlers = [];
/**
* Create a new pending process instance.
*
* @param \Illuminate\Process\Factory $factory
* @return void
*/
public function __construct(Factory $factory)
{
$this->factory = $factory;
}
/**
* Specify the command that will invoke the process.
*
* @param array<array-key, string>|string $command
* @return $this
*/
public function command(array|string $command)
{
$this->command = $command;
return $this;
}
/**
* Specify the working directory of the process.
*
* @param string $path
* @return $this
*/
public function path(string $path)
{
$this->path = $path;
return $this;
}
/**
* Specify the maximum number of seconds the process may run.
*
* @param int $timeout
* @return $this
*/
public function timeout(int $timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Specify the maximum number of seconds a process may go without returning output.
*
* @param int $timeout
* @return $this
*/
public function idleTimeout(int $timeout)
{
$this->idleTimeout = $timeout;
return $this;
}
/**
* Indicate that the process may run forever without timing out.
*
* @return $this
*/
public function forever()
{
$this->timeout = null;
return $this;
}
/**
* Set the additional environment variables for the process.
*
* @param array $environment
* @return $this
*/
public function env(array $environment)
{
$this->environment = $environment;
return $this;
}
/**
* Set the standard input that should be provided when invoking the process.
*
* @param \Traversable|resource|string|int|float|bool|null $input
* @return $this
*/
public function input($input)
{
$this->input = $input;
return $this;
}
/**
* Disable output for the process.
*
* @return $this
*/
public function quietly()
{
$this->quietly = true;
return $this;
}
/**
* Enable TTY mode for the process.
*
* @param bool $tty
* @return $this
*/
public function tty(bool $tty = true)
{
$this->tty = $tty;
return $this;
}
/**
* Set the "proc_open" options that should be used when invoking the process.
*
* @param array $options
* @return $this
*/
public function options(array $options)
{
$this->options = $options;
return $this;
}
/**
* Run the process.
*
* @param array<array-key, string>|string|null $command
* @param callable|null $output
* @return \Illuminate\Contracts\Process\ProcessResult
*
* @throws \Illuminate\Process\Exceptions\ProcessTimedOutException
* @throws \RuntimeException
*/
public function run(array|string $command = null, callable $output = null)
{
$this->command = $command ?: $this->command;
try {
$process = $this->toSymfonyProcess($command);
if ($fake = $this->fakeFor($command = $process->getCommandline())) {
return tap($this->resolveSynchronousFake($command, $fake), function ($result) {
$this->factory->recordIfRecording($this, $result);
});
} elseif ($this->factory->isRecording() && $this->factory->preventingStrayProcesses()) {
throw new RuntimeException('Attempted process ['.$command.'] without a matching fake.');
}
return new ProcessResult(tap($process)->run($output));
} catch (SymfonyTimeoutException $e) {
throw new ProcessTimedOutException($e, new ProcessResult($process));
}
}
/**
* Start the process in the background.
*
* @param array<array-key, string>|string|null $command
* @param callable $output
* @return \Illuminate\Process\InvokedProcess
*/
public function start(array|string $command = null, callable $output = null)
{
$this->command = $command ?: $this->command;
$process = $this->toSymfonyProcess($command);
if ($fake = $this->fakeFor($command = $process->getCommandline())) {
return tap($this->resolveAsynchronousFake($command, $output, $fake), function (FakeInvokedProcess $process) {
$this->factory->recordIfRecording($this, $process->predictProcessResult());
});
} elseif ($this->factory->isRecording() && $this->factory->preventingStrayProcesses()) {
throw new RuntimeException('Attempted process ['.$command.'] without a matching fake.');
}
return new InvokedProcess(tap($process)->start($output));
}
/**
* Get a Symfony Process instance from the current pending command.
*
* @param array<array-key, string>|string|null $command
* @return \Symfony\Component\Process\Process
*/
protected function toSymfonyProcess(array|string|null $command)
{
$command = $command ?? $this->command;
$process = is_iterable($command)
? new Process($command, null, $this->environment)
: Process::fromShellCommandline((string) $command, null, $this->environment);
$process->setWorkingDirectory((string) ($this->path ?? getcwd()));
$process->setTimeout($this->timeout);
if ($this->idleTimeout) {
$process->setIdleTimeout($this->idleTimeout);
}
if ($this->input) {
$process->setInput($this->input);
}
if ($this->quietly) {
$process->disableOutput();
}
if ($this->tty) {
$process->setTty(true);
}
if (! empty($this->options)) {
$process->setOptions($this->options);
}
return $process;
}
/**
* Specify the fake process result handlers for the pending process.
*
* @param array $fakeHandlers
* @return $this
*/
public function withFakeHandlers(array $fakeHandlers)
{
$this->fakeHandlers = $fakeHandlers;
return $this;
}
/**
* Get the fake handler for the given command, if applicable.
*
* @param string $command
* @return \Closure|null
*/
protected function fakeFor(string $command)
{
return collect($this->fakeHandlers)
->first(fn ($handler, $pattern) => Str::is($pattern, $command));
}
/**
* Resolve the given fake handler for a synchronous process.
*
* @param string $command
* @param \Closure $fake
* @return mixed
*/
protected function resolveSynchronousFake(string $command, Closure $fake)
{
$result = $fake($this);
if (is_string($result) || is_array($result)) {
return (new FakeProcessResult(output: $result))->withCommand($command);
}
return match (true) {
$result instanceof ProcessResult => $result,
$result instanceof FakeProcessResult => $result->withCommand($command),
$result instanceof FakeProcessDescription => $result->toProcessResult($command),
$result instanceof FakeProcessSequence => $this->resolveSynchronousFake($command, fn () => $result()),
default => throw new LogicException('Unsupported synchronous process fake result provided.'),
};
}
/**
* Resolve the given fake handler for an asynchronous process.
*
* @param string $command
* @param callable|null $output
* @param \Closure $fake
* @return \Illuminate\Process\FakeInvokedProcess
*/
protected function resolveAsynchronousFake(string $command, ?callable $output, Closure $fake)
{
$result = $fake($this);
if (is_string($result) || is_array($result)) {
$result = new FakeProcessResult(output: $result);
}
if ($result instanceof ProcessResult) {
return (new FakeInvokedProcess(
$command,
(new FakeProcessDescription)
->replaceOutput($result->output())
->replaceErrorOutput($result->errorOutput())
->runsFor(iterations: 0)
->exitCode($result->exitCode())
))->withOutputHandler($output);
} elseif ($result instanceof FakeProcessResult) {
return (new FakeInvokedProcess(
$command,
(new FakeProcessDescription)
->replaceOutput($result->output())
->replaceErrorOutput($result->errorOutput())
->runsFor(iterations: 0)
->exitCode($result->exitCode())
))->withOutputHandler($output);
} elseif ($result instanceof FakeProcessDescription) {
return (new FakeInvokedProcess($command, $result))->withOutputHandler($output);
} elseif ($result instanceof FakeProcessSequence) {
return $this->resolveAsynchronousFake($command, $output, fn () => $result());
}
throw new LogicException('Unsupported asynchronous process fake result provided.');
}
}