Update to laravel 7

This commit is contained in:
KodeStar
2022-03-10 11:54:29 +00:00
parent 61a5a1a8b0
commit f9a19fce91
7170 changed files with 274189 additions and 283773 deletions

View File

@@ -1,3 +0,0 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -1,6 +1,32 @@
CHANGELOG
=========
5.2.0
-----
* added `Process::setOptions()` to set `Process` specific options
* added option `create_new_console` to allow a subprocess to continue
to run after the main script exited, both on Linux and on Windows
5.1.0
-----
* added `Process::getStartTime()` to retrieve the start time of the process as float
5.0.0
-----
* removed `Process::inheritEnvironmentVariables()`
* removed `PhpProcess::setPhpBinary()`
* `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell
* removed `Process::setCommandLine()`
4.4.0
-----
* deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited.
* added `Process::getLastOutputTime()` method
4.2.0
-----

View File

@@ -20,8 +20,8 @@ use Symfony\Component\Process\Process;
*/
class ProcessTimedOutException extends RuntimeException
{
const TYPE_GENERAL = 1;
const TYPE_IDLE = 2;
public const TYPE_GENERAL = 1;
public const TYPE_IDLE = 2;
private $process;
private $timeoutType;

View File

@@ -31,10 +31,8 @@ class ExecutableFinder
/**
* Adds new possible suffix to check for executable.
*
* @param string $suffix
*/
public function addSuffix($suffix)
public function addSuffix(string $suffix)
{
$this->suffixes[] = $suffix;
}
@@ -46,12 +44,12 @@ class ExecutableFinder
* @param string|null $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string|null The executable path or default value
* @return string|null
*/
public function find($name, $default = null, array $extraDirs = [])
public function find(string $name, string $default = null, array $extraDirs = [])
{
if (ini_get('open_basedir')) {
$searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$dirs = [];
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
@@ -65,7 +63,7 @@ class ExecutableFinder
}
} else {
$dirs = array_merge(
explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
@@ -73,7 +71,7 @@ class ExecutableFinder
$suffixes = [''];
if ('\\' === \DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
$suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {

View File

@@ -17,6 +17,8 @@ use Symfony\Component\Process\Exception\RuntimeException;
* Provides a way to continuously write to the input of a Process until the InputStream is closed.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @implements \IteratorAggregate<int, string>
*/
class InputStream implements \IteratorAggregate
{
@@ -45,7 +47,7 @@ class InputStream implements \IteratorAggregate
return;
}
if ($this->isClosed()) {
throw new RuntimeException(sprintf('%s is closed', static::class));
throw new RuntimeException(sprintf('"%s" is closed.', static::class));
}
$this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
}
@@ -66,6 +68,10 @@ class InputStream implements \IteratorAggregate
return !$this->open;
}
/**
* @return \Traversable<int, string>
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
$this->open = true;

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2019 Fabien Potencier
Copyright (c) 2004-2022 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -29,16 +29,14 @@ class PhpExecutableFinder
/**
* Finds The PHP executable.
*
* @param bool $includeArgs Whether or not include command arguments
*
* @return string|false The PHP executable path or false if it cannot be found
* @return string|false
*/
public function find($includeArgs = true)
public function find(bool $includeArgs = true)
{
if ($php = getenv('PHP_BINARY')) {
if (!is_executable($php)) {
$command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) {
if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
if (!is_executable($php)) {
return false;
}
@@ -54,8 +52,8 @@ class PhpExecutableFinder
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// PHP_BINARY return the current sapi executable
if (PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
return PHP_BINARY.$args;
if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) {
return \PHP_BINARY.$args;
}
if ($php = getenv('PHP_PATH')) {
@@ -72,11 +70,11 @@ class PhpExecutableFinder
}
}
if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
return $php;
}
$dirs = [PHP_BINDIR];
$dirs = [\PHP_BINDIR];
if ('\\' === \DIRECTORY_SEPARATOR) {
$dirs[] = 'C:\xampp\php\\';
}
@@ -87,7 +85,7 @@ class PhpExecutableFinder
/**
* Finds the PHP executable arguments.
*
* @return array The PHP executable arguments
* @return array
*/
public function findArguments()
{

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;
/**
@@ -50,15 +51,11 @@ class PhpProcess extends Process
}
/**
* Sets the path to the PHP binary to use.
*
* @deprecated since Symfony 4.2, use the $php argument of the constructor instead.
* {@inheritdoc}
*/
public function setPhpBinary($php)
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
$this->setCommandLine($php);
throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
}
/**

View File

@@ -47,17 +47,17 @@ abstract class AbstractPipes implements PipesInterface
public function close()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
if (\is_resource($pipe)) {
fclose($pipe);
}
}
$this->pipes = [];
}
/**
* Returns true if a system call has been interrupted.
*
* @return bool
*/
protected function hasSystemCallBeenInterrupted()
protected function hasSystemCallBeenInterrupted(): bool
{
$lastError = $this->lastError;
$this->lastError = null;
@@ -90,10 +90,10 @@ abstract class AbstractPipes implements PipesInterface
*
* @throws InvalidArgumentException When an input iterator yields a non supported value
*/
protected function write()
protected function write(): ?array
{
if (!isset($this->pipes[0])) {
return;
return null;
}
$input = $this->input;
@@ -105,7 +105,7 @@ abstract class AbstractPipes implements PipesInterface
} elseif (!isset($this->inputBuffer[0])) {
if (!\is_string($input)) {
if (!is_scalar($input)) {
throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', \get_class($this->input), \gettype($input)));
throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
}
$input = (string) $input;
}
@@ -122,7 +122,7 @@ abstract class AbstractPipes implements PipesInterface
// let's have a look if something changed in streams
if (false === @stream_select($r, $w, $e, 0, 0)) {
return;
return null;
}
foreach ($w as $stdin) {
@@ -135,7 +135,7 @@ abstract class AbstractPipes implements PipesInterface
}
if ($input) {
for (;;) {
while (true) {
$data = fread($input, self::CHUNK_SIZE);
if (!isset($data[0])) {
break;
@@ -166,12 +166,14 @@ abstract class AbstractPipes implements PipesInterface
} elseif (!$w) {
return [$this->pipes[0]];
}
return null;
}
/**
* @internal
*/
public function handleError($type, $msg)
public function handleError(int $type, string $msg)
{
$this->lastError = $msg;
}

View File

@@ -20,21 +20,19 @@ namespace Symfony\Component\Process\Pipes;
*/
interface PipesInterface
{
const CHUNK_SIZE = 16384;
public const CHUNK_SIZE = 16384;
/**
* Returns an array of descriptors for the use of proc_open.
*
* @return array
*/
public function getDescriptors();
public function getDescriptors(): array;
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return string[]
*/
public function getFiles();
public function getFiles(): array;
/**
* Reads data in file handles and pipes.
@@ -44,21 +42,17 @@ interface PipesInterface
*
* @return string[] An array of read data indexed by their fd
*/
public function readAndWrite($blocking, $close = false);
public function readAndWrite(bool $blocking, bool $close = false): array;
/**
* Returns if the current state has open file handles or pipes.
*
* @return bool
*/
public function areOpen();
public function areOpen(): bool;
/**
* Returns if pipes are able to read output.
*
* @return bool
*/
public function haveReadSupport();
public function haveReadSupport(): bool;
/**
* Closes file handles and pipes.

View File

@@ -35,6 +35,16 @@ class UnixPipes extends AbstractPipes
parent::__construct($input);
}
public function __sleep(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->close();
@@ -43,7 +53,7 @@ class UnixPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function getDescriptors()
public function getDescriptors(): array
{
if (!$this->haveReadSupport) {
$nullstream = fopen('/dev/null', 'c');
@@ -81,7 +91,7 @@ class UnixPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function getFiles()
public function getFiles(): array
{
return [];
}
@@ -89,7 +99,7 @@ class UnixPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
public function readAndWrite(bool $blocking, bool $close = false): array
{
$this->unblock();
$w = $this->write();
@@ -118,7 +128,7 @@ class UnixPipes extends AbstractPipes
$read[$type = array_search($pipe, $this->pipes, true)] = '';
do {
$data = fread($pipe, self::CHUNK_SIZE);
$data = @fread($pipe, self::CHUNK_SIZE);
$read[$type] .= $data;
} while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
@@ -138,7 +148,7 @@ class UnixPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function haveReadSupport()
public function haveReadSupport(): bool
{
return $this->haveReadSupport;
}
@@ -146,7 +156,7 @@ class UnixPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function areOpen()
public function areOpen(): bool
{
return (bool) $this->pipes;
}

View File

@@ -17,8 +17,8 @@ use Symfony\Component\Process\Process;
/**
* WindowsPipes implementation uses temporary files as handles.
*
* @see https://bugs.php.net/bug.php?id=51800
* @see https://bugs.php.net/bug.php?id=65650
* @see https://bugs.php.net/51800
* @see https://bugs.php.net/65650
*
* @author Romain Neutron <imprec@gmail.com>
*
@@ -43,7 +43,7 @@ class WindowsPipes extends AbstractPipes
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/bug.php?id=51800
// @see https://bugs.php.net/51800
$pipes = [
Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR,
@@ -56,20 +56,23 @@ class WindowsPipes extends AbstractPipes
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
if (!$h = fopen($file.'.lock', 'w')) {
if (file_exists($file.'.lock')) {
continue 2;
}
restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
}
if (!flock($h, LOCK_EX | LOCK_NB)) {
if (!flock($h, \LOCK_EX | \LOCK_NB)) {
continue 2;
}
if (isset($this->lockHandles[$pipe])) {
flock($this->lockHandles[$pipe], LOCK_UN);
flock($this->lockHandles[$pipe], \LOCK_UN);
fclose($this->lockHandles[$pipe]);
}
$this->lockHandles[$pipe] = $h;
if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
flock($this->lockHandles[$pipe], LOCK_UN);
if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
flock($this->lockHandles[$pipe], \LOCK_UN);
fclose($this->lockHandles[$pipe]);
unset($this->lockHandles[$pipe]);
continue 2;
@@ -85,6 +88,16 @@ class WindowsPipes extends AbstractPipes
parent::__construct($input);
}
public function __sleep(): array
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->close();
@@ -93,7 +106,7 @@ class WindowsPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function getDescriptors()
public function getDescriptors(): array
{
if (!$this->haveReadSupport) {
$nullstream = fopen('NUL', 'c');
@@ -105,8 +118,8 @@ class WindowsPipes extends AbstractPipes
];
}
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
// So we redirect output within the commandline and pass the nul device to the process
return [
['pipe', 'r'],
@@ -118,7 +131,7 @@ class WindowsPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function getFiles()
public function getFiles(): array
{
return $this->files;
}
@@ -126,7 +139,7 @@ class WindowsPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function readAndWrite($blocking, $close = false)
public function readAndWrite(bool $blocking, bool $close = false): array
{
$this->unblock();
$w = $this->write();
@@ -149,7 +162,7 @@ class WindowsPipes extends AbstractPipes
if ($close) {
ftruncate($fileHandle, 0);
fclose($fileHandle);
flock($this->lockHandles[$type], LOCK_UN);
flock($this->lockHandles[$type], \LOCK_UN);
fclose($this->lockHandles[$type]);
unset($this->fileHandles[$type], $this->lockHandles[$type]);
}
@@ -161,7 +174,7 @@ class WindowsPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function haveReadSupport()
public function haveReadSupport(): bool
{
return $this->haveReadSupport;
}
@@ -169,7 +182,7 @@ class WindowsPipes extends AbstractPipes
/**
* {@inheritdoc}
*/
public function areOpen()
public function areOpen(): bool
{
return $this->pipes && $this->fileHandles;
}
@@ -183,7 +196,7 @@ class WindowsPipes extends AbstractPipes
foreach ($this->fileHandles as $type => $handle) {
ftruncate($handle, 0);
fclose($handle);
flock($this->lockHandles[$type], LOCK_UN);
flock($this->lockHandles[$type], \LOCK_UN);
fclose($this->lockHandles[$type]);
}
$this->fileHandles = $this->lockHandles = [];

View File

@@ -27,27 +27,29 @@ use Symfony\Component\Process\Pipes\WindowsPipes;
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
*
* @implements \IteratorAggregate<string, string>
*/
class Process implements \IteratorAggregate
{
const ERR = 'err';
const OUT = 'out';
public const ERR = 'err';
public const OUT = 'out';
const STATUS_READY = 'ready';
const STATUS_STARTED = 'started';
const STATUS_TERMINATED = 'terminated';
public const STATUS_READY = 'ready';
public const STATUS_STARTED = 'started';
public const STATUS_TERMINATED = 'terminated';
const STDIN = 0;
const STDOUT = 1;
const STDERR = 2;
public const STDIN = 0;
public const STDOUT = 1;
public const STDERR = 2;
// Timeout Precision in seconds.
const TIMEOUT_PRECISION = 0.2;
public const TIMEOUT_PRECISION = 0.2;
const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
private $callback;
private $hasCallback = false;
@@ -69,8 +71,9 @@ class Process implements \IteratorAggregate
private $status = self::STATUS_READY;
private $incrementalOutputOffset = 0;
private $incrementalErrorOutputOffset = 0;
private $tty;
private $tty = false;
private $pty;
private $options = ['suppress_errors' => true, 'bypass_shell' => true];
private $useFileHandles = false;
/** @var PipesInterface */
@@ -132,28 +135,24 @@ class Process implements \IteratorAggregate
* @param array $command The command to run and its arguments listed as separate entries
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @throws RuntimeException When proc_open is not installed
* @throws LogicException When proc_open is not installed
*/
public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
if (!\function_exists('proc_open')) {
throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
}
if (!\is_array($command)) {
@trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), E_USER_DEPRECATED);
}
$this->commandline = $command;
$this->cwd = $cwd;
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
// on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
// @see : https://bugs.php.net/bug.php?id=51800
// @see : https://bugs.php.net/bug.php?id=50524
// @see : https://bugs.php.net/51800
// @see : https://bugs.php.net/50524
if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
$this->cwd = getcwd();
}
@@ -177,16 +176,18 @@ class Process implements \IteratorAggregate
* In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
* This will save escaping values, which is not portable nor secure anyway:
*
* $process = Process::fromShellCommandline('my_command "$MY_VAR"');
* $process = Process::fromShellCommandline('my_command "${:MY_VAR}"');
* $process->run(null, ['MY_VAR' => $theValue]);
*
* @param string $command The command line to pass to the shell of the OS
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @throws RuntimeException When proc_open is not installed
* @return static
*
* @throws LogicException When proc_open is not installed
*/
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
@@ -196,9 +197,26 @@ class Process implements \IteratorAggregate
return $process;
}
/**
* @return array
*/
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->stop(0);
if ($this->options['create_new_console'] ?? false) {
$this->processPipes->close();
} else {
$this->stop(0);
}
}
public function __clone()
@@ -218,13 +236,14 @@ class Process implements \IteratorAggregate
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array $env An array of additional env vars to set when running the process
*
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*
* @final
*/
@@ -241,16 +260,13 @@ class Process implements \IteratorAggregate
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param callable|null $callback
* @param array $env An array of additional env vars to set when running the process
*
* @return self
* @return $this
*
* @throws ProcessFailedException if the process didn't terminate successfully
*
* @final
*/
public function mustRun(callable $callback = null, array $env = [])
public function mustRun(callable $callback = null, array $env = []): self
{
if (0 !== $this->run($callback, $env)) {
throw new ProcessFailedException($this);
@@ -273,7 +289,6 @@ class Process implements \IteratorAggregate
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array $env An array of additional env vars to set when running the process
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
@@ -282,7 +297,7 @@ class Process implements \IteratorAggregate
public function start(callable $callback = null, array $env = [])
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
throw new RuntimeException('Process is already running.');
}
$this->resetProcessData();
@@ -291,6 +306,12 @@ class Process implements \IteratorAggregate
$this->hasCallback = null !== $callback;
$descriptors = $this->getDescriptors();
if ($this->env) {
$env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;
}
$env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();
if (\is_array($commandline = $this->commandline)) {
$commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
@@ -298,17 +319,11 @@ class Process implements \IteratorAggregate
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$commandline;
}
} else {
$commandline = $this->replacePlaceholders($commandline, $env);
}
if ($this->env) {
$env += $this->env;
}
$env += $this->getDefaultEnv();
$options = ['suppress_errors' => true];
if ('\\' === \DIRECTORY_SEPARATOR) {
$options['bypass_shell'] = true;
$commandline = $this->prepareWindowsCommandLine($commandline, $env);
} elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
@@ -325,7 +340,7 @@ class Process implements \IteratorAggregate
$envPairs = [];
foreach ($env as $k => $v) {
if (false !== $v) {
if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) {
$envPairs[] = $k.'='.$v;
}
}
@@ -334,7 +349,7 @@ class Process implements \IteratorAggregate
throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
}
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
$this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
if (!\is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
@@ -360,9 +375,8 @@ class Process implements \IteratorAggregate
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param array $env An array of additional env vars to set when running the process
*
* @return $this
* @return static
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
@@ -371,10 +385,10 @@ class Process implements \IteratorAggregate
*
* @final
*/
public function restart(callable $callback = null, array $env = [])
public function restart(callable $callback = null, array $env = []): self
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
throw new RuntimeException('Process is already running.');
}
$process = clone $this;
@@ -394,9 +408,9 @@ class Process implements \IteratorAggregate
*
* @return int The exitcode of the process
*
* @throws RuntimeException When process timed out
* @throws RuntimeException When process stopped after receiving signal
* @throws LogicException When process is not yet started
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
public function wait(callable $callback = null)
{
@@ -407,7 +421,7 @@ class Process implements \IteratorAggregate
if (null !== $callback) {
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
}
$this->callback = $this->buildCallback($callback);
}
@@ -437,8 +451,9 @@ class Process implements \IteratorAggregate
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @throws RuntimeException When process timed out
* @throws LogicException When process is not yet started
* @throws RuntimeException When process timed out
* @throws LogicException When process is not yet started
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function waitUntil(callable $callback): bool
{
@@ -447,7 +462,7 @@ class Process implements \IteratorAggregate
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
}
$callback = $this->buildCallback($callback);
@@ -488,7 +503,7 @@ class Process implements \IteratorAggregate
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
*
* @return $this
*
@@ -496,7 +511,7 @@ class Process implements \IteratorAggregate
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
public function signal($signal)
public function signal(int $signal)
{
$this->doSignal($signal, true);
@@ -517,7 +532,7 @@ class Process implements \IteratorAggregate
throw new RuntimeException('Disabling output while the process is running is not possible.');
}
if (null !== $this->idleTimeout) {
throw new LogicException('Output can not be disabled while an idle timeout is set.');
throw new LogicException('Output cannot be disabled while an idle timeout is set.');
}
$this->outputDisabled = true;
@@ -556,7 +571,7 @@ class Process implements \IteratorAggregate
/**
* Returns the current output of the process (STDOUT).
*
* @return string The process output
* @return string
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
@@ -578,7 +593,7 @@ class Process implements \IteratorAggregate
* In comparison with the getOutput method which always return the whole
* output, this one returns the new output since the last call.
*
* @return string The process output since the last call
* @return string
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
@@ -605,9 +620,10 @@ class Process implements \IteratorAggregate
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*
* @return \Generator
* @return \Generator<string, string>
*/
public function getIterator($flags = 0)
#[\ReturnTypeWillChange]
public function getIterator(int $flags = 0)
{
$this->readPipesForOutput(__FUNCTION__, false);
@@ -671,7 +687,7 @@ class Process implements \IteratorAggregate
/**
* Returns the current error output of the process (STDERR).
*
* @return string The process error output
* @return string
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
@@ -694,7 +710,7 @@ class Process implements \IteratorAggregate
* whole error output, this one returns the new error output since the last
* call.
*
* @return string The process error output since the last call
* @return string
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
@@ -753,16 +769,16 @@ class Process implements \IteratorAggregate
public function getExitCodeText()
{
if (null === $exitcode = $this->getExitCode()) {
return;
return null;
}
return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
return self::$exitCodes[$exitcode] ?? 'Unknown error';
}
/**
* Checks if the process ended successfully.
*
* @return bool true if the process ended successfully, false otherwise
* @return bool
*/
public function isSuccessful()
{
@@ -800,7 +816,7 @@ class Process implements \IteratorAggregate
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');
}
return $this->processInformation['termsig'];
@@ -841,7 +857,7 @@ class Process implements \IteratorAggregate
/**
* Checks if the process is currently running.
*
* @return bool true if the process is currently running, false otherwise
* @return bool
*/
public function isRunning()
{
@@ -857,7 +873,7 @@ class Process implements \IteratorAggregate
/**
* Checks if the process has been started with no regard to the current state.
*
* @return bool true if status is ready, false otherwise
* @return bool
*/
public function isStarted()
{
@@ -867,7 +883,7 @@ class Process implements \IteratorAggregate
/**
* Checks if the process is terminated.
*
* @return bool true if process is terminated, false otherwise
* @return bool
*/
public function isTerminated()
{
@@ -881,7 +897,7 @@ class Process implements \IteratorAggregate
*
* The status is one of: ready, started, terminated.
*
* @return string The current process status
* @return string
*/
public function getStatus()
{
@@ -896,9 +912,9 @@ class Process implements \IteratorAggregate
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
*
* @return int The exit-code of the process
* @return int|null The exit-code of the process or null if it's not running
*/
public function stop($timeout = 10, $signal = null)
public function stop(float $timeout = 10, int $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
@@ -936,7 +952,7 @@ class Process implements \IteratorAggregate
{
$this->lastOutputTime = microtime(true);
fseek($this->stdout, 0, SEEK_END);
fseek($this->stdout, 0, \SEEK_END);
fwrite($this->stdout, $line);
fseek($this->stdout, $this->incrementalOutputOffset);
}
@@ -950,15 +966,23 @@ class Process implements \IteratorAggregate
{
$this->lastOutputTime = microtime(true);
fseek($this->stderr, 0, SEEK_END);
fseek($this->stderr, 0, \SEEK_END);
fwrite($this->stderr, $line);
fseek($this->stderr, $this->incrementalErrorOutputOffset);
}
/**
* Gets the last output time in seconds.
*/
public function getLastOutputTime(): ?float
{
return $this->lastOutputTime;
}
/**
* Gets the command line to be executed.
*
* @return string The command to execute
* @return string
*/
public function getCommandLine()
{
@@ -966,27 +990,9 @@ class Process implements \IteratorAggregate
}
/**
* Sets the command line to be executed.
* Gets the process timeout in seconds (max. runtime).
*
* @param string|array $commandline The command to execute
*
* @return self The current Process instance
*
* @deprecated since Symfony 4.2.
*/
public function setCommandLine($commandline)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
$this->commandline = $commandline;
return $this;
}
/**
* Gets the process timeout (max. runtime).
*
* @return float|null The timeout in seconds or null if it's disabled
* @return float|null
*/
public function getTimeout()
{
@@ -994,9 +1000,9 @@ class Process implements \IteratorAggregate
}
/**
* Gets the process idle timeout (max. time since last output).
* Gets the process idle timeout in seconds (max. time since last output).
*
* @return float|null The timeout in seconds or null if it's disabled
* @return float|null
*/
public function getIdleTimeout()
{
@@ -1004,17 +1010,15 @@ class Process implements \IteratorAggregate
}
/**
* Sets the process timeout (max. runtime).
* Sets the process timeout (max. runtime) in seconds.
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
* @return $this
*
* @throws InvalidArgumentException if the timeout is negative
*/
public function setTimeout($timeout)
public function setTimeout(?float $timeout)
{
$this->timeout = $this->validateTimeout($timeout);
@@ -1022,21 +1026,19 @@ class Process implements \IteratorAggregate
}
/**
* Sets the process idle timeout (max. time since last output).
* Sets the process idle timeout (max. time since last output) in seconds.
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return self The current Process instance
* @return $this
*
* @throws LogicException if the output is disabled
* @throws InvalidArgumentException if the timeout is negative
*/
public function setIdleTimeout($timeout)
public function setIdleTimeout(?float $timeout)
{
if (null !== $timeout && $this->outputDisabled) {
throw new LogicException('Idle timeout can not be set while the output is disabled.');
throw new LogicException('Idle timeout cannot be set while the output is disabled.');
}
$this->idleTimeout = $this->validateTimeout($timeout);
@@ -1047,13 +1049,11 @@ class Process implements \IteratorAggregate
/**
* Enables or disables the TTY mode.
*
* @param bool $tty True to enabled and false to disable
*
* @return self The current Process instance
* @return $this
*
* @throws RuntimeException In case the TTY mode is not supported
*/
public function setTty($tty)
public function setTty(bool $tty)
{
if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
throw new RuntimeException('TTY mode is not supported on Windows platform.');
@@ -1063,7 +1063,7 @@ class Process implements \IteratorAggregate
throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
}
$this->tty = (bool) $tty;
$this->tty = $tty;
return $this;
}
@@ -1071,7 +1071,7 @@ class Process implements \IteratorAggregate
/**
* Checks if the TTY mode is enabled.
*
* @return bool true if the TTY mode is enabled, false otherwise
* @return bool
*/
public function isTty()
{
@@ -1081,13 +1081,11 @@ class Process implements \IteratorAggregate
/**
* Sets PTY mode.
*
* @param bool $bool
*
* @return self
* @return $this
*/
public function setPty($bool)
public function setPty(bool $bool)
{
$this->pty = (bool) $bool;
$this->pty = $bool;
return $this;
}
@@ -1105,7 +1103,7 @@ class Process implements \IteratorAggregate
/**
* Gets the working directory.
*
* @return string|null The current working directory or null on failure
* @return string|null
*/
public function getWorkingDirectory()
{
@@ -1121,11 +1119,9 @@ class Process implements \IteratorAggregate
/**
* Sets the current working directory.
*
* @param string $cwd The new working directory
*
* @return self The current Process instance
* @return $this
*/
public function setWorkingDirectory($cwd)
public function setWorkingDirectory(string $cwd)
{
$this->cwd = $cwd;
@@ -1135,7 +1131,7 @@ class Process implements \IteratorAggregate
/**
* Gets the environment variables.
*
* @return array The current environment variables
* @return array
*/
public function getEnv()
{
@@ -1145,25 +1141,12 @@ class Process implements \IteratorAggregate
/**
* Sets the environment variables.
*
* Each environment variable value should be a string.
* If it is an array, the variable is ignored.
* If it is false or null, it will be removed when
* env vars are otherwise inherited.
* @param array<string|\Stringable> $env The new environment variables
*
* That happens in PHP when 'argv' is registered into
* the $_ENV array for instance.
*
* @param array $env The new environment variables
*
* @return self The current Process instance
* @return $this
*/
public function setEnv(array $env)
{
// Process can not handle env values that are arrays
$env = array_filter($env, function ($value) {
return !\is_array($value);
});
$this->env = $env;
return $this;
@@ -1172,7 +1155,7 @@ class Process implements \IteratorAggregate
/**
* Gets the Process input.
*
* @return resource|string|\Iterator|null The Process input
* @return resource|string|\Iterator|null
*/
public function getInput()
{
@@ -1186,14 +1169,14 @@ class Process implements \IteratorAggregate
*
* @param string|int|float|bool|resource|\Traversable|null $input The content
*
* @return self The current Process instance
* @return $this
*
* @throws LogicException In case the process is running
*/
public function setInput($input)
{
if ($this->isRunning()) {
throw new LogicException('Input can not be set while the process is running.');
throw new LogicException('Input cannot be set while the process is running.');
}
$this->input = ProcessUtils::validateInput(__METHOD__, $input);
@@ -1201,22 +1184,6 @@ class Process implements \IteratorAggregate
return $this;
}
/**
* Sets whether environment variables will be inherited or not.
*
* @param bool $inheritEnv
*
* @return self The current Process instance
*/
public function inheritEnvironmentVariables($inheritEnv = true)
{
if (!$inheritEnv) {
throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
}
return $this;
}
/**
* Performs a check between the timeout definition and the time the process started.
*
@@ -1244,6 +1211,44 @@ class Process implements \IteratorAggregate
}
}
/**
* @throws LogicException in case process is not started
*/
public function getStartTime(): float
{
if (!$this->isStarted()) {
throw new LogicException('Start time is only available after process start.');
}
return $this->starttime;
}
/**
* Defines options to pass to the underlying proc_open().
*
* @see https://php.net/proc_open for the options supported by PHP.
*
* Enabling the "create_new_console" option allows a subprocess to continue
* to run after the main process exited, on both Windows and *nix
*/
public function setOptions(array $options)
{
if ($this->isRunning()) {
throw new RuntimeException('Setting options while the process is running is not possible.');
}
$defaultOptions = $this->options;
$existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];
foreach ($options as $key => $value) {
if (!\in_array($key, $existingOptions)) {
$this->options = $defaultOptions;
throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
}
$this->options[$key] = $value;
}
}
/**
* Returns whether TTY is supported on the current operating system.
*/
@@ -1303,30 +1308,26 @@ class Process implements \IteratorAggregate
*
* @param callable|null $callback The user defined PHP callback
*
* @return \Closure A PHP closure
* @return \Closure
*/
protected function buildCallback(callable $callback = null)
{
if ($this->outputDisabled) {
return function ($type, $data) use ($callback) {
if (null !== $callback) {
return $callback($type, $data);
}
return function ($type, $data) use ($callback): bool {
return null !== $callback && $callback($type, $data);
};
}
$out = self::OUT;
return function ($type, $data) use ($callback, $out) {
return function ($type, $data) use ($callback, $out): bool {
if ($out == $type) {
$this->addOutput($data);
} else {
$this->addErrorOutput($data);
}
if (null !== $callback) {
return $callback($type, $data);
}
return null !== $callback && $callback($type, $data);
};
}
@@ -1335,7 +1336,7 @@ class Process implements \IteratorAggregate
*
* @param bool $blocking Whether to use a blocking read call
*/
protected function updateStatus($blocking)
protected function updateStatus(bool $blocking)
{
if (self::STATUS_STARTED !== $this->status) {
return;
@@ -1371,9 +1372,9 @@ class Process implements \IteratorAggregate
}
ob_start();
phpinfo(INFO_GENERAL);
phpinfo(\INFO_GENERAL);
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');
}
/**
@@ -1475,8 +1476,8 @@ class Process implements \IteratorAggregate
$this->exitcode = null;
$this->fallbackStatus = [];
$this->processInformation = null;
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
$this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY;
@@ -1487,11 +1488,9 @@ class Process implements \IteratorAggregate
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
* @param bool $throwException Whether to throw exception in case signal failed
*
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
@@ -1500,7 +1499,7 @@ class Process implements \IteratorAggregate
{
if (null === $pid = $this->getPid()) {
if ($throwException) {
throw new LogicException('Can not send signal on a non running process.');
throw new LogicException('Cannot send signal on a non running process.');
}
return false;
@@ -1540,7 +1539,7 @@ class Process implements \IteratorAggregate
return true;
}
private function prepareWindowsCommandLine(string $cmd, array &$env)
private function prepareWindowsCommandLine(string $cmd, array &$env): string
{
$uid = uniqid('', true);
$varCount = 0;
@@ -1560,7 +1559,7 @@ class Process implements \IteratorAggregate
if (isset($varCache[$m[0]])) {
return $varCache[$m[0]];
}
if (false !== strpos($value = $m[1], "\0")) {
if (str_contains($value = $m[1], "\0")) {
$value = str_replace("\0", '?', $value);
}
if (false === strpbrk($value, "\"%!\n")) {
@@ -1594,7 +1593,7 @@ class Process implements \IteratorAggregate
private function requireProcessIsStarted(string $functionName)
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
}
}
@@ -1606,7 +1605,7 @@ class Process implements \IteratorAggregate
private function requireProcessIsTerminated(string $functionName)
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
}
}
@@ -1621,7 +1620,7 @@ class Process implements \IteratorAggregate
if ('\\' !== \DIRECTORY_SEPARATOR) {
return "'".str_replace("'", "'\\''", $argument)."'";
}
if (false !== strpos($argument, "\0")) {
if (str_contains($argument, "\0")) {
$argument = str_replace("\0", '?', $argument);
}
if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
@@ -1632,22 +1631,22 @@ class Process implements \IteratorAggregate
return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
}
private function getDefaultEnv()
private function replacePlaceholders(string $commandline, array $env)
{
$env = [];
foreach ($_SERVER as $k => $v) {
if (\is_string($v) && false !== $v = getenv($k)) {
$env[$k] = $v;
return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
}
}
foreach ($_ENV as $k => $v) {
if (\is_string($v)) {
$env[$k] = $v;
}
}
return $this->escapeArgument($env[$matches[1]]);
}, $commandline);
}
return $env;
private function getDefaultEnv(): array
{
$env = getenv();
$env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;
return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);
}
}

View File

@@ -35,11 +35,11 @@ class ProcessUtils
* @param string $caller The name of method call that validates the input
* @param mixed $input The input to validate
*
* @return mixed The validated input
* @return mixed
*
* @throws InvalidArgumentException In case the input is not valid
*/
public static function validateInput($caller, $input)
public static function validateInput(string $caller, $input)
{
if (null !== $input) {
if (\is_resource($input)) {
@@ -61,7 +61,7 @@ class ProcessUtils
return new \IteratorIterator($input);
}
throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
}
return $input;

View File

@@ -3,11 +3,26 @@ Process Component
The Process component executes commands in sub-processes.
Sponsor
-------
The Process component for Symfony 5.4/6.0 is [backed][1] by [SensioLabs][2].
As the creator of Symfony, SensioLabs supports companies using Symfony, with an
offering encompassing consultancy, expertise, services, training, and technical
assistance to ensure the success of web application development projects.
Help Symfony by [sponsoring][3] its development!
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/process.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/components/process.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
[1]: https://symfony.com/backers
[2]: https://sensiolabs.com
[3]: https://symfony.com/sponsor

View File

@@ -1,36 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
require \dirname(__DIR__).'/vendor/autoload.php';
list('e' => $php) = getopt('e:') + ['e' => 'php'];
try {
$process = new Process("exec $php -r \"echo 'ready'; trigger_error('error', E_USER_ERROR);\"");
$process->start();
$process->setTimeout(0.5);
while (false === strpos($process->getOutput(), 'ready')) {
usleep(1000);
}
$process->signal(SIGSTOP);
$process->wait();
return $process->getExitCode();
} catch (ProcessTimedOutException $t) {
echo $t->getMessage().PHP_EOL;
return 1;
}

View File

@@ -1,178 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\ExecutableFinder;
/**
* @author Chris Smith <chris@cs278.org>
*/
class ExecutableFinderTest extends TestCase
{
private $path;
protected function tearDown()
{
if ($this->path) {
// Restore path if it was changed.
putenv('PATH='.$this->path);
}
}
private function setPath($path)
{
$this->path = getenv('PATH');
putenv('PATH='.$path);
}
public function testFind()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->setPath(\dirname(PHP_BINARY));
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName());
$this->assertSamePath(PHP_BINARY, $result);
}
public function testFindWithDefault()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$expected = 'defaultValue';
$this->setPath('');
$finder = new ExecutableFinder();
$result = $finder->find('foo', $expected);
$this->assertEquals($expected, $result);
}
public function testFindWithNullAsDefault()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->setPath('');
$finder = new ExecutableFinder();
$result = $finder->find('foo');
$this->assertNull($result);
}
public function testFindWithExtraDirs()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->setPath('');
$extraDirs = [\dirname(PHP_BINARY)];
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName(), null, $extraDirs);
$this->assertSamePath(PHP_BINARY, $result);
}
public function testFindWithOpenBaseDir()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Cannot run test on windows');
}
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$this->iniSet('open_basedir', \dirname(PHP_BINARY).PATH_SEPARATOR.'/');
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName());
$this->assertSamePath(PHP_BINARY, $result);
}
public function testFindProcessInOpenBasedir()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Cannot run test on windows');
}
$this->setPath('');
$this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName(), false);
$this->assertSamePath(PHP_BINARY, $result);
}
/**
* @requires PHP 5.4
*/
public function testFindBatchExecutableOnWindows()
{
if (ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
if ('\\' !== \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('Can be only tested on windows');
}
$target = tempnam(sys_get_temp_dir(), 'example-windows-executable');
touch($target);
touch($target.'.BAT');
$this->assertFalse(is_executable($target));
$this->setPath(sys_get_temp_dir());
$finder = new ExecutableFinder();
$result = $finder->find(basename($target), false);
unlink($target);
unlink($target.'.BAT');
$this->assertSamePath($target.'.BAT', $result);
}
private function assertSamePath($expected, $tested)
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->assertEquals(strtolower($expected), strtolower($tested));
} else {
$this->assertEquals($expected, $tested);
}
}
private function getPhpBinaryName()
{
return basename(PHP_BINARY, '\\' === \DIRECTORY_SEPARATOR ? '.exe' : '');
}
}

View File

@@ -1,25 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$outputs = [
'First iteration output',
'Second iteration output',
'One more iteration output',
'This took more time',
];
$iterationTime = 10000;
foreach ($outputs as $output) {
usleep($iterationTime);
$iterationTime *= 10;
echo $output."\n";
}

View File

@@ -1,47 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds.
*
* @args duration Run this script with a custom duration
*
* @example `php NonStopableProcess.php 42` will run the script for 42 seconds
*/
function handleSignal($signal)
{
switch ($signal) {
case SIGTERM:
$name = 'SIGTERM';
break;
case SIGINT:
$name = 'SIGINT';
break;
default:
$name = $signal.' (unknown)';
break;
}
echo "signal $name\n";
}
pcntl_signal(SIGTERM, 'handleSignal');
pcntl_signal(SIGINT, 'handleSignal');
echo 'received ';
$duration = isset($argv[1]) ? (int) $argv[1] : 3;
$start = microtime(true);
while ($duration > (microtime(true) - $start)) {
usleep(10000);
pcntl_signal_dispatch();
}

View File

@@ -1,49 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\PhpExecutableFinder;
/**
* @author Robert Schönthal <seroscho@googlemail.com>
*/
class PhpExecutableFinderTest extends TestCase
{
/**
* tests find() with the constant PHP_BINARY.
*/
public function testFind()
{
$f = new PhpExecutableFinder();
$current = PHP_BINARY;
$args = 'phpdbg' === \PHP_SAPI ? ' -qrr' : '';
$this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP');
$this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
}
/**
* tests find() with the env var PHP_PATH.
*/
public function testFindArguments()
{
$f = new PhpExecutableFinder();
if ('phpdbg' === \PHP_SAPI) {
$this->assertEquals($f->findArguments(), ['-qrr'], '::findArguments() returns phpdbg arguments');
} else {
$this->assertEquals($f->findArguments(), [], '::findArguments() returns no arguments');
}
}
}

View File

@@ -1,63 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\PhpProcess;
class PhpProcessTest extends TestCase
{
public function testNonBlockingWorks()
{
$expected = 'hello world!';
$process = new PhpProcess(<<<PHP
<?php echo '$expected';
PHP
);
$process->start();
$process->wait();
$this->assertEquals($expected, $process->getOutput());
}
public function testCommandLine()
{
$process = new PhpProcess(<<<'PHP'
<?php echo phpversion().PHP_SAPI;
PHP
);
$commandLine = $process->getCommandLine();
$process->start();
$this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start');
$process->wait();
$this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait');
$this->assertSame(PHP_VERSION.\PHP_SAPI, $process->getOutput());
}
public function testPassingPhpExplicitly()
{
$finder = new PhpExecutableFinder();
$php = array_merge([$finder->find(false)], $finder->findArguments());
$expected = 'hello world!';
$script = <<<PHP
<?php echo '$expected';
PHP;
$process = new PhpProcess($script, null, null, 60, $php);
$process->run();
$this->assertEquals($expected, $process->getOutput());
}
}

View File

@@ -1,72 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
define('ERR_SELECT_FAILED', 1);
define('ERR_TIMEOUT', 2);
define('ERR_READ_FAILED', 3);
define('ERR_WRITE_FAILED', 4);
$read = [STDIN];
$write = [STDOUT, STDERR];
stream_set_blocking(STDIN, 0);
stream_set_blocking(STDOUT, 0);
stream_set_blocking(STDERR, 0);
$out = $err = '';
while ($read || $write) {
$r = $read;
$w = $write;
$e = null;
$n = stream_select($r, $w, $e, 5);
if (false === $n) {
die(ERR_SELECT_FAILED);
} elseif ($n < 1) {
die(ERR_TIMEOUT);
}
if (in_array(STDOUT, $w) && strlen($out) > 0) {
$written = fwrite(STDOUT, (string) $out, 32768);
if (false === $written) {
die(ERR_WRITE_FAILED);
}
$out = (string) substr($out, $written);
}
if (null === $read && '' === $out) {
$write = array_diff($write, [STDOUT]);
}
if (in_array(STDERR, $w) && strlen($err) > 0) {
$written = fwrite(STDERR, (string) $err, 32768);
if (false === $written) {
die(ERR_WRITE_FAILED);
}
$err = (string) substr($err, $written);
}
if (null === $read && '' === $err) {
$write = array_diff($write, [STDERR]);
}
if ($r) {
$str = fread(STDIN, 32768);
if (false !== $str) {
$out .= $str;
$err .= $str;
}
if (false === $str || feof(STDIN)) {
$read = null;
if (!feof(STDIN)) {
die(ERR_READ_FAILED);
}
}
}
}

View File

@@ -1,137 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Process\Exception\ProcessFailedException;
/**
* @author Sebastian Marek <proofek@gmail.com>
*/
class ProcessFailedExceptionTest extends TestCase
{
/**
* tests ProcessFailedException throws exception if the process was successful.
*/
public function testProcessFailedExceptionThrowsException()
{
$process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful'])->setConstructorArgs([['php']])->getMock();
$process->expects($this->once())
->method('isSuccessful')
->willReturn(true);
if (method_exists($this, 'expectException')) {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Expected a failed process, but the given process was successful.');
} else {
$this->setExpectedException(\InvalidArgumentException::class, 'Expected a failed process, but the given process was successful.');
}
new ProcessFailedException($process);
}
/**
* tests ProcessFailedException uses information from process output
* to generate exception message.
*/
public function testProcessFailedExceptionPopulatesInformationFromProcessOutput()
{
$cmd = 'php';
$exitCode = 1;
$exitText = 'General error';
$output = 'Command output';
$errorOutput = 'FATAL: Unexpected error';
$workingDirectory = getcwd();
$process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
$process->expects($this->once())
->method('isSuccessful')
->willReturn(false);
$process->expects($this->once())
->method('getOutput')
->willReturn($output);
$process->expects($this->once())
->method('getErrorOutput')
->willReturn($errorOutput);
$process->expects($this->once())
->method('getExitCode')
->willReturn($exitCode);
$process->expects($this->once())
->method('getExitCodeText')
->willReturn($exitText);
$process->expects($this->once())
->method('isOutputDisabled')
->willReturn(false);
$process->expects($this->once())
->method('getWorkingDirectory')
->willReturn($workingDirectory);
$exception = new ProcessFailedException($process);
$this->assertEquals(
"The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}",
str_replace("'php'", 'php', $exception->getMessage())
);
}
/**
* Tests that ProcessFailedException does not extract information from
* process output if it was previously disabled.
*/
public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput()
{
$cmd = 'php';
$exitCode = 1;
$exitText = 'General error';
$workingDirectory = getcwd();
$process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(['isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'])->setConstructorArgs([[$cmd]])->getMock();
$process->expects($this->once())
->method('isSuccessful')
->willReturn(false);
$process->expects($this->never())
->method('getOutput');
$process->expects($this->never())
->method('getErrorOutput');
$process->expects($this->once())
->method('getExitCode')
->willReturn($exitCode);
$process->expects($this->once())
->method('getExitCodeText')
->willReturn($exitText);
$process->expects($this->once())
->method('isOutputDisabled')
->willReturn(true);
$process->expects($this->once())
->method('getWorkingDirectory')
->willReturn($workingDirectory);
$exception = new ProcessFailedException($process);
$this->assertEquals(
"The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
str_replace("'php'", 'php', $exception->getMessage())
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; });
echo 'Caught ';
$n = 0;
while ($n++ < 400) {
usleep(10000);
pcntl_signal_dispatch();
}

View File

@@ -1,7 +1,7 @@
{
"name": "symfony/process",
"type": "library",
"description": "Symfony Process Component",
"description": "Executes commands in sub-processes",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
@@ -16,7 +16,8 @@
}
],
"require": {
"php": "^7.1.3"
"php": ">=7.2.5",
"symfony/polyfill-php80": "^1.16"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Process\\": "" },
@@ -24,10 +25,5 @@
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.3-dev"
}
}
"minimum-stability": "dev"
}

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony Process Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>