summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Pugh2018-08-14 16:13:45 (GMT)
committerJon Pugh2018-08-14 16:13:45 (GMT)
commit010c8440dcb4fc5e725a8f1ee7a45583e439dafd (patch)
treeacecb3d2471ad263995ec358ae57fd197938a75f
parentea005958ec248bf1b19f0d1f5ef44eb91a03a882 (diff)
parent6fdcd2f1f8f59c7b1821265f21742636cd15d622 (diff)
Merge branch 'tmp' into 2953349-drush9-composer2953349-drush9-composer
-rw-r--r--Provision/Config/Drupal/provision_drupal_settings_8.tpl.php2
-rw-r--r--composer.json4
-rw-r--r--composer.lock70
-rw-r--r--http/Provision/Config/Apache/vhost.tpl.php4
-rw-r--r--http/Provision/Config/Apache/vhost_disabled.tpl.php5
-rw-r--r--http/Provision/Config/Nginx/Inc/vhost_include.tpl.php2
-rw-r--r--http/Provision/Service/http/nginx.php34
-rw-r--r--platform/verify.provision.inc32
-rw-r--r--provision.inc74
-rw-r--r--vendor/composer/ClassLoader.php6
-rw-r--r--vendor/composer/autoload_psr4.php1
-rw-r--r--vendor/composer/autoload_static.php16
-rw-r--r--vendor/composer/installed.json54
-rw-r--r--vendor/symfony/process/.gitignore3
-rw-r--r--vendor/symfony/process/CHANGELOG.md51
-rw-r--r--vendor/symfony/process/Exception/ExceptionInterface.php21
-rw-r--r--vendor/symfony/process/Exception/InvalidArgumentException.php21
-rw-r--r--vendor/symfony/process/Exception/LogicException.php21
-rw-r--r--vendor/symfony/process/Exception/ProcessFailedException.php54
-rw-r--r--vendor/symfony/process/Exception/ProcessTimedOutException.php69
-rw-r--r--vendor/symfony/process/Exception/RuntimeException.php21
-rw-r--r--vendor/symfony/process/ExecutableFinder.php90
-rw-r--r--vendor/symfony/process/InputStream.php90
-rw-r--r--vendor/symfony/process/LICENSE19
-rw-r--r--vendor/symfony/process/PhpExecutableFinder.php90
-rw-r--r--vendor/symfony/process/PhpProcess.php78
-rw-r--r--vendor/symfony/process/Pipes/AbstractPipes.php169
-rw-r--r--vendor/symfony/process/Pipes/PipesInterface.php67
-rw-r--r--vendor/symfony/process/Pipes/UnixPipes.php153
-rw-r--r--vendor/symfony/process/Pipes/WindowsPipes.php200
-rw-r--r--vendor/symfony/process/Process.php1735
-rw-r--r--vendor/symfony/process/ProcessBuilder.php288
-rw-r--r--vendor/symfony/process/ProcessUtils.php123
-rw-r--r--vendor/symfony/process/README.md13
-rw-r--r--vendor/symfony/process/Tests/ExecutableFinderTest.php133
-rw-r--r--vendor/symfony/process/Tests/NonStopableProcess.php47
-rw-r--r--vendor/symfony/process/Tests/PhpExecutableFinderTest.php72
-rw-r--r--vendor/symfony/process/Tests/PhpProcessTest.php48
-rw-r--r--vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php72
-rw-r--r--vendor/symfony/process/Tests/ProcessBuilderTest.php226
-rw-r--r--vendor/symfony/process/Tests/ProcessFailedExceptionTest.php135
-rw-r--r--vendor/symfony/process/Tests/ProcessTest.php1591
-rw-r--r--vendor/symfony/process/Tests/ProcessUtilsTest.php53
-rw-r--r--vendor/symfony/process/Tests/SignalListener.php21
-rw-r--r--vendor/symfony/process/composer.json33
-rw-r--r--vendor/symfony/process/phpunit.xml.dist30
46 files changed, 6099 insertions, 42 deletions
diff --git a/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php b/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php
index e173f45..e889b40 100644
--- a/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php
+++ b/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php
@@ -173,7 +173,7 @@ if (isset($_SERVER['db_name'])) {
$esc_uri = str_replace('.', '\.', $this->uri);
print " '^{$esc_uri}\$',\n";
foreach ($this->aliases as $alias_url) {
- $esc_alias = str_replace('.', '\.', $alias_url);
+ $esc_alias = preg_replace(['/\./', '/\/.+/'], ['\.', ''], $alias_url);
print " '^{$esc_alias}\$',\n";
}
?>
diff --git a/composer.json b/composer.json
index 938c358..612735d 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,9 @@
"issues": "https://www.drupal.org/project/issues/provision",
"docs": "http://docs.aegirproject.org/"
},
- "require": {},
+ "require": {
+ "symfony/process": "^3.3"
+ },
"config": {
"sort-packages": true,
"platform": {
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..ff81d07
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,70 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "c6615761b64ee98b0ecc471fd193dd04",
+ "packages": [
+ {
+ "name": "symfony/process",
+ "version": "v3.3.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
+ "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com",
+ "time": "2017-07-13T13:05:09+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "platform-overrides": {
+ "php": "7.0"
+ }
+}
diff --git a/http/Provision/Config/Apache/vhost.tpl.php b/http/Provision/Config/Apache/vhost.tpl.php
index 73b4819..361ecbc 100644
--- a/http/Provision/Config/Apache/vhost.tpl.php
+++ b/http/Provision/Config/Apache/vhost.tpl.php
@@ -42,10 +42,14 @@ if ($this->redirection || $ssl_redirection) {
if ($ssl_redirection && !$this->redirection) {
print " # Redirect aliases in non-ssl to the same alias on ssl.\n";
+ print " # Except for /.well-known/acme-challenge/ to prevent potential problems with Let's Encrypt\n";
+ print " RewriteCond %{REQUEST_URI} '!/.well-known/acme-challenge/'\n";
print " RewriteRule ^/*(.*)$ https://%{HTTP_HOST}/$1 [NE,L,R=301]\n";
}
elseif ($ssl_redirection && $this->redirection) {
print " # Redirect all aliases + main uri to the selected alias https uri.\n";
+ print " # Except for /.well-known/acme-challenge/ to prevent potential problems with Let's Encrypt\n";
+ print " RewriteCond %{REQUEST_URI} '!/.well-known/acme-challenge/'\n";
print " RewriteRule ^/*(.*)$ https://{$this->redirection}/$1 [NE,L,R=301]\n";
}
elseif (!$ssl_redirection && $this->redirection) {
diff --git a/http/Provision/Config/Apache/vhost_disabled.tpl.php b/http/Provision/Config/Apache/vhost_disabled.tpl.php
index 7d03c17..62395a6 100644
--- a/http/Provision/Config/Apache/vhost_disabled.tpl.php
+++ b/http/Provision/Config/Apache/vhost_disabled.tpl.php
@@ -15,6 +15,11 @@
?>
RewriteEngine on
+
+ # Redirect ALL visitors to a configured url.
+ # Except for /.well-known/acme-challenge/ to prevent potential problems with Let's Encrypt
+ RewriteCond %{REQUEST_URI} '!/.well-known/acme-challenge/'
+
# the ? at the end is to remove any query string in the original url
RewriteRule ^(.*)$ <?php print $this->platform->server->web_disable_url . '/' . $this->uri ?>?
diff --git a/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php b/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php
index 41a5e17..9558567 100644
--- a/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php
+++ b/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php
@@ -921,7 +921,7 @@ location ~* /(?:cross-?domain)\.xml$ {
###
### Allow some known php files (like serve.php in the ad module).
###
-location ~* /(?:modules|libraries)/(?:contrib/)?(?:ad|tinybrowser|f?ckeditor|tinymce|wysiwyg_spellcheck|ecc|civicrm|fbconnect|radioactivity)/.*\.php$ {
+location ~* /(?:modules|libraries)/(?:contrib/)?(?:ad|tinybrowser|f?ckeditor|tinymce|wysiwyg_spellcheck|ecc|civicrm|fbconnect|radioactivity|statistics)/.*\.php$ {
<?php if ($satellite_mode == 'boa'): ?>
limit_conn limreq 88;
<?php endif; ?>
diff --git a/http/Provision/Service/http/nginx.php b/http/Provision/Service/http/nginx.php
index 0711850..ca768bb 100644
--- a/http/Provision/Service/http/nginx.php
+++ b/http/Provision/Service/http/nginx.php
@@ -2,9 +2,10 @@
class Provision_Service_http_nginx extends Provision_Service_http_public {
- // Define socket file locations for various PHP versions.
+ // Define static socket file locations for various PHP versions.
+ // These are dynamic in PHP 7.
const SOCKET_PATH_PHP5 = '/var/run/php5-fpm.sock';
- const SOCKET_PATH_PHP7 = '/var/run/php/php7.0-fpm.sock';
+ const SOCKET_PATH_PHP7_BASE = '/var/run/php';
protected $application_name = 'nginx';
protected $has_restart_cmd = TRUE;
@@ -171,9 +172,9 @@ class Provision_Service_http_nginx extends Provision_Service_http_public {
$mode = 'socket';
$socket_path = self::SOCKET_PATH_PHP5;
break;
- case provision_file()->exists(self::SOCKET_PATH_PHP7)->status():
+ case provision_file()->exists(static::getPhp7FpmSocketPath())->status():
$mode = 'socket';
- $socket_path = self::SOCKET_PATH_PHP7;
+ $socket_path = static::getPhp7FpmSocketPath();
break;
default:
$mode = 'port';
@@ -187,7 +188,7 @@ class Provision_Service_http_nginx extends Provision_Service_http_public {
'@mode' => ($mode == 'socket') ? 'unix socket' : 'port',
'@task' => strtoupper($server_task),
'@yes_or_no' => ($mode == 'socket') ? 'YES' : 'NO',
- '@path' => ($socket_path ? $socket_path : self::SOCKET_PATH_PHP5 . ' or ' . self::SOCKET_PATH_PHP7),
+ '@path' => ($socket_path ? $socket_path : self::SOCKET_PATH_PHP5 . ' or ' . static::getPhp7FpmSocketPath()),
)));
}
@@ -212,7 +213,7 @@ class Provision_Service_http_nginx extends Provision_Service_http_public {
// Return the socket path based on the PHP version.
if (strtok(phpversion(), '.') == 7) {
- return self::SOCKET_PATH_PHP7;
+ return static::getPhp7FpmSocketPath();
}
else {
return self::SOCKET_PATH_PHP5;
@@ -220,6 +221,27 @@ class Provision_Service_http_nginx extends Provision_Service_http_public {
}
/**
+ * Gets the PHP FPM unix socket path for PHP 7.
+ *
+ * In PHP 7, there isn't a fixed socked path. It could be any one of the
+ * following:
+ * * php7.0-fpm.sock
+ * * php7.2-fpm.sock
+ * * ...
+ *
+ * @return string
+ * The path, or FALSE if there isn't one.
+ */
+ public static function getPhp7FpmSocketPath() {
+ foreach (scandir(static::SOCKET_PATH_PHP7_BASE, SCANDIR_SORT_DESCENDING) as $file) {
+ if (strpos($file, 'fpm.sock')) {
+ return static::SOCKET_PATH_PHP7_BASE . '/' . $file;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
* Guess at the likely value of the http_restart_cmd.
*
* This method is a static so that it can be re-used by the nginx_ssl
diff --git a/platform/verify.provision.inc b/platform/verify.provision.inc
index 9d140d2..3f60894 100644
--- a/platform/verify.provision.inc
+++ b/platform/verify.provision.inc
@@ -104,31 +104,13 @@ function drush_provision_drupal_pre_provision_verify() {
// Composer Install command: Do not interact, do not show download progress.
// Customizable by setting drush option 'provision_composer_install_command'
$composer_command = drush_get_option('provision_composer_install_command', 'composer install --no-interaction --no-progress --no-dev');
- drush_log(dt("Running command @command", array(
- '@command' => $composer_command
- )), 'ok');
- $start = time();
-
- // @TODO: Implement Symfony Process component for line-by-line output logging.
- if (drush_shell_cd_and_exec($composer_directory, $composer_command)) {
- $stop = time();
-
- $output = implode("\n", drush_shell_exec_output());
- $log_status = strpos($output, 'Warning:') === FALSE? 'success': 'warning';
-
- drush_log($output, $log_status);
- drush_log(dt(strpos($output, 'Warning:') . "Command ran successfully in @times: @command", array(
- '@command' => $composer_command,
- '@time' => $stop - $start,
- )), $log_status);
- }
- else {
- drush_log(implode("\n", drush_shell_exec_output()), 'error');
- drush_set_error('DRUSH_COMPOSER_ERROR', dt('The composer command failed in @dir: @command', array(
- '@dir' => $composer_directory,
- '@command' => $composer_command,
- )));
- }
+
+
+ // Implement Symfony Process component for line-by-line output logging.
+ provision_process($composer_command, $composer_directory);
+ $process = drush_get_context('provision_process_result');
+
+ drush_log(print_r($process, 1), 'devshop_log');
}
}
diff --git a/provision.inc b/provision.inc
index 0a4cbe0..13d3d81 100644
--- a/provision.inc
+++ b/provision.inc
@@ -413,7 +413,7 @@ function _scrub_object($input) {
/**
* Execute a command against a specific context object.
- *
+ *
* @param $target
* the context to operate on, @ prefix is optional.
* @param $command
@@ -427,9 +427,75 @@ function _scrub_object($input) {
*
* @see drush_invoke_process()
*/
-function provision_backend_invoke($target, $command, $arguments = array(), $data = array(), $mode = 'GET') {
- $context = '@' . ltrim($target, '@');
- return drush_invoke_process($context, $command, $arguments, $data, array('method' => $mode, 'integrate' => TRUE, 'dispatch-using-alias' => TRUE, 'drush-script' => DRUSH_COMMAND));
+function provision_backend_invoke($target, $drush_command, $arguments = array(), $task_options = array(), $mode = 'GET') {
+
+ // Build the command
+ $command = $env = [];
+ $command[] = $drush_bin = 'drush'; // @TODO: Point at the exact drush bin somewhere.
+ $command[] = $context = '@' . ltrim($target, '@');
+ $command[] = $drush_command;
+ $command = array_merge($command, $arguments);
+ $command[] = '--strict=0';
+ foreach ($task_options as $name => $value) {
+ $value = escapeshellarg($value);
+ $command[] = "--$name=$value";
+ }
+
+ return provision_process(implode(' ', $command), d($context)->site_path, 'Drush',
+ $env, t('Command failed.'));
+
+// return drush_invoke_process($context, $command, $arguments, $data, array('method' => $mode, 'integrate' => TRUE, 'dispatch-using-alias' => TRUE));
+}
+
+/**
+ * Run a command, sending output to drush logs in real time.
+ *
+ * The Symfony\Component\Process\Process Object is used to run this command.
+ * After implementing provision_process(), you can get the Process result object
+ * via drush context:
+ *
+ * $process = drush_get_context('provision_process_result');
+ * print $process->getExitCode();
+ *
+ */
+function provision_process($command, $cwd = null, $label = 'Process', $env = array(), $log_output = TRUE, $error_message = 'Process Failed') {
+ drush_log("[$label] $command", 'p_command');
+
+ // Merge in env vars, inheriting the CLI's
+ if (is_array($env)) {
+ $env = array_merge($_SERVER, $env);
+ }
+ else {
+ $env = $_SERVER;
+ }
+
+ // Make sure colors always come through
+ $env['TERM'] = 'xterm';
+
+ $process = new \Symfony\Component\Process\Process(escapeshellcmd($command), $cwd, $env);
+ $process->setTimeout(NULL);
+ if ($log_output) {
+ $exit_code = $process->run(function ($type, $buffer) {
+ drush_log($buffer, 'p_info');
+ });
+ }
+ else {
+ $exit_code = $process->run();
+ }
+
+ // Save the Provision Process object to Drush Context so that implementors can access the full object.
+ drush_set_context('provision_process_result', $process);
+
+ // check exit code
+ if ($exit_code === 0) {
+ drush_log('', 'p_ok');
+ return $process->getOutput();
+ }
+ else {
+ drush_log('', 'p_error');
+ drush_set_error('PROVISION_PROCESS_ERROR', dt($error_message));
+ return $process->getErrorOutput();
+ }
}
/**
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
index 2c72175..95f7e09 100644
--- a/vendor/composer/ClassLoader.php
+++ b/vendor/composer/ClassLoader.php
@@ -377,11 +377,11 @@ class ClassLoader
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
- $search = $subPath.'\\';
+ $search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
- $length = $this->prefixLengthsPsr4[$first][$search];
- if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+ if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index b265c64..8ae063a 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
+ 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
);
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 8f98a32..db03ac4 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -6,6 +6,20 @@ namespace Composer\Autoload;
class ComposerStaticInit93ce438f8ce8708ef24439d2070dee06
{
+ public static $prefixLengthsPsr4 = array (
+ 'S' =>
+ array (
+ 'Symfony\\Component\\Process\\' => 26,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'Symfony\\Component\\Process\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/symfony/process',
+ ),
+ );
+
public static $prefixesPsr0 = array (
'P' =>
array (
@@ -19,6 +33,8 @@ class ComposerStaticInit93ce438f8ce8708ef24439d2070dee06
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixesPsr0;
}, null, ClassLoader::class);
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index fe51488..289474b 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -1 +1,53 @@
-[]
+[
+ {
+ "name": "symfony/process",
+ "version": "v3.3.6",
+ "version_normalized": "3.3.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a",
+ "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "time": "2017-07-13T13:05:09+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.3-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Process Component",
+ "homepage": "https://symfony.com"
+ }
+]
diff --git a/vendor/symfony/process/.gitignore b/vendor/symfony/process/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/vendor/symfony/process/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md
new file mode 100644
index 0000000..bb719be
--- /dev/null
+++ b/vendor/symfony/process/CHANGELOG.md
@@ -0,0 +1,51 @@
+CHANGELOG
+=========
+
+3.3.0
+-----
+
+ * added command line arrays in the `Process` class
+ * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
+ * deprecated the `ProcessUtils::escapeArgument()` method
+ * deprecated not inheriting environment variables
+ * deprecated configuring `proc_open()` options
+ * deprecated configuring enhanced Windows compatibility
+ * deprecated configuring enhanced sigchild compatibility
+
+2.5.0
+-----
+
+ * added support for PTY mode
+ * added the convenience method "mustRun"
+ * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
+ * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
+ * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
+
+2.4.0
+-----
+
+ * added the ability to define an idle timeout
+
+2.3.0
+-----
+
+ * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
+ * added Process::signal()
+ * added Process::getPid()
+ * added support for a TTY mode
+
+2.2.0
+-----
+
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
+ * added a way to retrieve the standard and error output incrementally
+ * added Process:restart()
+
+2.1.0
+-----
+
+ * added support for non-blocking processes (start(), wait(), isRunning(), stop())
+ * enhanced Windows compatibility
+ * added Process::getExitCodeText() that returns a string representation for
+ the exit code returned by the process
+ * added ProcessBuilder
diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..75c1c9e
--- /dev/null
+++ b/vendor/symfony/process/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+<?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\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+interface ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..926ee21
--- /dev/null
+++ b/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+<?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\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php
new file mode 100644
index 0000000..be3d490
--- /dev/null
+++ b/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+<?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\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100644
index 0000000..328acfd
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+<?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\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100644
index 0000000..d451146
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+<?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\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ const TYPE_GENERAL = 1;
+ const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return $this->timeoutType === self::TYPE_GENERAL;
+ }
+
+ public function isIdleTimeout()
+ {
+ return $this->timeoutType === self::TYPE_IDLE;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php
new file mode 100644
index 0000000..adead25
--- /dev/null
+++ b/vendor/symfony/process/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+<?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\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php
new file mode 100644
index 0000000..d8e6896
--- /dev/null
+++ b/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,90 @@
+<?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;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ExecutableFinder
+{
+ private $suffixes = array('.exe', '.bat', '.cmd', '.com');
+
+ /**
+ * Replaces default suffixes of executable.
+ *
+ * @param array $suffixes
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ *
+ * @param string $suffix
+ */
+ public function addSuffix($suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string The executable path or default value
+ */
+ public function find($name, $default = null, array $extraDirs = array())
+ {
+ if (ini_get('open_basedir')) {
+ $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir'));
+ $dirs = array();
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && @is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = array('');
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = array_merge($suffixes, $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes);
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php
new file mode 100644
index 0000000..831b109
--- /dev/null
+++ b/vendor/symfony/process/InputStream.php
@@ -0,0 +1,90 @@
+<?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;
+
+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>
+ */
+class InputStream implements \IteratorAggregate
+{
+ private $onEmpty = null;
+ private $input = array();
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|scalar|\Traversable|null The input to append as stream resource, scalar or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('%s is closed', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ foreach ($current as $cur) {
+ yield $cur;
+ }
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE
new file mode 100644
index 0000000..17d16a1
--- /dev/null
+++ b/vendor/symfony/process/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2017 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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100644
index 0000000..db31cc1
--- /dev/null
+++ b/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,90 @@
+<?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;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * 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
+ */
+ public function find($includeArgs = true)
+ {
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // HHVM support
+ if (defined('HHVM_VERSION')) {
+ return (getenv('PHP_BINARY') ?: PHP_BINARY).$args;
+ }
+
+ // PHP_BINARY return the current sapi executable
+ if (PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) {
+ return PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!is_executable($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (is_executable($php)) {
+ return $php;
+ }
+ }
+
+ $dirs = array(PHP_BINDIR);
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array The PHP executable arguments
+ */
+ public function findArguments()
+ {
+ $arguments = array();
+
+ if (defined('HHVM_VERSION')) {
+ $arguments[] = '--php';
+ } elseif ('phpdbg' === PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php
new file mode 100644
index 0000000..7afd182
--- /dev/null
+++ b/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,78 @@
+<?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;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('<?php echo "foo"; ?>');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class PhpProcess extends Process
+{
+ /**
+ * Constructor.
+ *
+ * @param string $script The PHP script to run (as a string)
+ * @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 int $timeout The timeout in seconds
+ * @param array $options An array of options for proc_open
+ */
+ public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = null)
+ {
+ $executableFinder = new PhpExecutableFinder();
+ if (false === $php = $executableFinder->find(false)) {
+ $php = null;
+ } else {
+ $php = array_merge(array($php), $executableFinder->findArguments());
+ }
+ if ('phpdbg' === PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+ if (null !== $options) {
+ @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout, $options);
+ }
+
+ /**
+ * Sets the path to the PHP binary to use.
+ */
+ public function setPhpBinary($php)
+ {
+ $this->setCommandLine($php);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(callable $callback = null/*, array $env = array()*/)
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+ $env = 1 < func_num_args() ? func_get_arg(1) : null;
+
+ parent::start($callback, $env);
+ }
+}
diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100644
index 0000000..4c67d5b
--- /dev/null
+++ b/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,169 @@
+<?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\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ /** @var array */
+ public $pipes = array();
+
+ /** @var string */
+ private $inputBuffer = '';
+ /** @var resource|scalar|\Iterator|null */
+ private $input;
+ /** @var bool */
+ private $blocked = true;
+
+ public function __construct($input)
+ {
+ if (is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ fclose($pipe);
+ }
+ $this->pipes = array();
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ *
+ * @return bool
+ */
+ protected function hasSystemCallBeenInterrupted()
+ {
+ $lastError = error_get_last();
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write()
+ {
+ if (!isset($this->pipes[0])) {
+ return;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } 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)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = array();
+ $w = array($this->pipes[0]);
+
+ // let's have a look if something changed in streams
+ if (false === $n = @stream_select($r, $w, $e, 0, 0)) {
+ return;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return array($this->pipes[0]);
+ }
+ }
+
+ if ($input) {
+ for (;;) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return array($this->pipes[0]);
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return array($this->pipes[0]);
+ }
+ }
+}
diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100644
index 0000000..52bbe76
--- /dev/null
+++ b/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,67 @@
+<?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\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ *
+ * @return array
+ */
+ public function getDescriptors();
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles();
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite($blocking, $close = false);
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ *
+ * @return bool
+ */
+ public function areOpen();
+
+ /**
+ * Returns if pipes are able to read output.
+ *
+ * @return bool
+ */
+ public function haveReadSupport();
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100644
index 0000000..3185fe7
--- /dev/null
+++ b/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,153 @@
+<?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\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ /** @var bool */
+ private $ttyMode;
+ /** @var bool */
+ private $ptyMode;
+ /** @var bool */
+ private $haveReadSupport;
+
+ public function __construct($ttyMode, $ptyMode, $input, $haveReadSupport)
+ {
+ $this->ttyMode = (bool) $ttyMode;
+ $this->ptyMode = (bool) $ptyMode;
+ $this->haveReadSupport = (bool) $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return array(
+ array('pipe', 'r'),
+ $nullstream,
+ $nullstream,
+ );
+ }
+
+ if ($this->ttyMode) {
+ return array(
+ array('file', '/dev/tty', 'r'),
+ array('file', '/dev/tty', 'w'),
+ array('file', '/dev/tty', 'w'),
+ );
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return array(
+ array('pty'),
+ array('pty'),
+ array('pty'),
+ );
+ }
+
+ return array(
+ array('pipe', 'r'),
+ array('pipe', 'w'), // stdout
+ array('pipe', 'w'), // stderr
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = array();
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = array();
+ }
+
+ return $read;
+ }
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return (bool) $this->pipes;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100644
index 0000000..a1e3115
--- /dev/null
+++ b/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,200 @@
+<?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\Pipes;
+
+use Symfony\Component\Process\Process;
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * 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
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ /** @var array */
+ private $files = array();
+ /** @var array */
+ private $fileHandles = array();
+ /** @var array */
+ private $readBytes = array(
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ );
+ /** @var bool */
+ private $haveReadSupport;
+
+ public function __construct($input, $haveReadSupport)
+ {
+ $this->haveReadSupport = (bool) $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // 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
+ $pipes = array(
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ );
+ $tmpCheck = false;
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+ if (file_exists($file) && !unlink($file)) {
+ continue 2;
+ }
+ $h = fopen($file, 'xb');
+ if (!$h) {
+ $error = $lastError;
+ if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
+ continue;
+ }
+ restore_error_handler();
+ throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
+ }
+ if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) {
+ continue 2;
+ }
+ if (isset($this->files[$pipe])) {
+ unlink($this->files[$pipe]);
+ }
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ $this->removeFiles();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors()
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return array(
+ array('pipe', 'r'),
+ $nullstream,
+ $nullstream,
+ );
+ }
+
+ // 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
+ // So we redirect output within the commandline and pass the nul device to the process
+ return array(
+ array('pipe', 'r'),
+ array('file', 'NUL', 'w'),
+ array('file', 'NUL', 'w'),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite($blocking, $close = false)
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = array();
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep(Process::TIMEOUT_PRECISION * 1E6);
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ fclose($fileHandle);
+ unset($this->fileHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport()
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen()
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $handle) {
+ fclose($handle);
+ }
+ $this->fileHandles = array();
+ }
+
+ /**
+ * Removes temporary files.
+ */
+ private function removeFiles()
+ {
+ foreach ($this->files as $filename) {
+ if (file_exists($filename)) {
+ @unlink($filename);
+ }
+ }
+ $this->files = array();
+ }
+}
diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php
new file mode 100644
index 0000000..05f88d2
--- /dev/null
+++ b/vendor/symfony/process/Process.php
@@ -0,0 +1,1735 @@
+<?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;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class Process implements \IteratorAggregate
+{
+ const ERR = 'err';
+ const OUT = 'out';
+
+ const STATUS_READY = 'ready';
+ const STATUS_STARTED = 'started';
+ const STATUS_TERMINATED = 'terminated';
+
+ const STDIN = 0;
+ const STDOUT = 1;
+ const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ 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
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env;
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $options = array('suppress_errors' => true);
+ private $exitcode;
+ private $fallbackStatus = array();
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $enhanceWindowsCompatibility = true;
+ private $enhanceSigchildCompatibility;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty;
+ private $pty;
+ private $inheritEnv = false;
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ *
+ * @var array
+ */
+ public static $exitCodes = array(
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ );
+
+ /**
+ * Constructor.
+ *
+ * @param string|array $commandline The command line to run
+ * @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 int|float|null $timeout The timeout in seconds or null to disable
+ * @param array $options An array of options for proc_open
+ *
+ * @throws RuntimeException When proc_open is not installed
+ */
+ public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
+ {
+ if (!function_exists('proc_open')) {
+ throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ $this->commandline = $commandline;
+ $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
+ if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
+ if (null !== $options) {
+ @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED);
+ $this->options = array_replace($this->options, $options);
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->stop(0);
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @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
+ *
+ * @final since version 3.3
+ */
+ public function run($callback = null/*, array $env = array()*/)
+ {
+ $env = 1 < func_num_args() ? func_get_arg(1) : null;
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * 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
+ *
+ * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final since version 3.3
+ */
+ public function mustRun(callable $callback = null/*, array $env = array()*/)
+ {
+ if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
+ }
+ $env = 1 < func_num_args() ? func_get_arg(1) : null;
+
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes 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.
+ *
+ * @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
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(callable $callback = null/*, array $env = array()*/)
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+ if (2 <= func_num_args()) {
+ $env = func_get_arg(1);
+ } else {
+ if (__CLASS__ !== static::class) {
+ $r = new \ReflectionMethod($this, __FUNCTION__);
+ if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[0]->name)) {
+ @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since version 3.3. It will be made mandatory in 4.0.', static::class), E_USER_DEPRECATED);
+ }
+ }
+ $env = null;
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+ $inheritEnv = $this->inheritEnv;
+
+ if (is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map(array($this, 'escapeArgument'), $commandline));
+
+ if ('\\' !== DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ }
+
+ if (null === $env) {
+ $env = $this->env;
+ } else {
+ if ($this->env) {
+ $env += $this->env;
+ }
+ $inheritEnv = true;
+ }
+
+ $envBackup = array();
+ if (null !== $env && $inheritEnv) {
+ foreach ($env as $k => $v) {
+ $envBackup[$k] = getenv($k);
+ putenv(false === $v || null === $v ? $k : "$k=$v");
+ }
+ $env = null;
+ } elseif (null !== $env) {
+ @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED);
+ }
+ if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
+ $this->options['bypass_shell'] = true;
+ $commandline = $this->prepareWindowsCommandLine($commandline, $envBackup, $env);
+ } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = array('pipe', 'w');
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $env, $this->options);
+
+ foreach ($envBackup as $k => $v) {
+ putenv(false === $v ? $k : "$k=$v");
+ }
+
+ if (!is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @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
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final since version 3.3
+ */
+ public function restart(callable $callback = null/*, array $env = array()*/)
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running');
+ }
+ $env = 1 < func_num_args() ? func_get_arg(1) : null;
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * 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.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @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
+ */
+ public function wait(callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php)
+ *
+ * @return $this
+ *
+ * @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
+ */
+ public function signal($signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ 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.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string The process output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * 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
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ *
+ * @return \Generator
+ */
+ public function getIterator($flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string The process error output
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string The process error output since the last call
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return null|int The exit status code, null if the Process is not terminated
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
+ */
+ public function getExitCode()
+ {
+ if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
+ }
+
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return null|string A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return;
+ }
+
+ return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool true if the process ended successfully, false otherwise
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool true if the process is currently running, false otherwise
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool true if status is ready, false otherwise
+ */
+ public function isStarted()
+ {
+ return $this->status != self::STATUS_READY;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool true if process is terminated, false otherwise
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return $this->status == self::STATUS_TERMINATED;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string The current process status
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @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
+ */
+ public function stop($timeout = 10, $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ *
+ * @param string $line The line to append
+ */
+ public function addOutput($line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ *
+ * @param string $line The line to append
+ */
+ public function addErrorOutput($line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string The command to execute
+ */
+ public function getCommandLine()
+ {
+ return is_array($this->commandline) ? implode(' ', array_map(array($this, 'escapeArgument'), $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Sets the command line to be executed.
+ *
+ * @param string|array $commandline The command to execute
+ *
+ * @return self The current Process instance
+ */
+ public function setCommandLine($commandline)
+ {
+ $this->commandline = $commandline;
+
+ return $this;
+ }
+
+ /**
+ * Gets the process timeout (max. runtime).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout (max. time since last output).
+ *
+ * @return float|null The timeout in seconds or null if it's disabled
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return self The current Process instance
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout($timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output).
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param int|float|null $timeout The timeout in seconds
+ *
+ * @return self The current Process instance
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout($timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout can not be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @param bool $tty True to enabled and false to disable
+ *
+ * @return self The current Process instance
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty($tty)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+ if ($tty) {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w')), $pipes);
+ }
+
+ if (!$isTtySupported) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+ }
+
+ $this->tty = (bool) $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool true if the TTY mode is enabled, false otherwise
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @param bool $bool
+ *
+ * @return self
+ */
+ public function setPty($bool)
+ {
+ $this->pty = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null The current working directory or null on failure
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @param string $cwd The new working directory
+ *
+ * @return self The current Process instance
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array The current environment variables
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * An 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.
+ *
+ * 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
+ */
+ 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;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null The Process input
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param resource|scalar|\Traversable|null $input The content
+ *
+ * @return self The current Process instance
+ *
+ * @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.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Gets the options for proc_open.
+ *
+ * @return array The current options
+ *
+ * @deprecated since version 3.3, to be removed in 4.0.
+ */
+ public function getOptions()
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
+
+ return $this->options;
+ }
+
+ /**
+ * Sets the options for proc_open.
+ *
+ * @param array $options The new options
+ *
+ * @return self The current Process instance
+ *
+ * @deprecated since version 3.3, to be removed in 4.0.
+ */
+ public function setOptions(array $options)
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->options = $options;
+
+ return $this;
+ }
+
+ /**
+ * Gets whether or not Windows compatibility is enabled.
+ *
+ * This is true by default.
+ *
+ * @return bool
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
+ */
+ public function getEnhanceWindowsCompatibility()
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED);
+
+ return $this->enhanceWindowsCompatibility;
+ }
+
+ /**
+ * Sets whether or not Windows compatibility is enabled.
+ *
+ * @param bool $enhance
+ *
+ * @return self The current Process instance
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
+ */
+ public function setEnhanceWindowsCompatibility($enhance)
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->enhanceWindowsCompatibility = (bool) $enhance;
+
+ return $this;
+ }
+
+ /**
+ * Returns whether sigchild compatibility mode is activated or not.
+ *
+ * @return bool
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
+ */
+ public function getEnhanceSigchildCompatibility()
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED);
+
+ return $this->enhanceSigchildCompatibility;
+ }
+
+ /**
+ * Activates sigchild compatibility mode.
+ *
+ * Sigchild compatibility mode is required to get the exit code and
+ * determine the success of a process when PHP has been compiled with
+ * the --enable-sigchild option
+ *
+ * @param bool $enhance
+ *
+ * @return self The current Process instance
+ *
+ * @deprecated since version 3.3, to be removed in 4.0.
+ */
+ public function setEnhanceSigchildCompatibility($enhance)
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->enhanceSigchildCompatibility = (bool) $enhance;
+
+ 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) {
+ @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED);
+ }
+
+ $this->inheritEnv = (bool) $inheritEnv;
+
+ return $this;
+ }
+
+ /**
+ * Returns whether environment variables will be inherited or not.
+ *
+ * @return bool
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
+ */
+ public function areEnvironmentVariablesInherited()
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), E_USER_DEPRECATED);
+
+ return $this->inheritEnv;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if ($this->status !== self::STATUS_STARTED) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', array(array('pty'), array('pty'), array('pty')), $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ *
+ * @return array
+ */
+ private function getDescriptors()
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure A PHP closure
+ */
+ protected function buildCallback(callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback) {
+ if (null !== $callback) {
+ call_user_func($callback, $type, $data);
+ }
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out) {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ if (null !== $callback) {
+ call_user_func($callback, $type, $data);
+ }
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus($blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+
+ return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput($caller, $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @param int|float|null $timeout
+ *
+ * @return float|null
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout($timeout)
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes($blocking, $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close()
+ {
+ $this->processPipes->close();
+ if (is_resource($this->process)) {
+ proc_close($this->process);
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = array();
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * 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 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
+ */
+ private function doSignal($signal, $throwException)
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Can not send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = (int) $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine($cmd, array &$envBackup, array &$env = null)
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = array();
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$envBackup, &$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (false !== strpos($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(array('!LF!', '"^!"', '"^%"', '"^^"', '""'), array("\n", '!', '%', '^', '"'), $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ if (null === $env) {
+ putenv("$var=$value");
+ } else {
+ $env[$var] = $value;
+ }
+
+ $envBackup[$var] = false;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @param string $functionName The function name that was called
+ *
+ * @throws LogicException If the process has not run.
+ */
+ private function requireProcessIsStarted($functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
+ *
+ * @param string $functionName The function name that was called
+ *
+ * @throws LogicException If the process is not yet terminated.
+ */
+ private function requireProcessIsTerminated($functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ *
+ * @param string $argument The argument that will be escaped
+ *
+ * @return string The escaped argument
+ */
+ private function escapeArgument($argument)
+ {
+ if ('\\' !== DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if ('' === $argument = (string) $argument) {
+ return '""';
+ }
+ if (false !== strpos($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"';
+ }
+}
diff --git a/vendor/symfony/process/ProcessBuilder.php b/vendor/symfony/process/ProcessBuilder.php
new file mode 100644
index 0000000..36db35e
--- /dev/null
+++ b/vendor/symfony/process/ProcessBuilder.php
@@ -0,0 +1,288 @@
+<?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;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+
+/**
+ * Process builder.
+ *
+ * @author Kris Wallsmith <kris@symfony.com>
+ */
+class ProcessBuilder
+{
+ private $arguments;
+ private $cwd;
+ private $env = array();
+ private $input;
+ private $timeout = 60;
+ private $options;
+ private $inheritEnv = true;
+ private $prefix = array();
+ private $outputDisabled = false;
+
+ /**
+ * Constructor.
+ *
+ * @param string[] $arguments An array of arguments
+ */
+ public function __construct(array $arguments = array())
+ {
+ $this->arguments = $arguments;
+ }
+
+ /**
+ * Creates a process builder instance.
+ *
+ * @param string[] $arguments An array of arguments
+ *
+ * @return static
+ */
+ public static function create(array $arguments = array())
+ {
+ return new static($arguments);
+ }
+
+ /**
+ * Adds an unescaped argument to the command string.
+ *
+ * @param string $argument A command argument
+ *
+ * @return $this
+ */
+ public function add($argument)
+ {
+ $this->arguments[] = $argument;
+
+ return $this;
+ }
+
+ /**
+ * Adds a prefix to the command string.
+ *
+ * The prefix is preserved when resetting arguments.
+ *
+ * @param string|array $prefix A command prefix or an array of command prefixes
+ *
+ * @return $this
+ */
+ public function setPrefix($prefix)
+ {
+ $this->prefix = is_array($prefix) ? $prefix : array($prefix);
+
+ return $this;
+ }
+
+ /**
+ * Sets the arguments of the process.
+ *
+ * Arguments must not be escaped.
+ * Previous arguments are removed.
+ *
+ * @param string[] $arguments
+ *
+ * @return $this
+ */
+ public function setArguments(array $arguments)
+ {
+ $this->arguments = $arguments;
+
+ return $this;
+ }
+
+ /**
+ * Sets the working directory.
+ *
+ * @param null|string $cwd The working directory
+ *
+ * @return $this
+ */
+ public function setWorkingDirectory($cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Sets whether environment variables will be inherited or not.
+ *
+ * @param bool $inheritEnv
+ *
+ * @return $this
+ *
+ * @deprecated since version 3.3, to be removed in 4.0.
+ */
+ public function inheritEnvironmentVariables($inheritEnv = true)
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->inheritEnv = $inheritEnv;
+
+ return $this;
+ }
+
+ /**
+ * Sets an environment variable.
+ *
+ * Setting a variable overrides its previous value. Use `null` to unset a
+ * defined environment variable.
+ *
+ * @param string $name The variable name
+ * @param null|string $value The variable value
+ *
+ * @return $this
+ */
+ public function setEnv($name, $value)
+ {
+ $this->env[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Adds a set of environment variables.
+ *
+ * Already existing environment variables with the same name will be
+ * overridden by the new values passed to this method. Pass `null` to unset
+ * a variable.
+ *
+ * @param array $variables The variables
+ *
+ * @return $this
+ */
+ public function addEnvironmentVariables(array $variables)
+ {
+ $this->env = array_replace($this->env, $variables);
+
+ return $this;
+ }
+
+ /**
+ * Sets the input of the process.
+ *
+ * @param resource|scalar|\Traversable|null $input The input content
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException In case the argument is invalid
+ */
+ public function setInput($input)
+ {
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process timeout.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @param float|null $timeout
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException
+ */
+ public function setTimeout($timeout)
+ {
+ if (null === $timeout) {
+ $this->timeout = null;
+
+ return $this;
+ }
+
+ $timeout = (float) $timeout;
+
+ if ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ $this->timeout = $timeout;
+
+ return $this;
+ }
+
+ /**
+ * Adds a proc_open option.
+ *
+ * @param string $name The option name
+ * @param string $value The option value
+ *
+ * @return $this
+ *
+ * @deprecated since version 3.3, to be removed in 4.0.
+ */
+ public function setOption($name, $value)
+ {
+ @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED);
+
+ $this->options[$name] = $value;
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ */
+ public function disableOutput()
+ {
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ */
+ public function enableOutput()
+ {
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Creates a Process instance and returns it.
+ *
+ * @return Process
+ *
+ * @throws LogicException In case no arguments have been provided
+ */
+ public function getProcess()
+ {
+ if (0 === count($this->prefix) && 0 === count($this->arguments)) {
+ throw new LogicException('You must add() command arguments before calling getProcess().');
+ }
+
+ $arguments = array_merge($this->prefix, $this->arguments);
+ $process = new Process($arguments, $this->cwd, $this->env, $this->input, $this->timeout, $this->options);
+ // to preserve the BC with symfony <3.3, we convert the array structure
+ // to a string structure to avoid the prefixing with the exec command
+ $process->setCommandLine($process->getCommandLine());
+
+ if ($this->inheritEnv) {
+ $process->inheritEnvironmentVariables();
+ }
+ if ($this->outputDisabled) {
+ $process->disableOutput();
+ }
+
+ return $process;
+ }
+}
diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php
new file mode 100644
index 0000000..24438d9
--- /dev/null
+++ b/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,123 @@
+<?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;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň <martin.hason@gmail.com>
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ *
+ * @param string $argument The argument that will be escaped
+ *
+ * @return string The escaped argument
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use a command line array or give env vars to the `Process::start/run()` method instead.
+ */
+ public static function escapeArgument($argument)
+ {
+ @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 and will be removed in 4.0. Use a command line array or give env vars to the Process::start/run() method instead.', E_USER_DEPRECATED);
+
+ //Fix for PHP bug #43784 escapeshellarg removes % from given string
+ //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
+ //@see https://bugs.php.net/bug.php?id=43784
+ //@see https://bugs.php.net/bug.php?id=49446
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ if ('' === $argument) {
+ return escapeshellarg($argument);
+ }
+
+ $escapedArgument = '';
+ $quote = false;
+ foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
+ if ('"' === $part) {
+ $escapedArgument .= '\\"';
+ } elseif (self::isSurroundedBy($part, '%')) {
+ // Avoid environment variable expansion
+ $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%';
+ } else {
+ // escape trailing backslash
+ if ('\\' === substr($part, -1)) {
+ $part .= '\\';
+ }
+ $quote = true;
+ $escapedArgument .= $part;
+ }
+ }
+ if ($quote) {
+ $escapedArgument = '"'.$escapedArgument.'"';
+ }
+
+ return $escapedArgument;
+ }
+
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed The validated input
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput($caller, $input)
+ {
+ if (null !== $input) {
+ if (is_resource($input)) {
+ return $input;
+ }
+ if (is_string($input)) {
+ return $input;
+ }
+ if (is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+
+ private static function isSurroundedBy($arg, $char)
+ {
+ return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
+ }
+}
diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md
new file mode 100644
index 0000000..b7ca5b4
--- /dev/null
+++ b/vendor/symfony/process/README.md
@@ -0,0 +1,13 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+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)
diff --git a/vendor/symfony/process/Tests/ExecutableFinderTest.php b/vendor/symfony/process/Tests/ExecutableFinderTest.php
new file mode 100644
index 0000000..bc692f6
--- /dev/null
+++ b/vendor/symfony/process/Tests/ExecutableFinderTest.php
@@ -0,0 +1,133 @@
+<?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 testFindWithExtraDirs()
+ {
+ if (ini_get('open_basedir')) {
+ $this->markTestSkipped('Cannot test when open_basedir is set');
+ }
+
+ $this->setPath('');
+
+ $extraDirs = array(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).(!defined('HHVM_VERSION') || HHVM_VERSION_ID >= 30800 ? 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.(!defined('HHVM_VERSION') || HHVM_VERSION_ID >= 30800 ? PATH_SEPARATOR.'/' : ''));
+
+ $finder = new ExecutableFinder();
+ $result = $finder->find($this->getPhpBinaryName(), false);
+
+ $this->assertSamePath(PHP_BINARY, $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' : '');
+ }
+}
diff --git a/vendor/symfony/process/Tests/NonStopableProcess.php b/vendor/symfony/process/Tests/NonStopableProcess.php
new file mode 100644
index 0000000..5643259
--- /dev/null
+++ b/vendor/symfony/process/Tests/NonStopableProcess.php
@@ -0,0 +1,47 @@
+<?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();
+}
diff --git a/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
new file mode 100644
index 0000000..b08ad5d
--- /dev/null
+++ b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php
@@ -0,0 +1,72 @@
+<?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()
+ {
+ if (defined('HHVM_VERSION')) {
+ $this->markTestSkipped('Should not be executed in HHVM context.');
+ }
+
+ $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 / constant PHP_BINARY with HHVM.
+ */
+ public function testFindWithHHVM()
+ {
+ if (!defined('HHVM_VERSION')) {
+ $this->markTestSkipped('Should be executed in HHVM context.');
+ }
+
+ $f = new PhpExecutableFinder();
+
+ $current = getenv('PHP_BINARY') ?: PHP_BINARY;
+
+ $this->assertEquals($current.' --php', $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 (defined('HHVM_VERSION')) {
+ $this->assertEquals($f->findArguments(), array('--php'), '::findArguments() returns HHVM arguments');
+ } elseif ('phpdbg' === PHP_SAPI) {
+ $this->assertEquals($f->findArguments(), array('-qrr'), '::findArguments() returns phpdbg arguments');
+ } else {
+ $this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments');
+ }
+ }
+}
diff --git a/vendor/symfony/process/Tests/PhpProcessTest.php b/vendor/symfony/process/Tests/PhpProcessTest.php
new file mode 100644
index 0000000..988cd09
--- /dev/null
+++ b/vendor/symfony/process/Tests/PhpProcessTest.php
@@ -0,0 +1,48 @@
+<?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\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(phpversion().PHP_SAPI, $process->getOutput());
+ }
+}
diff --git a/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
new file mode 100644
index 0000000..bbd7ddf
--- /dev/null
+++ b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php
@@ -0,0 +1,72 @@
+<?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 = array(STDIN);
+$write = array(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, (binary) $out, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $out = (binary) substr($out, $written);
+ }
+ if (null === $read && '' === $out) {
+ $write = array_diff($write, array(STDOUT));
+ }
+
+ if (in_array(STDERR, $w) && strlen($err) > 0) {
+ $written = fwrite(STDERR, (binary) $err, 32768);
+ if (false === $written) {
+ die(ERR_WRITE_FAILED);
+ }
+ $err = (binary) substr($err, $written);
+ }
+ if (null === $read && '' === $err) {
+ $write = array_diff($write, array(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);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessBuilderTest.php b/vendor/symfony/process/Tests/ProcessBuilderTest.php
new file mode 100644
index 0000000..fa7c8c5
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessBuilderTest.php
@@ -0,0 +1,226 @@
+<?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\ProcessBuilder;
+
+class ProcessBuilderTest extends TestCase
+{
+ /**
+ * @group legacy
+ */
+ public function testInheritEnvironmentVars()
+ {
+ $proc = ProcessBuilder::create()
+ ->add('foo')
+ ->getProcess();
+
+ $this->assertTrue($proc->areEnvironmentVariablesInherited());
+
+ $proc = ProcessBuilder::create()
+ ->add('foo')
+ ->inheritEnvironmentVariables(false)
+ ->getProcess();
+
+ $this->assertFalse($proc->areEnvironmentVariablesInherited());
+ }
+
+ public function testAddEnvironmentVariables()
+ {
+ $pb = new ProcessBuilder();
+ $env = array(
+ 'foo' => 'bar',
+ 'foo2' => 'bar2',
+ );
+ $proc = $pb
+ ->add('command')
+ ->setEnv('foo', 'bar2')
+ ->addEnvironmentVariables($env)
+ ->getProcess()
+ ;
+
+ $this->assertSame($env, $proc->getEnv());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromSetter()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setTimeout(-1);
+ }
+
+ public function testNullTimeout()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setTimeout(10);
+ $pb->setTimeout(null);
+
+ $r = new \ReflectionObject($pb);
+ $p = $r->getProperty('timeout');
+ $p->setAccessible(true);
+
+ $this->assertNull($p->getValue($pb));
+ }
+
+ public function testShouldSetArguments()
+ {
+ $pb = new ProcessBuilder(array('initial'));
+ $pb->setArguments(array('second'));
+
+ $proc = $pb->getProcess();
+
+ $this->assertContains('second', $proc->getCommandLine());
+ }
+
+ public function testPrefixIsPrependedToAllGeneratedProcess()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setPrefix('/usr/bin/php');
+
+ $proc = $pb->setArguments(array('-v'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" -v', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine());
+ }
+
+ $proc = $pb->setArguments(array('-i'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" -i', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine());
+ }
+ }
+
+ public function testArrayPrefixesArePrependedToAllGeneratedProcess()
+ {
+ $pb = new ProcessBuilder();
+ $pb->setPrefix(array('/usr/bin/php', 'composer.phar'));
+
+ $proc = $pb->setArguments(array('-v'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" composer.phar -v', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' 'composer.phar' '-v'", $proc->getCommandLine());
+ }
+
+ $proc = $pb->setArguments(array('-i'))->getProcess();
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php" composer.phar -i', $proc->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php' 'composer.phar' '-i'", $proc->getCommandLine());
+ }
+ }
+
+ public function testShouldEscapeArguments()
+ {
+ $pb = new ProcessBuilder(array('%path%', 'foo " bar', '%baz%baz'));
+ $proc = $pb->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertSame('""^%"path"^%"" "foo "" bar" ""^%"baz"^%"baz"', $proc->getCommandLine());
+ } else {
+ $this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine());
+ }
+ }
+
+ public function testShouldEscapeArgumentsAndPrefix()
+ {
+ $pb = new ProcessBuilder(array('arg'));
+ $pb->setPrefix('%prefix%');
+ $proc = $pb->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertSame('""^%"prefix"^%"" arg', $proc->getCommandLine());
+ } else {
+ $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine());
+ }
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ */
+ public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument()
+ {
+ ProcessBuilder::create()->getProcess();
+ }
+
+ public function testShouldNotThrowALogicExceptionIfNoArgument()
+ {
+ $process = ProcessBuilder::create()
+ ->setPrefix('/usr/bin/php')
+ ->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
+ }
+ }
+
+ public function testShouldNotThrowALogicExceptionIfNoPrefix()
+ {
+ $process = ProcessBuilder::create(array('/usr/bin/php'))
+ ->getProcess();
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
+ } else {
+ $this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
+ }
+ }
+
+ public function testShouldReturnProcessWithDisabledOutput()
+ {
+ $process = ProcessBuilder::create(array('/usr/bin/php'))
+ ->disableOutput()
+ ->getProcess();
+
+ $this->assertTrue($process->isOutputDisabled());
+ }
+
+ public function testShouldReturnProcessWithEnabledOutput()
+ {
+ $process = ProcessBuilder::create(array('/usr/bin/php'))
+ ->disableOutput()
+ ->enableOutput()
+ ->getProcess();
+
+ $this->assertFalse($process->isOutputDisabled());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings, Traversable objects or stream resources.
+ */
+ public function testInvalidInput()
+ {
+ $builder = ProcessBuilder::create();
+ $builder->setInput(array());
+ }
+
+ public function testDoesNotPrefixExec()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test cannot run on Windows.');
+ }
+
+ $builder = ProcessBuilder::create(array('command', '-v', 'ls'));
+ $process = $builder->getProcess();
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
new file mode 100644
index 0000000..5f73915
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php
@@ -0,0 +1,135 @@
+<?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(array('isSuccessful'))->setConstructorArgs(array('php'))->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->will($this->returnValue(true));
+
+ $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}(
+ '\InvalidArgumentException',
+ '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(array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'))->setConstructorArgs(array($cmd))->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->will($this->returnValue(false));
+
+ $process->expects($this->once())
+ ->method('getOutput')
+ ->will($this->returnValue($output));
+
+ $process->expects($this->once())
+ ->method('getErrorOutput')
+ ->will($this->returnValue($errorOutput));
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->will($this->returnValue($exitCode));
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->will($this->returnValue($exitText));
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->will($this->returnValue(false));
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->will($this->returnValue($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}",
+ $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(array('isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'))->setConstructorArgs(array($cmd))->getMock();
+ $process->expects($this->once())
+ ->method('isSuccessful')
+ ->will($this->returnValue(false));
+
+ $process->expects($this->never())
+ ->method('getOutput');
+
+ $process->expects($this->never())
+ ->method('getErrorOutput');
+
+ $process->expects($this->once())
+ ->method('getExitCode')
+ ->will($this->returnValue($exitCode));
+
+ $process->expects($this->once())
+ ->method('getExitCodeText')
+ ->will($this->returnValue($exitText));
+
+ $process->expects($this->once())
+ ->method('isOutputDisabled')
+ ->will($this->returnValue(true));
+
+ $process->expects($this->once())
+ ->method('getWorkingDirectory')
+ ->will($this->returnValue($workingDirectory));
+
+ $exception = new ProcessFailedException($process);
+
+ $this->assertEquals(
+ "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}",
+ $exception->getMessage()
+ );
+ }
+}
diff --git a/vendor/symfony/process/Tests/ProcessTest.php b/vendor/symfony/process/Tests/ProcessTest.php
new file mode 100644
index 0000000..2672571
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessTest.php
@@ -0,0 +1,1591 @@
+<?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\LogicException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\InputStream;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Process;
+
+/**
+ * @author Robert Schönthal <seroscho@googlemail.com>
+ */
+class ProcessTest extends TestCase
+{
+ private static $phpBin;
+ private static $process;
+ private static $sigchild;
+ private static $notEnhancedSigchild = false;
+
+ public static function setUpBeforeClass()
+ {
+ $phpBin = new PhpExecutableFinder();
+ self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find());
+
+ ob_start();
+ phpinfo(INFO_GENERAL);
+ self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+ }
+
+ protected function tearDown()
+ {
+ if (self::$process) {
+ self::$process->stop(0);
+ self::$process = null;
+ }
+ }
+
+ public function testThatProcessDoesNotThrowWarningDuringRun()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is transient on Windows');
+ }
+ @trigger_error('Test Error', E_USER_NOTICE);
+ $process = $this->getProcessForCode('sleep(3)');
+ $process->run();
+ $actualError = error_get_last();
+ $this->assertEquals('Test Error', $actualError['message']);
+ $this->assertEquals(E_USER_NOTICE, $actualError['type']);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromConstructor()
+ {
+ $this->getProcess('', null, null, null, -1);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ */
+ public function testNegativeTimeoutFromSetter()
+ {
+ $p = $this->getProcess('');
+ $p->setTimeout(-1);
+ }
+
+ public function testFloatAndNullTimeout()
+ {
+ $p = $this->getProcess('');
+
+ $p->setTimeout(10);
+ $this->assertSame(10.0, $p->getTimeout());
+
+ $p->setTimeout(null);
+ $this->assertNull($p->getTimeout());
+
+ $p->setTimeout(0.0);
+ $this->assertNull($p->getTimeout());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testStopWithTimeoutIsActuallyWorking()
+ {
+ $p = $this->getProcess(array(self::$phpBin, __DIR__.'/NonStopableProcess.php', 30));
+ $p->start();
+
+ while (false === strpos($p->getOutput(), 'received')) {
+ usleep(1000);
+ }
+ $start = microtime(true);
+ $p->stop(0.1);
+
+ $p->wait();
+
+ $this->assertLessThan(15, microtime(true) - $start);
+ }
+
+ public function testAllOutputIsActuallyReadOnTermination()
+ {
+ // this code will result in a maximum of 2 reads of 8192 bytes by calling
+ // start() and isRunning(). by the time getOutput() is called the process
+ // has terminated so the internal pipes array is already empty. normally
+ // the call to start() will not read any data as the process will not have
+ // generated output, but this is non-deterministic so we must count it as
+ // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus
+ // another byte which will never be read.
+ $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
+
+ $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
+ $p = $this->getProcessForCode($code);
+
+ $p->start();
+
+ // Don't call Process::run nor Process::wait to avoid any read of pipes
+ $h = new \ReflectionProperty($p, 'process');
+ $h->setAccessible(true);
+ $h = $h->getValue($p);
+ $s = @proc_get_status($h);
+
+ while (!empty($s['running'])) {
+ usleep(1000);
+ $s = proc_get_status($h);
+ }
+
+ $o = $p->getOutput();
+
+ $this->assertEquals($expectedOutputSize, strlen($o));
+ }
+
+ public function testCallbacksAreExecutedWithStart()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->start(function ($type, $buffer) use (&$data) {
+ $data .= $buffer;
+ });
+
+ $process->wait();
+
+ $this->assertSame('foo'.PHP_EOL, $data);
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider responsesCodeProvider
+ */
+ public function testProcessResponses($expected, $getter, $code)
+ {
+ $p = $this->getProcessForCode($code);
+ $p->run();
+
+ $this->assertSame($expected, $p->$getter());
+ }
+
+ /**
+ * tests results from sub processes.
+ *
+ * @dataProvider pipesCodeProvider
+ */
+ public function testProcessPipes($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($expected);
+ $p->run();
+
+ $this->assertEquals($expectedLength, strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
+ }
+
+ /**
+ * @dataProvider pipesCodeProvider
+ */
+ public function testSetStreamAsInput($code, $size)
+ {
+ $expected = str_repeat(str_repeat('*', 1024), $size).'!';
+ $expectedLength = (1024 * $size) + 1;
+
+ $stream = fopen('php://temporary', 'w+');
+ fwrite($stream, $expected);
+ rewind($stream);
+
+ $p = $this->getProcessForCode($code);
+ $p->setInput($stream);
+ $p->run();
+
+ fclose($stream);
+
+ $this->assertEquals($expectedLength, strlen($p->getOutput()));
+ $this->assertEquals($expectedLength, strlen($p->getErrorOutput()));
+ }
+
+ public function testLiveStreamAsInput()
+ {
+ $stream = fopen('php://memory', 'r+');
+ fwrite($stream, 'hello');
+ rewind($stream);
+
+ $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p->setInput($stream);
+ $p->start(function ($type, $data) use ($stream) {
+ if ('hello' === $data) {
+ fclose($stream);
+ }
+ });
+ $p->wait();
+
+ $this->assertSame('hello', $p->getOutput());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Input can not be set while the process is running.
+ */
+ public function testSetInputWhileRunningThrowsAnException()
+ {
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->start();
+ try {
+ $process->setInput('foobar');
+ $process->stop();
+ $this->fail('A LogicException should have been raised.');
+ } catch (LogicException $e) {
+ }
+ $process->stop();
+
+ throw $e;
+ }
+
+ /**
+ * @dataProvider provideInvalidInputValues
+ * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
+ * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.
+ */
+ public function testInvalidInput($value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ }
+
+ public function provideInvalidInputValues()
+ {
+ return array(
+ array(array()),
+ array(new NonStringifiable()),
+ );
+ }
+
+ /**
+ * @dataProvider provideInputValues
+ */
+ public function testValidInput($expected, $value)
+ {
+ $process = $this->getProcess('foo');
+ $process->setInput($value);
+ $this->assertSame($expected, $process->getInput());
+ }
+
+ public function provideInputValues()
+ {
+ return array(
+ array(null, null),
+ array('24.5', 24.5),
+ array('input data', 'input data'),
+ );
+ }
+
+ public function chainedCommandsOutputProvider()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return array(
+ array("2 \r\n2\r\n", '&&', '2'),
+ );
+ }
+
+ return array(
+ array("1\n1\n", ';', '1'),
+ array("2\n2\n", '&&', '2'),
+ );
+ }
+
+ /**
+ * @dataProvider chainedCommandsOutputProvider
+ */
+ public function testChainedCommandsOutput($expected, $operator, $input)
+ {
+ $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input));
+ $process->run();
+ $this->assertEquals($expected, $process->getOutput());
+ }
+
+ public function testCallbackIsExecutedForOutput()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = $buffer === 'foo';
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
+ {
+ $p = $this->getProcessForCode('echo \'foo\';');
+ $p->disableOutput();
+
+ $called = false;
+ $p->run(function ($type, $buffer) use (&$called) {
+ $called = $buffer === 'foo';
+ });
+
+ $this->assertTrue($called, 'The callback should be executed with the output');
+ }
+
+ public function testGetErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
+ }
+
+ public function testFlushErrorOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
+
+ $p->run();
+ $p->clearErrorOutput();
+ $this->assertEmpty($p->getErrorOutput());
+ }
+
+ /**
+ * @dataProvider provideIncrementalOutput
+ */
+ public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri)
+ {
+ $lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
+
+ $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');');
+
+ $h = fopen($lock, 'w');
+ flock($h, LOCK_EX);
+
+ $p->start();
+
+ foreach (array('foo', 'bar') as $s) {
+ while (false === strpos($p->$getOutput(), $s)) {
+ usleep(1000);
+ }
+
+ $this->assertSame($s, $p->$getIncrementalOutput());
+ $this->assertSame('', $p->$getIncrementalOutput());
+
+ flock($h, LOCK_UN);
+ }
+
+ fclose($h);
+ }
+
+ public function provideIncrementalOutput()
+ {
+ return array(
+ array('getOutput', 'getIncrementalOutput', 'php://stdout'),
+ array('getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'),
+ );
+ }
+
+ public function testGetOutput()
+ {
+ $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
+
+ $p->run();
+ $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
+ }
+
+ public function testFlushOutput()
+ {
+ $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
+
+ $p->run();
+ $p->clearOutput();
+ $this->assertEmpty($p->getOutput());
+ }
+
+ public function testZeroAsOutput()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line
+ $p = $this->getProcess('echo | set /p dummyName=0');
+ } else {
+ $p = $this->getProcess('printf 0');
+ }
+
+ $p->run();
+ $this->assertSame('0', $p->getOutput());
+ }
+
+ public function testExitCodeCommandFailed()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX exit code');
+ }
+ $this->skipIfNotEnhancedSigchild();
+
+ // such command run in bash return an exitcode 127
+ $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis');
+ $process->run();
+
+ $this->assertGreaterThan(0, $process->getExitCode());
+ }
+
+ /**
+ * @group tty
+ */
+ public function testTTYCommand()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not have /dev/tty support');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
+ $process->setTty(true);
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->wait();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ /**
+ * @group tty
+ */
+ public function testTTYCommandExitCode()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does have /dev/tty support');
+ }
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(true);
+ $process->run();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage TTY mode is not supported on Windows platform.
+ */
+ public function testTTYInWindowsEnvironment()
+ {
+ if ('\\' !== DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('This test is for Windows platform only');
+ }
+
+ $process = $this->getProcess('echo "foo" >> /dev/null');
+ $process->setTty(false);
+ $process->setTty(true);
+ }
+
+ public function testExitCodeTextIsNullWhenExitCodeIsNull()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('');
+ $this->assertNull($process->getExitCodeText());
+ }
+
+ public function testPTYCommand()
+ {
+ if (!Process::isPtySupported()) {
+ $this->markTestSkipped('PTY is not supported on this operating system.');
+ }
+
+ $process = $this->getProcess('echo "foo"');
+ $process->setPty(true);
+ $process->run();
+
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ $this->assertEquals("foo\r\n", $process->getOutput());
+ }
+
+ public function testMustRun()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo foo');
+
+ $this->assertSame($process, $process->mustRun());
+ $this->assertEquals('foo'.PHP_EOL, $process->getOutput());
+ }
+
+ public function testSuccessfulMustRunHasCorrectExitCode()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo foo')->mustRun();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException
+ */
+ public function testMustRunThrowsException()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('exit 1');
+ $process->mustRun();
+ }
+
+ public function testExitCodeText()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('');
+ $r = new \ReflectionObject($process);
+ $p = $r->getProperty('exitcode');
+ $p->setAccessible(true);
+
+ $p->setValue($process, 2);
+ $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText());
+ }
+
+ public function testStartIsNonBlocking()
+ {
+ $process = $this->getProcessForCode('usleep(500000);');
+ $start = microtime(true);
+ $process->start();
+ $end = microtime(true);
+ $this->assertLessThan(0.4, $end - $start);
+ $process->stop();
+ }
+
+ public function testUpdateStatus()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertTrue(strlen($process->getOutput()) > 0);
+ }
+
+ public function testGetExitCodeIsNullOnStart()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertNull($process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCodeIsNullOnWhenStartingAgain()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->run();
+ $this->assertEquals(0, $process->getExitCode());
+ $process->start();
+ $this->assertNull($process->getExitCode());
+ $process->wait();
+ $this->assertEquals(0, $process->getExitCode());
+ }
+
+ public function testGetExitCode()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertSame(0, $process->getExitCode());
+ }
+
+ public function testStatus()
+ {
+ $process = $this->getProcessForCode('usleep(100000);');
+ $this->assertFalse($process->isRunning());
+ $this->assertFalse($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_READY, $process->getStatus());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertFalse($process->isTerminated());
+ $this->assertSame(Process::STATUS_STARTED, $process->getStatus());
+ $process->wait();
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->isStarted());
+ $this->assertTrue($process->isTerminated());
+ $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
+ }
+
+ public function testStop()
+ {
+ $process = $this->getProcessForCode('sleep(31);');
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop();
+ $this->assertFalse($process->isRunning());
+ }
+
+ public function testIsSuccessful()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsSuccessfulOnlyAfterTerminated()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcessForCode('usleep(100000);');
+ $process->start();
+
+ $this->assertFalse($process->isSuccessful());
+
+ $process->wait();
+
+ $this->assertTrue($process->isSuccessful());
+ }
+
+ public function testIsNotSuccessful()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
+ $process->run();
+ $this->assertFalse($process->isSuccessful());
+ }
+
+ public function testProcessIsNotSignaled()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertFalse($process->hasBeenSignaled());
+ }
+
+ public function testProcessWithoutTermSignal()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertEquals(0, $process->getTermSignal());
+ }
+
+ public function testProcessIsSignaledIfStopped()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('Windows does not support POSIX signals');
+ }
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcessForCode('sleep(32);');
+ $process->start();
+ $process->stop();
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertEquals(15, $process->getTermSignal()); // SIGTERM
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage The process has been signaled
+ */
+ public function testProcessThrowsExceptionWhenExternallySignaled()
+ {
+ if (!function_exists('posix_kill')) {
+ $this->markTestSkipped('Function posix_kill is required.');
+ }
+ $this->skipIfNotEnhancedSigchild(false);
+
+ $process = $this->getProcessForCode('sleep(32.1);');
+ $process->start();
+ posix_kill($process->getPid(), 9); // SIGKILL
+
+ $process->wait();
+ }
+
+ public function testRestart()
+ {
+ $process1 = $this->getProcessForCode('echo getmypid();');
+ $process1->run();
+ $process2 = $process1->restart();
+
+ $process2->wait(); // wait for output
+
+ // Ensure that both processed finished and the output is numeric
+ $this->assertFalse($process1->isRunning());
+ $this->assertFalse($process2->isRunning());
+ $this->assertInternalType('numeric', $process1->getOutput());
+ $this->assertInternalType('numeric', $process2->getOutput());
+
+ // Ensure that restart returned a new process by check that the output is different
+ $this->assertNotEquals($process1->getOutput(), $process2->getOutput());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testRunProcessWithTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->run();
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testIterateOverProcessWithTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(30);');
+ $process->setTimeout(0.1);
+ $start = microtime(true);
+ try {
+ $process->start();
+ foreach ($process as $buffer);
+ $this->fail('A RuntimeException should have been raised');
+ } catch (RuntimeException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testCheckTimeoutOnNonStartedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $this->assertNull($process->checkTimeout());
+ }
+
+ public function testCheckTimeoutOnTerminatedProcess()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->checkTimeout());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testCheckTimeoutOnStartedProcess()
+ {
+ $process = $this->getProcessForCode('sleep(33);');
+ $process->setTimeout(0.1);
+
+ $process->start();
+ $start = microtime(true);
+
+ try {
+ while ($process->isRunning()) {
+ $process->checkTimeout();
+ usleep(100000);
+ }
+ $this->fail('A ProcessTimedOutException should have been raised');
+ } catch (ProcessTimedOutException $e) {
+ }
+
+ $this->assertLessThan(15, microtime(true) - $start);
+
+ throw $e;
+ }
+
+ public function testIdleTimeout()
+ {
+ $process = $this->getProcessForCode('sleep(34);');
+ $process->setTimeout(60);
+ $process->setIdleTimeout(0.1);
+
+ try {
+ $process->run();
+
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isIdleTimeout());
+ $this->assertFalse($e->isGeneralTimeout());
+ $this->assertEquals(0.1, $e->getExceededTimeout());
+ }
+ }
+
+ public function testIdleTimeoutNotExceededWhenOutputIsSent()
+ {
+ $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
+ $process->setTimeout(1);
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'foo')) {
+ usleep(1000);
+ }
+
+ $process->setIdleTimeout(0.5);
+
+ try {
+ $process->wait();
+ $this->fail('A timeout exception was expected.');
+ } catch (ProcessTimedOutException $e) {
+ $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.');
+ $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.');
+ $this->assertEquals(1, $e->getExceededTimeout());
+ }
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException
+ * @expectedExceptionMessage exceeded the timeout of 0.1 seconds.
+ */
+ public function testStartAfterATimeout()
+ {
+ $process = $this->getProcessForCode('sleep(35);');
+ $process->setTimeout(0.1);
+
+ try {
+ $process->run();
+ $this->fail('A ProcessTimedOutException should have been raised.');
+ } catch (ProcessTimedOutException $e) {
+ }
+ $this->assertFalse($process->isRunning());
+ $process->start();
+ $this->assertTrue($process->isRunning());
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function testGetPid()
+ {
+ $process = $this->getProcessForCode('sleep(36);');
+ $process->start();
+ $this->assertGreaterThan(0, $process->getPid());
+ $process->stop(0);
+ }
+
+ public function testGetPidIsNullBeforeStart()
+ {
+ $process = $this->getProcess('foo');
+ $this->assertNull($process->getPid());
+ }
+
+ public function testGetPidIsNullAfterRun()
+ {
+ $process = $this->getProcess('echo foo');
+ $process->run();
+ $this->assertNull($process->getPid());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testSignal()
+ {
+ $process = $this->getProcess(array(self::$phpBin, __DIR__.'/SignalListener.php'));
+ $process->start();
+
+ while (false === strpos($process->getOutput(), 'Caught')) {
+ usleep(1000);
+ }
+ $process->signal(SIGUSR1);
+ $process->wait();
+
+ $this->assertEquals('Caught SIGUSR1', $process->getOutput());
+ }
+
+ /**
+ * @requires extension pcntl
+ */
+ public function testExitCodeIsAvailableAfterSignal()
+ {
+ $this->skipIfNotEnhancedSigchild();
+
+ $process = $this->getProcess('sleep 4');
+ $process->start();
+ $process->signal(SIGKILL);
+
+ while ($process->isRunning()) {
+ usleep(10000);
+ }
+
+ $this->assertFalse($process->isRunning());
+ $this->assertTrue($process->hasBeenSignaled());
+ $this->assertFalse($process->isSuccessful());
+ $this->assertEquals(137, $process->getExitCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Can not send signal on a non running process.
+ */
+ public function testSignalProcessNotRunning()
+ {
+ $process = $this->getProcess('foo');
+ $process->signal(1); // SIGHUP
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedARunningProcess
+ */
+ public function testMethodsThatNeedARunningProcess($method)
+ {
+ $process = $this->getProcess('foo');
+
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Process\Exception\LogicException');
+ $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method));
+ } else {
+ $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
+ }
+
+ $process->{$method}();
+ }
+
+ public function provideMethodsThatNeedARunningProcess()
+ {
+ return array(
+ array('getOutput'),
+ array('getIncrementalOutput'),
+ array('getErrorOutput'),
+ array('getIncrementalErrorOutput'),
+ array('wait'),
+ );
+ }
+
+ /**
+ * @dataProvider provideMethodsThatNeedATerminatedProcess
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Process must be terminated before calling
+ */
+ public function testMethodsThatNeedATerminatedProcess($method)
+ {
+ $process = $this->getProcessForCode('sleep(37);');
+ $process->start();
+ try {
+ $process->{$method}();
+ $process->stop(0);
+ $this->fail('A LogicException must have been thrown');
+ } catch (\Exception $e) {
+ }
+ $process->stop(0);
+
+ throw $e;
+ }
+
+ public function provideMethodsThatNeedATerminatedProcess()
+ {
+ return array(
+ array('hasBeenSignaled'),
+ array('getTermSignal'),
+ array('hasBeenStopped'),
+ array('getStopSignal'),
+ );
+ }
+
+ /**
+ * @dataProvider provideWrongSignal
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ */
+ public function testWrongSignal($signal)
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ $this->markTestSkipped('POSIX signals do not work on Windows');
+ }
+
+ $process = $this->getProcessForCode('sleep(38);');
+ $process->start();
+ try {
+ $process->signal($signal);
+ $this->fail('A RuntimeException must have been thrown');
+ } catch (RuntimeException $e) {
+ $process->stop(0);
+ }
+
+ throw $e;
+ }
+
+ public function provideWrongSignal()
+ {
+ return array(
+ array(-4),
+ array('Céphalopodes'),
+ );
+ }
+
+ public function testDisableOutputDisablesTheOutput()
+ {
+ $p = $this->getProcess('foo');
+ $this->assertFalse($p->isOutputDisabled());
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ $p->enableOutput();
+ $this->assertFalse($p->isOutputDisabled());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage Disabling output while the process is running is not possible.
+ */
+ public function testDisableOutputWhileRunningThrowsException()
+ {
+ $p = $this->getProcessForCode('sleep(39);');
+ $p->start();
+ $p->disableOutput();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\RuntimeException
+ * @expectedExceptionMessage Enabling output while the process is running is not possible.
+ */
+ public function testEnableOutputWhileRunningThrowsException()
+ {
+ $p = $this->getProcessForCode('sleep(40);');
+ $p->disableOutput();
+ $p->start();
+ $p->enableOutput();
+ }
+
+ public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
+ {
+ $p = $this->getProcess('echo foo');
+ $p->disableOutput();
+ $p->run();
+ $p->enableOutput();
+ $p->disableOutput();
+ $this->assertTrue($p->isOutputDisabled());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Output can not be disabled while an idle timeout is set.
+ */
+ public function testDisableOutputWhileIdleTimeoutIsSet()
+ {
+ $process = $this->getProcess('foo');
+ $process->setIdleTimeout(1);
+ $process->disableOutput();
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage timeout can not be set while the output is disabled.
+ */
+ public function testSetIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $process->setIdleTimeout(1);
+ }
+
+ public function testSetNullIdleTimeoutWhileOutputIsDisabled()
+ {
+ $process = $this->getProcess('foo');
+ $process->disableOutput();
+ $this->assertSame($process, $process->setIdleTimeout(null));
+ }
+
+ /**
+ * @dataProvider provideOutputFetchingMethods
+ * @expectedException \Symfony\Component\Process\Exception\LogicException
+ * @expectedExceptionMessage Output has been disabled.
+ */
+ public function testGetOutputWhileDisabled($fetchMethod)
+ {
+ $p = $this->getProcessForCode('sleep(41);');
+ $p->disableOutput();
+ $p->start();
+ $p->{$fetchMethod}();
+ }
+
+ public function provideOutputFetchingMethods()
+ {
+ return array(
+ array('getOutput'),
+ array('getIncrementalOutput'),
+ array('getErrorOutput'),
+ array('getIncrementalErrorOutput'),
+ );
+ }
+
+ public function testStopTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(42);');
+ $process->run(function () use ($process) {
+ $process->stop();
+ });
+ $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testKillSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(43);');
+ $process->run(function () use ($process) {
+ $process->signal(9); // SIGKILL
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function testTermSignalTerminatesProcessCleanly()
+ {
+ $process = $this->getProcessForCode('echo 123; sleep(44);');
+ $process->run(function () use ($process) {
+ $process->signal(15); // SIGTERM
+ });
+ $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException');
+ }
+
+ public function responsesCodeProvider()
+ {
+ return array(
+ //expected output / getter / code to execute
+ //array(1,'getExitCode','exit(1);'),
+ //array(true,'isSuccessful','exit();'),
+ array('output', 'getOutput', 'echo \'output\';'),
+ );
+ }
+
+ public function pipesCodeProvider()
+ {
+ $variations = array(
+ 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);',
+ 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';',
+ );
+
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650
+ $sizes = array(1, 2, 4, 8);
+ } else {
+ $sizes = array(1, 16, 64, 1024, 4096);
+ }
+
+ $codes = array();
+ foreach ($sizes as $size) {
+ foreach ($variations as $code) {
+ $codes[] = array($code, $size);
+ }
+ }
+
+ return $codes;
+ }
+
+ /**
+ * @dataProvider provideVariousIncrementals
+ */
+ public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
+ {
+ $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null);
+ $process->start();
+ $result = '';
+ $limit = microtime(true) + 3;
+ $expected = '012';
+
+ while ($result !== $expected && microtime(true) < $limit) {
+ $result .= $process->$method();
+ }
+
+ $this->assertSame($expected, $result);
+ $process->stop();
+ }
+
+ public function provideVariousIncrementals()
+ {
+ return array(
+ array('php://stdout', 'getIncrementalOutput'),
+ array('php://stderr', 'getIncrementalErrorOutput'),
+ );
+ }
+
+ public function testIteratorInput()
+ {
+ $input = function () {
+ yield 'ping';
+ yield 'pong';
+ };
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
+ $process->run();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testSimpleInputStream()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);');
+ $process->setInput($input);
+
+ $process->start(function ($type, $data) use ($input) {
+ if ('ping' === $data) {
+ $input->write('pang');
+ } elseif (!$input->isClosed()) {
+ $input->write('pong');
+ $input->close();
+ }
+ });
+
+ $process->wait();
+ $this->assertSame('pingpangpong', $process->getOutput());
+ }
+
+ public function testInputStreamWithCallable()
+ {
+ $i = 0;
+ $stream = fopen('php://memory', 'w+');
+ $stream = function () use ($stream, &$i) {
+ if ($i < 3) {
+ rewind($stream);
+ fwrite($stream, ++$i);
+ rewind($stream);
+
+ return $stream;
+ }
+ };
+
+ $input = new InputStream();
+ $input->onEmpty($stream);
+ $input->write($stream());
+
+ $process = $this->getProcessForCode('echo fread(STDIN, 3);');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ $input->close();
+ });
+
+ $process->wait();
+ $this->assertSame('123', $process->getOutput());
+ }
+
+ public function testInputStreamWithGenerator()
+ {
+ $input = new InputStream();
+ $input->onEmpty(function ($input) {
+ yield 'pong';
+ $input->close();
+ });
+
+ $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $process->setInput($input);
+ $process->start();
+ $input->write('ping');
+ $process->wait();
+ $this->assertSame('pingpong', $process->getOutput());
+ }
+
+ public function testInputStreamOnEmpty()
+ {
+ $i = 0;
+ $input = new InputStream();
+ $input->onEmpty(function () use (&$i) { ++$i; });
+
+ $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
+ $process->setInput($input);
+ $process->start(function ($type, $data) use ($input) {
+ if ('123' === $data) {
+ $input->close();
+ }
+ });
+ $process->wait();
+
+ $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty');
+ $this->assertSame('123456', $process->getOutput());
+ }
+
+ public function testIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);');
+ $process->setInput($input);
+ $process->start();
+ $output = array();
+
+ foreach ($process as $type => $data) {
+ $output[] = array($type, $data);
+ break;
+ }
+ $expectedOutput = array(
+ array($process::OUT, '123'),
+ );
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(345);
+
+ foreach ($process as $type => $data) {
+ $output[] = array($type, $data);
+ }
+
+ $this->assertSame('', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = array(
+ array($process::OUT, '123'),
+ array($process::ERR, '234'),
+ array($process::OUT, '345'),
+ array($process::ERR, '456'),
+ );
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testNonBlockingNorClearingIteratorOutput()
+ {
+ $input = new InputStream();
+
+ $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
+ $process->setInput($input);
+ $process->start();
+ $output = array();
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ $output[] = array($type, $data);
+ break;
+ }
+ $expectedOutput = array(
+ array($process::OUT, ''),
+ );
+ $this->assertSame($expectedOutput, $output);
+
+ $input->write(123);
+
+ foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) {
+ if ('' !== $data) {
+ $output[] = array($type, $data);
+ }
+ }
+
+ $this->assertSame('123', $process->getOutput());
+ $this->assertFalse($process->isRunning());
+
+ $expectedOutput = array(
+ array($process::OUT, ''),
+ array($process::OUT, '123'),
+ );
+ $this->assertSame($expectedOutput, $output);
+ }
+
+ public function testChainedProcesses()
+ {
+ $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
+ $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
+ $p2->setInput($p1);
+
+ $p1->start();
+ $p2->run();
+
+ $this->assertSame('123', $p1->getErrorOutput());
+ $this->assertSame('', $p1->getOutput());
+ $this->assertSame('', $p2->getErrorOutput());
+ $this->assertSame('456', $p2->getOutput());
+ }
+
+ public function testSetBadEnv()
+ {
+ $process = $this->getProcess('echo hello');
+ $process->setEnv(array('bad%%' => '123'));
+ $process->inheritEnvironmentVariables(true);
+
+ $process->run();
+
+ $this->assertSame('hello'.PHP_EOL, $process->getOutput());
+ $this->assertSame('', $process->getErrorOutput());
+ }
+
+ public function testEnvBackupDoesNotDeleteExistingVars()
+ {
+ putenv('existing_var=foo');
+ $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"');
+ $process->setEnv(array('existing_var' => 'bar', 'new_test_var' => 'foo'));
+ $process->inheritEnvironmentVariables();
+
+ $process->run();
+
+ $this->assertSame('foo', $process->getOutput());
+ $this->assertSame('foo', getenv('existing_var'));
+ $this->assertFalse(getenv('new_test_var'));
+ }
+
+ public function testEnvIsInherited()
+ {
+ $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
+
+ putenv('FOO=BAR');
+
+ $process->run();
+
+ $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
+ $env = array_intersect_key(unserialize($process->getOutput()), $expected);
+
+ $this->assertEquals($expected, $env);
+ }
+
+ /**
+ * @group legacy
+ */
+ public function testInheritEnvDisabled()
+ {
+ $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
+
+ putenv('FOO=BAR');
+
+ $this->assertSame($process, $process->inheritEnvironmentVariables(false));
+ $this->assertFalse($process->areEnvironmentVariablesInherited());
+
+ $process->run();
+
+ $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
+ $env = array_intersect_key(unserialize($process->getOutput()), $expected);
+ unset($expected['FOO']);
+
+ $this->assertSame($expected, $env);
+ }
+
+ public function testGetCommandLine()
+ {
+ $p = new Process(array('/usr/bin/php'));
+
+ $expected = '\\' === DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'";
+ $this->assertSame($expected, $p->getCommandLine());
+ }
+
+ /**
+ * @dataProvider provideEscapeArgument
+ */
+ public function testEscapeArgument($arg)
+ {
+ $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg));
+ $p->run();
+
+ $this->assertSame($arg, $p->getOutput());
+ }
+
+ /**
+ * @dataProvider provideEscapeArgument
+ * @group legacy
+ */
+ public function testEscapeArgumentWhenInheritEnvDisabled($arg)
+ {
+ $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg), null, array('BAR' => 'BAZ'));
+ $p->inheritEnvironmentVariables(false);
+ $p->run();
+
+ $this->assertSame($arg, $p->getOutput());
+ }
+
+ public function testRawCommandLine()
+ {
+ $p = new Process(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);')));
+ $p->run();
+
+ $expected = <<<EOTXT
+Array
+(
+ [0] => -
+ [1] => a
+ [2] =>
+ [3] => b
+)
+
+EOTXT;
+ $this->assertSame($expected, $p->getOutput());
+ }
+
+ public function provideEscapeArgument()
+ {
+ yield array('a"b%c%');
+ yield array('a"b^c^');
+ yield array("a\nb'c");
+ yield array('a^b c!');
+ yield array("a!b\tc");
+ yield array('a\\\\"\\"');
+ yield array('éÉèÈàÀöä');
+ }
+
+ public function testEnvArgument()
+ {
+ $env = array('FOO' => 'Foo', 'BAR' => 'Bar');
+ $cmd = '\\' === DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ';
+ $p = new Process($cmd, null, $env);
+ $p->run(null, array('BAR' => 'baR', 'BAZ' => 'baZ'));
+
+ $this->assertSame('Foo baR baZ', rtrim($p->getOutput()));
+ $this->assertSame($env, $p->getEnv());
+ }
+
+ /**
+ * @param string $commandline
+ * @param null|string $cwd
+ * @param null|array $env
+ * @param null|string $input
+ * @param int $timeout
+ * @param array $options
+ *
+ * @return Process
+ */
+ private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60)
+ {
+ $process = new Process($commandline, $cwd, $env, $input, $timeout);
+ $process->inheritEnvironmentVariables();
+
+ if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) {
+ try {
+ $process->setEnhanceSigchildCompatibility(false);
+ $process->getExitCode();
+ $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.');
+ } catch (RuntimeException $e) {
+ $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage());
+ if ($enhance) {
+ $process->setEnhanceSigchildCompatibility(true);
+ } else {
+ self::$notEnhancedSigchild = true;
+ }
+ }
+ }
+
+ if (self::$process) {
+ self::$process->stop(0);
+ }
+
+ return self::$process = $process;
+ }
+
+ /**
+ * @return Process
+ */
+ private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60)
+ {
+ return $this->getProcess(array(self::$phpBin, '-r', $code), $cwd, $env, $input, $timeout);
+ }
+
+ private function skipIfNotEnhancedSigchild($expectException = true)
+ {
+ if (self::$sigchild) {
+ if (!$expectException) {
+ $this->markTestSkipped('PHP is compiled with --enable-sigchild.');
+ } elseif (self::$notEnhancedSigchild) {
+ if (method_exists($this, 'expectException')) {
+ $this->expectException('Symfony\Component\Process\Exception\RuntimeException');
+ $this->expectExceptionMessage('This PHP has been compiled with --enable-sigchild.');
+ } else {
+ $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.');
+ }
+ }
+ }
+ }
+}
+
+class NonStringifiable
+{
+}
diff --git a/vendor/symfony/process/Tests/ProcessUtilsTest.php b/vendor/symfony/process/Tests/ProcessUtilsTest.php
new file mode 100644
index 0000000..82fd8cf
--- /dev/null
+++ b/vendor/symfony/process/Tests/ProcessUtilsTest.php
@@ -0,0 +1,53 @@
+<?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\ProcessUtils;
+
+/**
+ * @group legacy
+ */
+class ProcessUtilsTest extends TestCase
+{
+ /**
+ * @dataProvider dataArguments
+ */
+ public function testEscapeArgument($result, $argument)
+ {
+ $this->assertSame($result, ProcessUtils::escapeArgument($argument));
+ }
+
+ public function dataArguments()
+ {
+ if ('\\' === DIRECTORY_SEPARATOR) {
+ return array(
+ array('"\"php\" \"-v\""', '"php" "-v"'),
+ array('"foo bar"', 'foo bar'),
+ array('^%"path"^%', '%path%'),
+ array('"<|>\\" \\"\'f"', '<|>" "\'f'),
+ array('""', ''),
+ array('"with\trailingbs\\\\"', 'with\trailingbs\\'),
+ );
+ }
+
+ return array(
+ array("'\"php\" \"-v\"'", '"php" "-v"'),
+ array("'foo bar'", 'foo bar'),
+ array("'%path%'", '%path%'),
+ array("'<|>\" \"'\\''f'", '<|>" "\'f'),
+ array("''", ''),
+ array("'with\\trailingbs\\'", 'with\trailingbs\\'),
+ array("'withNonAsciiAccentLikeéÉèÈàÀöä'", 'withNonAsciiAccentLikeéÉèÈàÀöä'),
+ );
+ }
+}
diff --git a/vendor/symfony/process/Tests/SignalListener.php b/vendor/symfony/process/Tests/SignalListener.php
new file mode 100644
index 0000000..9e30ce3
--- /dev/null
+++ b/vendor/symfony/process/Tests/SignalListener.php
@@ -0,0 +1,21 @@
+<?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();
+}
diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json
new file mode 100644
index 0000000..f899c52
--- /dev/null
+++ b/vendor/symfony/process/composer.json
@@ -0,0 +1,33 @@
+{
+ "name": "symfony/process",
+ "type": "library",
+ "description": "Symfony Process Component",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.5.9"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.3-dev"
+ }
+ }
+}
diff --git a/vendor/symfony/process/phpunit.xml.dist b/vendor/symfony/process/phpunit.xml.dist
new file mode 100644
index 0000000..d388467
--- /dev/null
+++ b/vendor/symfony/process/phpunit.xml.dist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/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>