PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.3
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.3
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / symfony / process / Process.php

Process.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.3, at vendor/symfony/process/Process.php

1,747 lines 54.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Process;
13
14 use Symfony\Component\Process\Exception\InvalidArgumentException;
15 use Symfony\Component\Process\Exception\LogicException;
16 use Symfony\Component\Process\Exception\ProcessFailedException;
17 use Symfony\Component\Process\Exception\ProcessTimedOutException;
18 use Symfony\Component\Process\Exception\RuntimeException;
19 use Symfony\Component\Process\Pipes\PipesInterface;
20 use Symfony\Component\Process\Pipes\UnixPipes;
21 use Symfony\Component\Process\Pipes\WindowsPipes;
22
23 /**
24 * Process is a thin wrapper around proc_* functions to easily
25 * start independent PHP processes.
26 *
27 * @author Fabien Potencier <[email protected]>
28 * @author Romain Neutron <[email protected]>
29 */
30 class Process implements \IteratorAggregate
31 {
32 const ERR = 'err';
33 const OUT = 'out';
34
35 const STATUS_READY = 'ready';
36 const STATUS_STARTED = 'started';
37 const STATUS_TERMINATED = 'terminated';
38
39 const STDIN = 0;
40 const STDOUT = 1;
41 const STDERR = 2;
42
43 // Timeout Precision in seconds.
44 const TIMEOUT_PRECISION = 0.2;
45
46 const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
47 const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
48 const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
49 const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
50
51 private $callback;
52 private $hasCallback = false;
53 private $commandline;
54 private $cwd;
55 private $env;
56 private $input;
57 private $starttime;
58 private $lastOutputTime;
59 private $timeout;
60 private $idleTimeout;
61 private $options = ['suppress_errors' => true];
62 private $exitcode;
63 private $fallbackStatus = [];
64 private $processInformation;
65 private $outputDisabled = false;
66 private $stdout;
67 private $stderr;
68 private $enhanceWindowsCompatibility = true;
69 private $enhanceSigchildCompatibility;
70 private $process;
71 private $status = self::STATUS_READY;
72 private $incrementalOutputOffset = 0;
73 private $incrementalErrorOutputOffset = 0;
74 private $tty = false;
75 private $pty;
76 private $inheritEnv = false;
77
78 private $useFileHandles = false;
79 /** @var PipesInterface */
80 private $processPipes;
81
82 private $latestSignal;
83
84 private static $sigchild;
85
86 /**
87 * Exit codes translation table.
88 *
89 * User-defined errors must use exit codes in the 64-113 range.
90 */
91 public static $exitCodes = [
92 0 => 'OK',
93 1 => 'General error',
94 2 => 'Misuse of shell builtins',
95
96 126 => 'Invoked command cannot execute',
97 127 => 'Command not found',
98 128 => 'Invalid exit argument',
99
100 // signals
101 129 => 'Hangup',
102 130 => 'Interrupt',
103 131 => 'Quit and dump core',
104 132 => 'Illegal instruction',
105 133 => 'Trace/breakpoint trap',
106 134 => 'Process aborted',
107 135 => 'Bus error: "access to undefined portion of memory object"',
108 136 => 'Floating point exception: "erroneous arithmetic operation"',
109 137 => 'Kill (terminate immediately)',
110 138 => 'User-defined 1',
111 139 => 'Segmentation violation',
112 140 => 'User-defined 2',
113 141 => 'Write to pipe with no one reading',
114 142 => 'Signal raised by alarm',
115 143 => 'Termination (request to terminate)',
116 // 144 - not defined
117 145 => 'Child process terminated, stopped (or continued*)',
118 146 => 'Continue if stopped',
119 147 => 'Stop executing temporarily',
120 148 => 'Terminal stop signal',
121 149 => 'Background process attempting to read from tty ("in")',
122 150 => 'Background process attempting to write to tty ("out")',
123 151 => 'Urgent data available on socket',
124 152 => 'CPU time limit exceeded',
125 153 => 'File size limit exceeded',
126 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
127 155 => 'Profiling timer expired',
128 // 156 - not defined
129 157 => 'Pollable event',
130 // 158 - not defined
131 159 => 'Bad syscall',
132 ];
133
134 /**
135 * @param string|array $commandline The command line to run
136 * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
137 * @param array|null $env The environment variables or null to use the same environment as the current PHP process
138 * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
139 * @param int|float|null $timeout The timeout in seconds or null to disable
140 * @param array $options An array of options for proc_open
141 *
142 * @throws RuntimeException When proc_open is not installed
143 */
144 public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
145 {
146 if (!\function_exists('proc_open')) {
147 throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
148 }
149
150 $this->commandline = $commandline;
151 $this->cwd = $cwd;
152
153 // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
154 // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
155 // @see : https://bugs.php.net/51800
156 // @see : https://bugs.php.net/50524
157 if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
158 $this->cwd = getcwd();
159 }
160 if (null !== $env) {
161 $this->setEnv($env);
162 }
163
164 $this->setInput($input);
165 $this->setTimeout($timeout);
166 $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
167 $this->pty = false;
168 $this->enhanceSigchildCompatibility = '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
169 if (null !== $options) {
170 @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED);
171 $this->options = array_replace($this->options, $options);
172 }
173 }
174
175 public function __destruct()
176 {
177 $this->stop(0);
178 }
179
180 public function __clone()
181 {
182 $this->resetProcessData();
183 }
184
185 /**
186 * Runs the process.
187 *
188 * The callback receives the type of output (out or err) and
189 * some bytes from the output in real-time. It allows to have feedback
190 * from the independent process during execution.
191 *
192 * The STDOUT and STDERR are also available after the process is finished
193 * via the getOutput() and getErrorOutput() methods.
194 *
195 * @param callable|null $callback A PHP callback to run whenever there is some
196 * output available on STDOUT or STDERR
197 *
198 * @return int The exit status code
199 *
200 * @throws RuntimeException When process can't be launched
201 * @throws RuntimeException When process stopped after receiving signal
202 * @throws LogicException In case a callback is provided and output has been disabled
203 *
204 * @final since version 3.3
205 */
206 public function run($callback = null/*, array $env = []*/)
207 {
208 $env = 1 < \func_num_args() ? func_get_arg(1) : null;
209 $this->start($callback, $env);
210
211 return $this->wait();
212 }
213
214 /**
215 * Runs the process.
216 *
217 * This is identical to run() except that an exception is thrown if the process
218 * exits with a non-zero exit code.
219 *
220 * @return $this
221 *
222 * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
223 * @throws ProcessFailedException if the process didn't terminate successfully
224 *
225 * @final since version 3.3
226 */
227 public function mustRun(callable $callback = null/*, array $env = []*/)
228 {
229 if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
230 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
231 }
232 $env = 1 < \func_num_args() ? func_get_arg(1) : null;
233
234 if (0 !== $this->run($callback, $env)) {
235 throw new ProcessFailedException($this);
236 }
237
238 return $this;
239 }
240
241 /**
242 * Starts the process and returns after writing the input to STDIN.
243 *
244 * This method blocks until all STDIN data is sent to the process then it
245 * returns while the process runs in the background.
246 *
247 * The termination of the process can be awaited with wait().
248 *
249 * The callback receives the type of output (out or err) and some bytes from
250 * the output in real-time while writing the standard input to the process.
251 * It allows to have feedback from the independent process during execution.
252 *
253 * @param callable|null $callback A PHP callback to run whenever there is some
254 * output available on STDOUT or STDERR
255 *
256 * @throws RuntimeException When process can't be launched
257 * @throws RuntimeException When process is already running
258 * @throws LogicException In case a callback is provided and output has been disabled
259 */
260 public function start(callable $callback = null/*, array $env = [*/)
261 {
262 if ($this->isRunning()) {
263 throw new RuntimeException('Process is already running.');
264 }
265 if (2 <= \func_num_args()) {
266 $env = func_get_arg(1);
267 } else {
268 if (__CLASS__ !== static::class) {
269 $r = new \ReflectionMethod($this, __FUNCTION__);
270 if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) {
271 @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), \E_USER_DEPRECATED);
272 }
273 }
274 $env = null;
275 }
276
277 $this->resetProcessData();
278 $this->starttime = $this->lastOutputTime = microtime(true);
279 $this->callback = $this->buildCallback($callback);
280 $this->hasCallback = null !== $callback;
281 $descriptors = $this->getDescriptors();
282 $inheritEnv = $this->inheritEnv;
283
284 if (\is_array($commandline = $this->commandline)) {
285 $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
286
287 if ('\\' !== \DIRECTORY_SEPARATOR) {
288 // exec is mandatory to deal with sending a signal to the process
289 $commandline = 'exec '.$commandline;
290 }
291 }
292
293 if (null === $env) {
294 $env = $this->env;
295 } else {
296 if ($this->env) {
297 $env += $this->env;
298 }
299 $inheritEnv = true;
300 }
301
302 if (null !== $env && $inheritEnv) {
303 $env += $this->getDefaultEnv();
304 } elseif (null !== $env) {
305 @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);
306 } else {
307 $env = $this->getDefaultEnv();
308 }
309 if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
310 $this->options['bypass_shell'] = true;
311 $commandline = $this->prepareWindowsCommandLine($commandline, $env);
312 } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
313 // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
314 $descriptors[3] = ['pipe', 'w'];
315
316 // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
317 $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
318 $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
319
320 // Workaround for the bug, when PTS functionality is enabled.
321 // @see : https://bugs.php.net/69442
322 $ptsWorkaround = fopen(__FILE__, 'r');
323 }
324 if (\defined('HHVM_VERSION')) {
325 $envPairs = $env;
326 } else {
327 $envPairs = [];
328 foreach ($env as $k => $v) {
329 if (false !== $v) {
330 $envPairs[] = $k.'='.$v;
331 }
332 }
333 }
334
335 if (!is_dir($this->cwd)) {
336 @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
337 }
338
339 $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
340
341 if (!\is_resource($this->process)) {
342 throw new RuntimeException('Unable to launch a new process.');
343 }
344 $this->status = self::STATUS_STARTED;
345
346 if (isset($descriptors[3])) {
347 $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
348 }
349
350 if ($this->tty) {
351 return;
352 }
353
354 $this->updateStatus(false);
355 $this->checkTimeout();
356 }
357
358 /**
359 * Restarts the process.
360 *
361 * Be warned that the process is cloned before being started.
362 *
363 * @param callable|null $callback A PHP callback to run whenever there is some
364 * output available on STDOUT or STDERR
365 *
366 * @return static
367 *
368 * @throws RuntimeException When process can't be launched
369 * @throws RuntimeException When process is already running
370 *
371 * @see start()
372 *
373 * @final since version 3.3
374 */
375 public function restart(callable $callback = null/*, array $env = []*/)
376 {
377 if ($this->isRunning()) {
378 throw new RuntimeException('Process is already running.');
379 }
380 $env = 1 < \func_num_args() ? func_get_arg(1) : null;
381
382 $process = clone $this;
383 $process->start($callback, $env);
384
385 return $process;
386 }
387
388 /**
389 * Waits for the process to terminate.
390 *
391 * The callback receives the type of output (out or err) and some bytes
392 * from the output in real-time while writing the standard input to the process.
393 * It allows to have feedback from the independent process during execution.
394 *
395 * @param callable|null $callback A valid PHP callback
396 *
397 * @return int The exitcode of the process
398 *
399 * @throws RuntimeException When process timed out
400 * @throws RuntimeException When process stopped after receiving signal
401 * @throws LogicException When process is not yet started
402 */
403 public function wait(callable $callback = null)
404 {
405 $this->requireProcessIsStarted(__FUNCTION__);
406
407 $this->updateStatus(false);
408
409 if (null !== $callback) {
410 if (!$this->processPipes->haveReadSupport()) {
411 $this->stop(0);
412 throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait.');
413 }
414 $this->callback = $this->buildCallback($callback);
415 }
416
417 do {
418 $this->checkTimeout();
419 $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
420 $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
421 } while ($running);
422
423 while ($this->isRunning()) {
424 $this->checkTimeout();
425 usleep(1000);
426 }
427
428 if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
429 throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
430 }
431
432 return $this->exitcode;
433 }
434
435 /**
436 * Returns the Pid (process identifier), if applicable.
437 *
438 * @return int|null The process id if running, null otherwise
439 */
440 public function getPid()
441 {
442 return $this->isRunning() ? $this->processInformation['pid'] : null;
443 }
444
445 /**
446 * Sends a POSIX signal to the process.
447 *
448 * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
449 *
450 * @return $this
451 *
452 * @throws LogicException In case the process is not running
453 * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
454 * @throws RuntimeException In case of failure
455 */
456 public function signal($signal)
457 {
458 $this->doSignal($signal, true);
459
460 return $this;
461 }
462
463 /**
464 * Disables fetching output and error output from the underlying process.
465 *
466 * @return $this
467 *
468 * @throws RuntimeException In case the process is already running
469 * @throws LogicException if an idle timeout is set
470 */
471 public function disableOutput()
472 {
473 if ($this->isRunning()) {
474 throw new RuntimeException('Disabling output while the process is running is not possible.');
475 }
476 if (null !== $this->idleTimeout) {
477 throw new LogicException('Output can not be disabled while an idle timeout is set.');
478 }
479
480 $this->outputDisabled = true;
481
482 return $this;
483 }
484
485 /**
486 * Enables fetching output and error output from the underlying process.
487 *
488 * @return $this
489 *
490 * @throws RuntimeException In case the process is already running
491 */
492 public function enableOutput()
493 {
494 if ($this->isRunning()) {
495 throw new RuntimeException('Enabling output while the process is running is not possible.');
496 }
497
498 $this->outputDisabled = false;
499
500 return $this;
501 }
502
503 /**
504 * Returns true in case the output is disabled, false otherwise.
505 *
506 * @return bool
507 */
508 public function isOutputDisabled()
509 {
510 return $this->outputDisabled;
511 }
512
513 /**
514 * Returns the current output of the process (STDOUT).
515 *
516 * @return string The process output
517 *
518 * @throws LogicException in case the output has been disabled
519 * @throws LogicException In case the process is not started
520 */
521 public function getOutput()
522 {
523 $this->readPipesForOutput(__FUNCTION__);
524
525 if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
526 return '';
527 }
528
529 return $ret;
530 }
531
532 /**
533 * Returns the output incrementally.
534 *
535 * In comparison with the getOutput method which always return the whole
536 * output, this one returns the new output since the last call.
537 *
538 * @return string The process output since the last call
539 *
540 * @throws LogicException in case the output has been disabled
541 * @throws LogicException In case the process is not started
542 */
543 public function getIncrementalOutput()
544 {
545 $this->readPipesForOutput(__FUNCTION__);
546
547 $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
548 $this->incrementalOutputOffset = ftell($this->stdout);
549
550 if (false === $latest) {
551 return '';
552 }
553
554 return $latest;
555 }
556
557 /**
558 * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
559 *
560 * @param int $flags A bit field of Process::ITER_* flags
561 *
562 * @throws LogicException in case the output has been disabled
563 * @throws LogicException In case the process is not started
564 *
565 * @return \Generator
566 */
567 public function getIterator($flags = 0)
568 {
569 $this->readPipesForOutput(__FUNCTION__, false);
570
571 $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
572 $blocking = !(self::ITER_NON_BLOCKING & $flags);
573 $yieldOut = !(self::ITER_SKIP_OUT & $flags);
574 $yieldErr = !(self::ITER_SKIP_ERR & $flags);
575
576 while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
577 if ($yieldOut) {
578 $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
579
580 if (isset($out[0])) {
581 if ($clearOutput) {
582 $this->clearOutput();
583 } else {
584 $this->incrementalOutputOffset = ftell($this->stdout);
585 }
586
587 yield self::OUT => $out;
588 }
589 }
590
591 if ($yieldErr) {
592 $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
593
594 if (isset($err[0])) {
595 if ($clearOutput) {
596 $this->clearErrorOutput();
597 } else {
598 $this->incrementalErrorOutputOffset = ftell($this->stderr);
599 }
600
601 yield self::ERR => $err;
602 }
603 }
604
605 if (!$blocking && !isset($out[0]) && !isset($err[0])) {
606 yield self::OUT => '';
607 }
608
609 $this->checkTimeout();
610 $this->readPipesForOutput(__FUNCTION__, $blocking);
611 }
612 }
613
614 /**
615 * Clears the process output.
616 *
617 * @return $this
618 */
619 public function clearOutput()
620 {
621 ftruncate($this->stdout, 0);
622 fseek($this->stdout, 0);
623 $this->incrementalOutputOffset = 0;
624
625 return $this;
626 }
627
628 /**
629 * Returns the current error output of the process (STDERR).
630 *
631 * @return string The process error output
632 *
633 * @throws LogicException in case the output has been disabled
634 * @throws LogicException In case the process is not started
635 */
636 public function getErrorOutput()
637 {
638 $this->readPipesForOutput(__FUNCTION__);
639
640 if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
641 return '';
642 }
643
644 return $ret;
645 }
646
647 /**
648 * Returns the errorOutput incrementally.
649 *
650 * In comparison with the getErrorOutput method which always return the
651 * whole error output, this one returns the new error output since the last
652 * call.
653 *
654 * @return string The process error output since the last call
655 *
656 * @throws LogicException in case the output has been disabled
657 * @throws LogicException In case the process is not started
658 */
659 public function getIncrementalErrorOutput()
660 {
661 $this->readPipesForOutput(__FUNCTION__);
662
663 $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
664 $this->incrementalErrorOutputOffset = ftell($this->stderr);
665
666 if (false === $latest) {
667 return '';
668 }
669
670 return $latest;
671 }
672
673 /**
674 * Clears the process output.
675 *
676 * @return $this
677 */
678 public function clearErrorOutput()
679 {
680 ftruncate($this->stderr, 0);
681 fseek($this->stderr, 0);
682 $this->incrementalErrorOutputOffset = 0;
683
684 return $this;
685 }
686
687 /**
688 * Returns the exit code returned by the process.
689 *
690 * @return int|null The exit status code, null if the Process is not terminated
691 *
692 * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
693 */
694 public function getExitCode()
695 {
696 if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
697 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
698 }
699
700 $this->updateStatus(false);
701
702 return $this->exitcode;
703 }
704
705 /**
706 * Returns a string representation for the exit code returned by the process.
707 *
708 * This method relies on the Unix exit code status standardization
709 * and might not be relevant for other operating systems.
710 *
711 * @return string|null A string representation for the exit status code, null if the Process is not terminated
712 *
713 * @see http://tldp.org/LDP/abs/html/exitcodes.html
714 * @see http://en.wikipedia.org/wiki/Unix_signal
715 */
716 public function getExitCodeText()
717 {
718 if (null === $exitcode = $this->getExitCode()) {
719 return null;
720 }
721
722 return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
723 }
724
725 /**
726 * Checks if the process ended successfully.
727 *
728 * @return bool true if the process ended successfully, false otherwise
729 */
730 public function isSuccessful()
731 {
732 return 0 === $this->getExitCode();
733 }
734
735 /**
736 * Returns true if the child process has been terminated by an uncaught signal.
737 *
738 * It always returns false on Windows.
739 *
740 * @return bool
741 *
742 * @throws RuntimeException In case --enable-sigchild is activated
743 * @throws LogicException In case the process is not terminated
744 */
745 public function hasBeenSignaled()
746 {
747 $this->requireProcessIsTerminated(__FUNCTION__);
748
749 if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
750 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
751 }
752
753 return $this->processInformation['signaled'];
754 }
755
756 /**
757 * Returns the number of the signal that caused the child process to terminate its execution.
758 *
759 * It is only meaningful if hasBeenSignaled() returns true.
760 *
761 * @return int
762 *
763 * @throws RuntimeException In case --enable-sigchild is activated
764 * @throws LogicException In case the process is not terminated
765 */
766 public function getTermSignal()
767 {
768 $this->requireProcessIsTerminated(__FUNCTION__);
769
770 if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
771 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
772 }
773
774 return $this->processInformation['termsig'];
775 }
776
777 /**
778 * Returns true if the child process has been stopped by a signal.
779 *
780 * It always returns false on Windows.
781 *
782 * @return bool
783 *
784 * @throws LogicException In case the process is not terminated
785 */
786 public function hasBeenStopped()
787 {
788 $this->requireProcessIsTerminated(__FUNCTION__);
789
790 return $this->processInformation['stopped'];
791 }
792
793 /**
794 * Returns the number of the signal that caused the child process to stop its execution.
795 *
796 * It is only meaningful if hasBeenStopped() returns true.
797 *
798 * @return int
799 *
800 * @throws LogicException In case the process is not terminated
801 */
802 public function getStopSignal()
803 {
804 $this->requireProcessIsTerminated(__FUNCTION__);
805
806 return $this->processInformation['stopsig'];
807 }
808
809 /**
810 * Checks if the process is currently running.
811 *
812 * @return bool true if the process is currently running, false otherwise
813 */
814 public function isRunning()
815 {
816 if (self::STATUS_STARTED !== $this->status) {
817 return false;
818 }
819
820 $this->updateStatus(false);
821
822 return $this->processInformation['running'];
823 }
824
825 /**
826 * Checks if the process has been started with no regard to the current state.
827 *
828 * @return bool true if status is ready, false otherwise
829 */
830 public function isStarted()
831 {
832 return self::STATUS_READY != $this->status;
833 }
834
835 /**
836 * Checks if the process is terminated.
837 *
838 * @return bool true if process is terminated, false otherwise
839 */
840 public function isTerminated()
841 {
842 $this->updateStatus(false);
843
844 return self::STATUS_TERMINATED == $this->status;
845 }
846
847 /**
848 * Gets the process status.
849 *
850 * The status is one of: ready, started, terminated.
851 *
852 * @return string The current process status
853 */
854 public function getStatus()
855 {
856 $this->updateStatus(false);
857
858 return $this->status;
859 }
860
861 /**
862 * Stops the process.
863 *
864 * @param int|float $timeout The timeout in seconds
865 * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
866 *
867 * @return int|null The exit-code of the process or null if it's not running
868 */
869 public function stop($timeout = 10, $signal = null)
870 {
871 $timeoutMicro = microtime(true) + $timeout;
872 if ($this->isRunning()) {
873 // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
874 $this->doSignal(15, false);
875 do {
876 usleep(1000);
877 } while ($this->isRunning() && microtime(true) < $timeoutMicro);
878
879 if ($this->isRunning()) {
880 // Avoid exception here: process is supposed to be running, but it might have stopped just
881 // after this line. In any case, let's silently discard the error, we cannot do anything.
882 $this->doSignal($signal ?: 9, false);
883 }
884 }
885
886 if ($this->isRunning()) {
887 if (isset($this->fallbackStatus['pid'])) {
888 unset($this->fallbackStatus['pid']);
889
890 return $this->stop(0, $signal);
891 }
892 $this->close();
893 }
894
895 return $this->exitcode;
896 }
897
898 /**
899 * Adds a line to the STDOUT stream.
900 *
901 * @internal
902 *
903 * @param string $line The line to append
904 */
905 public function addOutput($line)
906 {
907 $this->lastOutputTime = microtime(true);
908
909 fseek($this->stdout, 0, \SEEK_END);
910 fwrite($this->stdout, $line);
911 fseek($this->stdout, $this->incrementalOutputOffset);
912 }
913
914 /**
915 * Adds a line to the STDERR stream.
916 *
917 * @internal
918 *
919 * @param string $line The line to append
920 */
921 public function addErrorOutput($line)
922 {
923 $this->lastOutputTime = microtime(true);
924
925 fseek($this->stderr, 0, \SEEK_END);
926 fwrite($this->stderr, $line);
927 fseek($this->stderr, $this->incrementalErrorOutputOffset);
928 }
929
930 /**
931 * Gets the command line to be executed.
932 *
933 * @return string The command to execute
934 */
935 public function getCommandLine()
936 {
937 return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
938 }
939
940 /**
941 * Sets the command line to be executed.
942 *
943 * @param string|array $commandline The command to execute
944 *
945 * @return $this
946 */
947 public function setCommandLine($commandline)
948 {
949 $this->commandline = $commandline;
950
951 return $this;
952 }
953
954 /**
955 * Gets the process timeout (max. runtime).
956 *
957 * @return float|null The timeout in seconds or null if it's disabled
958 */
959 public function getTimeout()
960 {
961 return $this->timeout;
962 }
963
964 /**
965 * Gets the process idle timeout (max. time since last output).
966 *
967 * @return float|null The timeout in seconds or null if it's disabled
968 */
969 public function getIdleTimeout()
970 {
971 return $this->idleTimeout;
972 }
973
974 /**
975 * Sets the process timeout (max. runtime) in seconds.
976 *
977 * To disable the timeout, set this value to null.
978 *
979 * @param int|float|null $timeout The timeout in seconds
980 *
981 * @return $this
982 *
983 * @throws InvalidArgumentException if the timeout is negative
984 */
985 public function setTimeout($timeout)
986 {
987 $this->timeout = $this->validateTimeout($timeout);
988
989 return $this;
990 }
991
992 /**
993 * Sets the process idle timeout (max. time since last output).
994 *
995 * To disable the timeout, set this value to null.
996 *
997 * @param int|float|null $timeout The timeout in seconds
998 *
999 * @return $this
1000 *
1001 * @throws LogicException if the output is disabled
1002 * @throws InvalidArgumentException if the timeout is negative
1003 */
1004 public function setIdleTimeout($timeout)
1005 {
1006 if (null !== $timeout && $this->outputDisabled) {
1007 throw new LogicException('Idle timeout can not be set while the output is disabled.');
1008 }
1009
1010 $this->idleTimeout = $this->validateTimeout($timeout);
1011
1012 return $this;
1013 }
1014
1015 /**
1016 * Enables or disables the TTY mode.
1017 *
1018 * @param bool $tty True to enabled and false to disable
1019 *
1020 * @return $this
1021 *
1022 * @throws RuntimeException In case the TTY mode is not supported
1023 */
1024 public function setTty($tty)
1025 {
1026 if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
1027 throw new RuntimeException('TTY mode is not supported on Windows platform.');
1028 }
1029 if ($tty) {
1030 static $isTtySupported;
1031
1032 if (null === $isTtySupported) {
1033 $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
1034 }
1035
1036 if (!$isTtySupported) {
1037 throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
1038 }
1039 }
1040
1041 $this->tty = (bool) $tty;
1042
1043 return $this;
1044 }
1045
1046 /**
1047 * Checks if the TTY mode is enabled.
1048 *
1049 * @return bool true if the TTY mode is enabled, false otherwise
1050 */
1051 public function isTty()
1052 {
1053 return $this->tty;
1054 }
1055
1056 /**
1057 * Sets PTY mode.
1058 *
1059 * @param bool $bool
1060 *
1061 * @return $this
1062 */
1063 public function setPty($bool)
1064 {
1065 $this->pty = (bool) $bool;
1066
1067 return $this;
1068 }
1069
1070 /**
1071 * Returns PTY state.
1072 *
1073 * @return bool
1074 */
1075 public function isPty()
1076 {
1077 return $this->pty;
1078 }
1079
1080 /**
1081 * Gets the working directory.
1082 *
1083 * @return string|null The current working directory or null on failure
1084 */
1085 public function getWorkingDirectory()
1086 {
1087 if (null === $this->cwd) {
1088 // getcwd() will return false if any one of the parent directories does not have
1089 // the readable or search mode set, even if the current directory does
1090 return getcwd() ?: null;
1091 }
1092
1093 return $this->cwd;
1094 }
1095
1096 /**
1097 * Sets the current working directory.
1098 *
1099 * @param string $cwd The new working directory
1100 *
1101 * @return $this
1102 */
1103 public function setWorkingDirectory($cwd)
1104 {
1105 $this->cwd = $cwd;
1106
1107 return $this;
1108 }
1109
1110 /**
1111 * Gets the environment variables.
1112 *
1113 * @return array The current environment variables
1114 */
1115 public function getEnv()
1116 {
1117 return $this->env;
1118 }
1119
1120 /**
1121 * Sets the environment variables.
1122 *
1123 * Each environment variable value should be a string.
1124 * If it is an array, the variable is ignored.
1125 * If it is false or null, it will be removed when
1126 * env vars are otherwise inherited.
1127 *
1128 * That happens in PHP when 'argv' is registered into
1129 * the $_ENV array for instance.
1130 *
1131 * @param array $env The new environment variables
1132 *
1133 * @return $this
1134 */
1135 public function setEnv(array $env)
1136 {
1137 // Process can not handle env values that are arrays
1138 $env = array_filter($env, function ($value) {
1139 return !\is_array($value);
1140 });
1141
1142 $this->env = $env;
1143
1144 return $this;
1145 }
1146
1147 /**
1148 * Gets the Process input.
1149 *
1150 * @return resource|string|\Iterator|null The Process input
1151 */
1152 public function getInput()
1153 {
1154 return $this->input;
1155 }
1156
1157 /**
1158 * Sets the input.
1159 *
1160 * This content will be passed to the underlying process standard input.
1161 *
1162 * @param string|int|float|bool|resource|\Traversable|null $input The content
1163 *
1164 * @return $this
1165 *
1166 * @throws LogicException In case the process is running
1167 */
1168 public function setInput($input)
1169 {
1170 if ($this->isRunning()) {
1171 throw new LogicException('Input can not be set while the process is running.');
1172 }
1173
1174 $this->input = ProcessUtils::validateInput(__METHOD__, $input);
1175
1176 return $this;
1177 }
1178
1179 /**
1180 * Gets the options for proc_open.
1181 *
1182 * @return array The current options
1183 *
1184 * @deprecated since version 3.3, to be removed in 4.0.
1185 */
1186 public function getOptions()
1187 {
1188 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED);
1189
1190 return $this->options;
1191 }
1192
1193 /**
1194 * Sets the options for proc_open.
1195 *
1196 * @param array $options The new options
1197 *
1198 * @return $this
1199 *
1200 * @deprecated since version 3.3, to be removed in 4.0.
1201 */
1202 public function setOptions(array $options)
1203 {
1204 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED);
1205
1206 $this->options = $options;
1207
1208 return $this;
1209 }
1210
1211 /**
1212 * Gets whether or not Windows compatibility is enabled.
1213 *
1214 * This is true by default.
1215 *
1216 * @return bool
1217 *
1218 * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
1219 */
1220 public function getEnhanceWindowsCompatibility()
1221 {
1222 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1223
1224 return $this->enhanceWindowsCompatibility;
1225 }
1226
1227 /**
1228 * Sets whether or not Windows compatibility is enabled.
1229 *
1230 * @param bool $enhance
1231 *
1232 * @return $this
1233 *
1234 * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
1235 */
1236 public function setEnhanceWindowsCompatibility($enhance)
1237 {
1238 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1239
1240 $this->enhanceWindowsCompatibility = (bool) $enhance;
1241
1242 return $this;
1243 }
1244
1245 /**
1246 * Returns whether sigchild compatibility mode is activated or not.
1247 *
1248 * @return bool
1249 *
1250 * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
1251 */
1252 public function getEnhanceSigchildCompatibility()
1253 {
1254 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1255
1256 return $this->enhanceSigchildCompatibility;
1257 }
1258
1259 /**
1260 * Activates sigchild compatibility mode.
1261 *
1262 * Sigchild compatibility mode is required to get the exit code and
1263 * determine the success of a process when PHP has been compiled with
1264 * the --enable-sigchild option
1265 *
1266 * @param bool $enhance
1267 *
1268 * @return $this
1269 *
1270 * @deprecated since version 3.3, to be removed in 4.0.
1271 */
1272 public function setEnhanceSigchildCompatibility($enhance)
1273 {
1274 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);
1275
1276 $this->enhanceSigchildCompatibility = (bool) $enhance;
1277
1278 return $this;
1279 }
1280
1281 /**
1282 * Sets whether environment variables will be inherited or not.
1283 *
1284 * @param bool $inheritEnv
1285 *
1286 * @return $this
1287 */
1288 public function inheritEnvironmentVariables($inheritEnv = true)
1289 {
1290 if (!$inheritEnv) {
1291 @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);
1292 }
1293
1294 $this->inheritEnv = (bool) $inheritEnv;
1295
1296 return $this;
1297 }
1298
1299 /**
1300 * Returns whether environment variables will be inherited or not.
1301 *
1302 * @return bool
1303 *
1304 * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
1305 */
1306 public function areEnvironmentVariablesInherited()
1307 {
1308 @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), \E_USER_DEPRECATED);
1309
1310 return $this->inheritEnv;
1311 }
1312
1313 /**
1314 * Performs a check between the timeout definition and the time the process started.
1315 *
1316 * In case you run a background process (with the start method), you should
1317 * trigger this method regularly to ensure the process timeout
1318 *
1319 * @throws ProcessTimedOutException In case the timeout was reached
1320 */
1321 public function checkTimeout()
1322 {
1323 if (self::STATUS_STARTED !== $this->status) {
1324 return;
1325 }
1326
1327 if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
1328 $this->stop(0);
1329
1330 throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
1331 }
1332
1333 if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
1334 $this->stop(0);
1335
1336 throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
1337 }
1338 }
1339
1340 /**
1341 * Returns whether PTY is supported on the current operating system.
1342 *
1343 * @return bool
1344 */
1345 public static function isPtySupported()
1346 {
1347 static $result;
1348
1349 if (null !== $result) {
1350 return $result;
1351 }
1352
1353 if ('\\' === \DIRECTORY_SEPARATOR) {
1354 return $result = false;
1355 }
1356
1357 return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
1358 }
1359
1360 /**
1361 * Creates the descriptors needed by the proc_open.
1362 *
1363 * @return array
1364 */
1365 private function getDescriptors()
1366 {
1367 if ($this->input instanceof \Iterator) {
1368 $this->input->rewind();
1369 }
1370 if ('\\' === \DIRECTORY_SEPARATOR) {
1371 $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
1372 } else {
1373 $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
1374 }
1375
1376 return $this->processPipes->getDescriptors();
1377 }
1378
1379 /**
1380 * Builds up the callback used by wait().
1381 *
1382 * The callbacks adds all occurred output to the specific buffer and calls
1383 * the user callback (if present) with the received output.
1384 *
1385 * @param callable|null $callback The user defined PHP callback
1386 *
1387 * @return \Closure A PHP closure
1388 */
1389 protected function buildCallback(callable $callback = null)
1390 {
1391 if ($this->outputDisabled) {
1392 return function ($type, $data) use ($callback) {
1393 if (null !== $callback) {
1394 \call_user_func($callback, $type, $data);
1395 }
1396 };
1397 }
1398
1399 $out = self::OUT;
1400
1401 return function ($type, $data) use ($callback, $out) {
1402 if ($out == $type) {
1403 $this->addOutput($data);
1404 } else {
1405 $this->addErrorOutput($data);
1406 }
1407
1408 if (null !== $callback) {
1409 \call_user_func($callback, $type, $data);
1410 }
1411 };
1412 }
1413
1414 /**
1415 * Updates the status of the process, reads pipes.
1416 *
1417 * @param bool $blocking Whether to use a blocking read call
1418 */
1419 protected function updateStatus($blocking)
1420 {
1421 if (self::STATUS_STARTED !== $this->status) {
1422 return;
1423 }
1424
1425 $this->processInformation = proc_get_status($this->process);
1426 $running = $this->processInformation['running'];
1427
1428 $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
1429
1430 if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1431 $this->processInformation = $this->fallbackStatus + $this->processInformation;
1432 }
1433
1434 if (!$running) {
1435 $this->close();
1436 }
1437 }
1438
1439 /**
1440 * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
1441 *
1442 * @return bool
1443 */
1444 protected function isSigchildEnabled()
1445 {
1446 if (null !== self::$sigchild) {
1447 return self::$sigchild;
1448 }
1449
1450 if (!\function_exists('phpinfo') || \defined('HHVM_VERSION')) {
1451 return self::$sigchild = false;
1452 }
1453
1454 ob_start();
1455 phpinfo(\INFO_GENERAL);
1456
1457 return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1458 }
1459
1460 /**
1461 * Reads pipes for the freshest output.
1462 *
1463 * @param string $caller The name of the method that needs fresh outputs
1464 * @param bool $blocking Whether to use blocking calls or not
1465 *
1466 * @throws LogicException in case output has been disabled or process is not started
1467 */
1468 private function readPipesForOutput($caller, $blocking = false)
1469 {
1470 if ($this->outputDisabled) {
1471 throw new LogicException('Output has been disabled.');
1472 }
1473
1474 $this->requireProcessIsStarted($caller);
1475
1476 $this->updateStatus($blocking);
1477 }
1478
1479 /**
1480 * Validates and returns the filtered timeout.
1481 *
1482 * @param int|float|null $timeout
1483 *
1484 * @return float|null
1485 *
1486 * @throws InvalidArgumentException if the given timeout is a negative number
1487 */
1488 private function validateTimeout($timeout)
1489 {
1490 $timeout = (float) $timeout;
1491
1492 if (0.0 === $timeout) {
1493 $timeout = null;
1494 } elseif ($timeout < 0) {
1495 throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
1496 }
1497
1498 return $timeout;
1499 }
1500
1501 /**
1502 * Reads pipes, executes callback.
1503 *
1504 * @param bool $blocking Whether to use blocking calls or not
1505 * @param bool $close Whether to close file handles or not
1506 */
1507 private function readPipes($blocking, $close)
1508 {
1509 $result = $this->processPipes->readAndWrite($blocking, $close);
1510
1511 $callback = $this->callback;
1512 foreach ($result as $type => $data) {
1513 if (3 !== $type) {
1514 $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
1515 } elseif (!isset($this->fallbackStatus['signaled'])) {
1516 $this->fallbackStatus['exitcode'] = (int) $data;
1517 }
1518 }
1519 }
1520
1521 /**
1522 * Closes process resource, closes file handles, sets the exitcode.
1523 *
1524 * @return int The exitcode
1525 */
1526 private function close()
1527 {
1528 $this->processPipes->close();
1529 if (\is_resource($this->process)) {
1530 proc_close($this->process);
1531 }
1532 $this->exitcode = $this->processInformation['exitcode'];
1533 $this->status = self::STATUS_TERMINATED;
1534
1535 if (-1 === $this->exitcode) {
1536 if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1537 // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1538 $this->exitcode = 128 + $this->processInformation['termsig'];
1539 } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1540 $this->processInformation['signaled'] = true;
1541 $this->processInformation['termsig'] = -1;
1542 }
1543 }
1544
1545 // Free memory from self-reference callback created by buildCallback
1546 // Doing so in other contexts like __destruct or by garbage collector is ineffective
1547 // Now pipes are closed, so the callback is no longer necessary
1548 $this->callback = null;
1549
1550 return $this->exitcode;
1551 }
1552
1553 /**
1554 * Resets data related to the latest run of the process.
1555 */
1556 private function resetProcessData()
1557 {
1558 $this->starttime = null;
1559 $this->callback = null;
1560 $this->exitcode = null;
1561 $this->fallbackStatus = [];
1562 $this->processInformation = null;
1563 $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
1564 $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
1565 $this->process = null;
1566 $this->latestSignal = null;
1567 $this->status = self::STATUS_READY;
1568 $this->incrementalOutputOffset = 0;
1569 $this->incrementalErrorOutputOffset = 0;
1570 }
1571
1572 /**
1573 * Sends a POSIX signal to the process.
1574 *
1575 * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
1576 * @param bool $throwException Whether to throw exception in case signal failed
1577 *
1578 * @return bool True if the signal was sent successfully, false otherwise
1579 *
1580 * @throws LogicException In case the process is not running
1581 * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
1582 * @throws RuntimeException In case of failure
1583 */
1584 private function doSignal($signal, $throwException)
1585 {
1586 if (null === $pid = $this->getPid()) {
1587 if ($throwException) {
1588 throw new LogicException('Can not send signal on a non running process.');
1589 }
1590
1591 return false;
1592 }
1593
1594 if ('\\' === \DIRECTORY_SEPARATOR) {
1595 exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
1596 if ($exitCode && $this->isRunning()) {
1597 if ($throwException) {
1598 throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
1599 }
1600
1601 return false;
1602 }
1603 } else {
1604 if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
1605 $ok = @proc_terminate($this->process, $signal);
1606 } elseif (\function_exists('posix_kill')) {
1607 $ok = @posix_kill($pid, $signal);
1608 } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
1609 $ok = false === fgets($pipes[2]);
1610 }
1611 if (!$ok) {
1612 if ($throwException) {
1613 throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1614 }
1615
1616 return false;
1617 }
1618 }
1619
1620 $this->latestSignal = (int) $signal;
1621 $this->fallbackStatus['signaled'] = true;
1622 $this->fallbackStatus['exitcode'] = -1;
1623 $this->fallbackStatus['termsig'] = $this->latestSignal;
1624
1625 return true;
1626 }
1627
1628 private function prepareWindowsCommandLine($cmd, array &$env)
1629 {
1630 $uid = uniqid('', true);
1631 $varCount = 0;
1632 $varCache = [];
1633 $cmd = preg_replace_callback(
1634 '/"(?:(
1635 [^"%!^]*+
1636 (?:
1637 (?: !LF! | "(?:\^[%!^])?+" )
1638 [^"%!^]*+
1639 )++
1640 ) | [^"]*+ )"/x',
1641 function ($m) use (&$env, &$varCache, &$varCount, $uid) {
1642 if (!isset($m[1])) {
1643 return $m[0];
1644 }
1645 if (isset($varCache[$m[0]])) {
1646 return $varCache[$m[0]];
1647 }
1648 if (false !== strpos($value = $m[1], "\0")) {
1649 $value = str_replace("\0", '?', $value);
1650 }
1651 if (false === strpbrk($value, "\"%!\n")) {
1652 return '"'.$value.'"';
1653 }
1654
1655 $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
1656 $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
1657 $var = $uid.++$varCount;
1658
1659 $env[$var] = $value;
1660
1661 return $varCache[$m[0]] = '!'.$var.'!';
1662 },
1663 $cmd
1664 );
1665
1666 $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
1667 foreach ($this->processPipes->getFiles() as $offset => $filename) {
1668 $cmd .= ' '.$offset.'>"'.$filename.'"';
1669 }
1670
1671 return $cmd;
1672 }
1673
1674 /**
1675 * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
1676 *
1677 * @param string $functionName The function name that was called
1678 *
1679 * @throws LogicException if the process has not run
1680 */
1681 private function requireProcessIsStarted($functionName)
1682 {
1683 if (!$this->isStarted()) {
1684 throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
1685 }
1686 }
1687
1688 /**
1689 * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
1690 *
1691 * @param string $functionName The function name that was called
1692 *
1693 * @throws LogicException if the process is not yet terminated
1694 */
1695 private function requireProcessIsTerminated($functionName)
1696 {
1697 if (!$this->isTerminated()) {
1698 throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
1699 }
1700 }
1701
1702 /**
1703 * Escapes a string to be used as a shell argument.
1704 *
1705 * @param string $argument The argument that will be escaped
1706 *
1707 * @return string The escaped argument
1708 */
1709 private function escapeArgument($argument)
1710 {
1711 if ('\\' !== \DIRECTORY_SEPARATOR) {
1712 return "'".str_replace("'", "'\\''", $argument)."'";
1713 }
1714 if ('' === $argument = (string) $argument) {
1715 return '""';
1716 }
1717 if (false !== strpos($argument, "\0")) {
1718 $argument = str_replace("\0", '?', $argument);
1719 }
1720 if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
1721 return $argument;
1722 }
1723 $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
1724
1725 return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
1726 }
1727
1728 private function getDefaultEnv()
1729 {
1730 $env = [];
1731
1732 foreach ($_SERVER as $k => $v) {
1733 if (\is_string($v) && false !== $v = getenv($k)) {
1734 $env[$k] = $v;
1735 }
1736 }
1737
1738 foreach ($_ENV as $k => $v) {
1739 if (\is_string($v)) {
1740 $env[$k] = $v;
1741 }
1742 }
1743
1744 return $env;
1745 }
1746 }
1747