ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
Process.php
Go to the documentation of this file.
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
13
22
30class 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;
59 private $timeout;
60 private $idleTimeout;
61 private $options;
62 private $exitcode;
63 private $fallbackStatus = array();
65 private $outputDisabled = false;
66 private $stdout;
67 private $stderr;
70 private $process;
74 private $tty;
75 private $pty;
76
77 private $useFileHandles = false;
80
82
83 private static $sigchild;
84
92 public static $exitCodes = array(
93 0 => 'OK',
94 1 => 'General error',
95 2 => 'Misuse of shell builtins',
96
97 126 => 'Invoked command cannot execute',
98 127 => 'Command not found',
99 128 => 'Invalid exit argument',
100
101 // signals
102 129 => 'Hangup',
103 130 => 'Interrupt',
104 131 => 'Quit and dump core',
105 132 => 'Illegal instruction',
106 133 => 'Trace/breakpoint trap',
107 134 => 'Process aborted',
108 135 => 'Bus error: "access to undefined portion of memory object"',
109 136 => 'Floating point exception: "erroneous arithmetic operation"',
110 137 => 'Kill (terminate immediately)',
111 138 => 'User-defined 1',
112 139 => 'Segmentation violation',
113 140 => 'User-defined 2',
114 141 => 'Write to pipe with no one reading',
115 142 => 'Signal raised by alarm',
116 143 => 'Termination (request to terminate)',
117 // 144 - not defined
118 145 => 'Child process terminated, stopped (or continued*)',
119 146 => 'Continue if stopped',
120 147 => 'Stop executing temporarily',
121 148 => 'Terminal stop signal',
122 149 => 'Background process attempting to read from tty ("in")',
123 150 => 'Background process attempting to write to tty ("out")',
124 151 => 'Urgent data available on socket',
125 152 => 'CPU time limit exceeded',
126 153 => 'File size limit exceeded',
127 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
128 155 => 'Profiling timer expired',
129 // 156 - not defined
130 157 => 'Pollable event',
131 // 158 - not defined
132 159 => 'Bad syscall',
133 );
134
147 public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = array())
148 {
149 if (!function_exists('proc_open')) {
150 throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
151 }
152
153 $this->commandline = $commandline;
154 $this->cwd = $cwd;
155
156 // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
157 // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
158 // @see : https://bugs.php.net/bug.php?id=51800
159 // @see : https://bugs.php.net/bug.php?id=50524
160 if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) {
161 $this->cwd = getcwd();
162 }
163 if (null !== $env) {
164 $this->setEnv($env);
165 }
166
167 $this->setInput($input);
168 $this->setTimeout($timeout);
169 $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;
170 $this->pty = false;
171 $this->enhanceWindowsCompatibility = true;
172 $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
173 $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
174 }
175
176 public function __destruct()
177 {
178 $this->stop(0);
179 }
180
181 public function __clone()
182 {
183 $this->resetProcessData();
184 }
185
205 public function run($callback = null)
206 {
207 $this->start($callback);
208
209 return $this->wait();
210 }
211
225 public function mustRun(callable $callback = null)
226 {
227 if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
228 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
229 }
230
231 if (0 !== $this->run($callback)) {
232 throw new ProcessFailedException($this);
233 }
234
235 return $this;
236 }
237
257 public function start(callable $callback = null)
258 {
259 if ($this->isRunning()) {
260 throw new RuntimeException('Process is already running');
261 }
262
263 $this->resetProcessData();
264 $this->starttime = $this->lastOutputTime = microtime(true);
265 $this->callback = $this->buildCallback($callback);
266 $this->hasCallback = null !== $callback;
267 $descriptors = $this->getDescriptors();
268
270
271 if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
272 $commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')';
273 foreach ($this->processPipes->getFiles() as $offset => $filename) {
275 }
276 $commandline .= '"';
277
278 if (!isset($this->options['bypass_shell'])) {
279 $this->options['bypass_shell'] = true;
280 }
281 } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
282 // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
283 $descriptors[3] = array('pipe', 'w');
284
285 // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
286 $commandline = '{ ('.$this->commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
287 $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
288
289 // Workaround for the bug, when PTS functionality is enabled.
290 // @see : https://bugs.php.net/69442
291 $ptsWorkaround = fopen(__FILE__, 'r');
292 }
293
294 $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);
295
296 if (!is_resource($this->process)) {
297 throw new RuntimeException('Unable to launch a new process.');
298 }
299 $this->status = self::STATUS_STARTED;
300
301 if (isset($descriptors[3])) {
302 $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
303 }
304
305 if ($this->tty) {
306 return;
307 }
308
309 $this->updateStatus(false);
310 $this->checkTimeout();
311 }
312
328 public function restart(callable $callback = null)
329 {
330 if ($this->isRunning()) {
331 throw new RuntimeException('Process is already running');
332 }
333
334 $process = clone $this;
335 $process->start($callback);
336
337 return $process;
338 }
339
355 public function wait(callable $callback = null)
356 {
357 $this->requireProcessIsStarted(__FUNCTION__);
358
359 $this->updateStatus(false);
360
361 if (null !== $callback) {
362 if (!$this->processPipes->haveReadSupport()) {
363 $this->stop(0);
364 throw new \LogicException('Pass the callback to the Process:start method or enableOutput to use a callback with Process::wait');
365 }
366 $this->callback = $this->buildCallback($callback);
367 }
368
369 do {
370 $this->checkTimeout();
371 $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
372 $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
373 } while ($running);
374
375 while ($this->isRunning()) {
376 usleep(1000);
377 }
378
379 if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
380 throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
381 }
382
383 return $this->exitcode;
384 }
385
391 public function getPid()
392 {
393 return $this->isRunning() ? $this->processInformation['pid'] : null;
394 }
395
407 public function signal($signal)
408 {
409 $this->doSignal($signal, true);
410
411 return $this;
412 }
413
422 public function disableOutput()
423 {
424 if ($this->isRunning()) {
425 throw new RuntimeException('Disabling output while the process is running is not possible.');
426 }
427 if (null !== $this->idleTimeout) {
428 throw new LogicException('Output can not be disabled while an idle timeout is set.');
429 }
430
431 $this->outputDisabled = true;
432
433 return $this;
434 }
435
443 public function enableOutput()
444 {
445 if ($this->isRunning()) {
446 throw new RuntimeException('Enabling output while the process is running is not possible.');
447 }
448
449 $this->outputDisabled = false;
450
451 return $this;
452 }
453
459 public function isOutputDisabled()
460 {
462 }
463
472 public function getOutput()
473 {
474 $this->readPipesForOutput(__FUNCTION__);
475
476 if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
477 return '';
478 }
479
480 return $ret;
481 }
482
494 public function getIncrementalOutput()
495 {
496 $this->readPipesForOutput(__FUNCTION__);
497
498 $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
499 $this->incrementalOutputOffset = ftell($this->stdout);
500
501 if (false === $latest) {
502 return '';
503 }
504
505 return $latest;
506 }
507
518 public function getIterator($flags = 0)
519 {
520 $this->readPipesForOutput(__FUNCTION__, false);
521
522 $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
523 $blocking = !(self::ITER_NON_BLOCKING & $flags);
524 $yieldOut = !(self::ITER_SKIP_OUT & $flags);
525 $yieldErr = !(self::ITER_SKIP_ERR & $flags);
526
527 while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
528 if ($yieldOut) {
529 $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
530
531 if (isset($out[0])) {
532 if ($clearOutput) {
533 $this->clearOutput();
534 } else {
535 $this->incrementalOutputOffset = ftell($this->stdout);
536 }
537
538 yield self::OUT => $out;
539 }
540 }
541
542 if ($yieldErr) {
543 $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
544
545 if (isset($err[0])) {
546 if ($clearOutput) {
547 $this->clearErrorOutput();
548 } else {
549 $this->incrementalErrorOutputOffset = ftell($this->stderr);
550 }
551
552 yield self::ERR => $err;
553 }
554 }
555
556 if (!$blocking && !isset($out[0]) && !isset($err[0])) {
557 yield self::OUT => '';
558 }
559
560 $this->readPipesForOutput(__FUNCTION__, $blocking);
561 }
562 }
563
569 public function clearOutput()
570 {
571 ftruncate($this->stdout, 0);
572 fseek($this->stdout, 0);
573 $this->incrementalOutputOffset = 0;
574
575 return $this;
576 }
577
586 public function getErrorOutput()
587 {
588 $this->readPipesForOutput(__FUNCTION__);
589
590 if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
591 return '';
592 }
593
594 return $ret;
595 }
596
610 {
611 $this->readPipesForOutput(__FUNCTION__);
612
613 $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
614 $this->incrementalErrorOutputOffset = ftell($this->stderr);
615
616 if (false === $latest) {
617 return '';
618 }
619
620 return $latest;
621 }
622
628 public function clearErrorOutput()
629 {
630 ftruncate($this->stderr, 0);
631 fseek($this->stderr, 0);
632 $this->incrementalErrorOutputOffset = 0;
633
634 return $this;
635 }
636
644 public function getExitCode()
645 {
646 if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
647 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
648 }
649
650 $this->updateStatus(false);
651
652 return $this->exitcode;
653 }
654
666 public function getExitCodeText()
667 {
668 if (null === $exitcode = $this->getExitCode()) {
669 return;
670 }
671
672 return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
673 }
674
680 public function isSuccessful()
681 {
682 return 0 === $this->getExitCode();
683 }
684
695 public function hasBeenSignaled()
696 {
697 $this->requireProcessIsTerminated(__FUNCTION__);
698
699 if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
700 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
701 }
702
703 return $this->processInformation['signaled'];
704 }
705
716 public function getTermSignal()
717 {
718 $this->requireProcessIsTerminated(__FUNCTION__);
719
720 if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) {
721 throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
722 }
723
724 return $this->processInformation['termsig'];
725 }
726
736 public function hasBeenStopped()
737 {
738 $this->requireProcessIsTerminated(__FUNCTION__);
739
740 return $this->processInformation['stopped'];
741 }
742
752 public function getStopSignal()
753 {
754 $this->requireProcessIsTerminated(__FUNCTION__);
755
756 return $this->processInformation['stopsig'];
757 }
758
764 public function isRunning()
765 {
766 if (self::STATUS_STARTED !== $this->status) {
767 return false;
768 }
769
770 $this->updateStatus(false);
771
772 return $this->processInformation['running'];
773 }
774
780 public function isStarted()
781 {
782 return $this->status != self::STATUS_READY;
783 }
784
790 public function isTerminated()
791 {
792 $this->updateStatus(false);
793
794 return $this->status == self::STATUS_TERMINATED;
795 }
796
804 public function getStatus()
805 {
806 $this->updateStatus(false);
807
808 return $this->status;
809 }
810
819 public function stop($timeout = 10, $signal = null)
820 {
821 $timeoutMicro = microtime(true) + $timeout;
822 if ($this->isRunning()) {
823 // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
824 $this->doSignal(15, false);
825 do {
826 usleep(1000);
827 } while ($this->isRunning() && microtime(true) < $timeoutMicro);
828
829 if ($this->isRunning()) {
830 // Avoid exception here: process is supposed to be running, but it might have stopped just
831 // after this line. In any case, let's silently discard the error, we cannot do anything.
832 $this->doSignal($signal ?: 9, false);
833 }
834 }
835
836 if ($this->isRunning()) {
837 if (isset($this->fallbackStatus['pid'])) {
838 unset($this->fallbackStatus['pid']);
839
840 return $this->stop(0, $signal);
841 }
842 $this->close();
843 }
844
845 return $this->exitcode;
846 }
847
855 public function addOutput($line)
856 {
857 $this->lastOutputTime = microtime(true);
858
859 fseek($this->stdout, 0, SEEK_END);
860 fwrite($this->stdout, $line);
861 fseek($this->stdout, $this->incrementalOutputOffset);
862 }
863
871 public function addErrorOutput($line)
872 {
873 $this->lastOutputTime = microtime(true);
874
875 fseek($this->stderr, 0, SEEK_END);
876 fwrite($this->stderr, $line);
877 fseek($this->stderr, $this->incrementalErrorOutputOffset);
878 }
879
885 public function getCommandLine()
886 {
887 return $this->commandline;
888 }
889
898 {
899 $this->commandline = $commandline;
900
901 return $this;
902 }
903
909 public function getTimeout()
910 {
911 return $this->timeout;
912 }
913
919 public function getIdleTimeout()
920 {
921 return $this->idleTimeout;
922 }
923
935 public function setTimeout($timeout)
936 {
937 $this->timeout = $this->validateTimeout($timeout);
938
939 return $this;
940 }
941
954 public function setIdleTimeout($timeout)
955 {
956 if (null !== $timeout && $this->outputDisabled) {
957 throw new LogicException('Idle timeout can not be set while the output is disabled.');
958 }
959
960 $this->idleTimeout = $this->validateTimeout($timeout);
961
962 return $this;
963 }
964
974 public function setTty($tty)
975 {
976 if ('\\' === DIRECTORY_SEPARATOR && $tty) {
977 throw new RuntimeException('TTY mode is not supported on Windows platform.');
978 }
979 if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) {
980 throw new RuntimeException('TTY mode requires /dev/tty to be readable.');
981 }
982
983 $this->tty = (bool) $tty;
984
985 return $this;
986 }
987
993 public function isTty()
994 {
995 return $this->tty;
996 }
997
1005 public function setPty($bool)
1006 {
1007 $this->pty = (bool) $bool;
1008
1009 return $this;
1010 }
1011
1017 public function isPty()
1018 {
1019 return $this->pty;
1020 }
1021
1027 public function getWorkingDirectory()
1028 {
1029 if (null === $this->cwd) {
1030 // getcwd() will return false if any one of the parent directories does not have
1031 // the readable or search mode set, even if the current directory does
1032 return getcwd() ?: null;
1033 }
1034
1035 return $this->cwd;
1036 }
1037
1045 public function setWorkingDirectory($cwd)
1046 {
1047 $this->cwd = $cwd;
1048
1049 return $this;
1050 }
1051
1057 public function getEnv()
1058 {
1059 return $this->env;
1060 }
1061
1075 public function setEnv(array $env)
1076 {
1077 // Process can not handle env values that are arrays
1078 $env = array_filter($env, function ($value) {
1079 return !is_array($value);
1080 });
1081
1082 $this->env = array();
1083 foreach ($env as $key => $value) {
1084 $this->env[$key] = (string) $value;
1085 }
1086
1087 return $this;
1088 }
1089
1095 public function getInput()
1096 {
1097 return $this->input;
1098 }
1099
1111 public function setInput($input)
1112 {
1113 if ($this->isRunning()) {
1114 throw new LogicException('Input can not be set while the process is running.');
1115 }
1116
1117 $this->input = ProcessUtils::validateInput(__METHOD__, $input);
1118
1119 return $this;
1120 }
1121
1127 public function getOptions()
1128 {
1129 return $this->options;
1130 }
1131
1139 public function setOptions(array $options)
1140 {
1141 $this->options = $options;
1142
1143 return $this;
1144 }
1145
1154 {
1156 }
1157
1165 public function setEnhanceWindowsCompatibility($enhance)
1166 {
1167 $this->enhanceWindowsCompatibility = (bool) $enhance;
1168
1169 return $this;
1170 }
1171
1178 {
1180 }
1181
1193 public function setEnhanceSigchildCompatibility($enhance)
1194 {
1195 $this->enhanceSigchildCompatibility = (bool) $enhance;
1196
1197 return $this;
1198 }
1199
1208 public function checkTimeout()
1209 {
1210 if ($this->status !== self::STATUS_STARTED) {
1211 return;
1212 }
1213
1214 if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
1215 $this->stop(0);
1216
1218 }
1219
1220 if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
1221 $this->stop(0);
1222
1224 }
1225 }
1226
1232 public static function isPtySupported()
1233 {
1234 static $result;
1235
1236 if (null !== $result) {
1237 return $result;
1238 }
1239
1240 if ('\\' === DIRECTORY_SEPARATOR) {
1241 return $result = false;
1242 }
1243
1244 return $result = (bool) @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
1245 }
1246
1252 private function getDescriptors()
1253 {
1254 if ($this->input instanceof \Iterator) {
1255 $this->input->rewind();
1256 }
1257 if ('\\' === DIRECTORY_SEPARATOR) {
1258 $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
1259 } else {
1260 $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
1261 }
1262
1263 return $this->processPipes->getDescriptors();
1264 }
1265
1276 protected function buildCallback(callable $callback = null)
1277 {
1278 if ($this->outputDisabled) {
1279 return function ($type, $data) use ($callback) {
1280 if (null !== $callback) {
1281 call_user_func($callback, $type, $data);
1282 }
1283 };
1284 }
1285
1286 $out = self::OUT;
1287
1288 return function ($type, $data) use ($callback, $out) {
1289 if ($out == $type) {
1290 $this->addOutput($data);
1291 } else {
1292 $this->addErrorOutput($data);
1293 }
1294
1295 if (null !== $callback) {
1296 call_user_func($callback, $type, $data);
1297 }
1298 };
1299 }
1300
1306 protected function updateStatus($blocking)
1307 {
1308 if (self::STATUS_STARTED !== $this->status) {
1309 return;
1310 }
1311
1312 $this->processInformation = proc_get_status($this->process);
1313 $running = $this->processInformation['running'];
1314
1315 $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running);
1316
1317 if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1318 $this->processInformation = $this->fallbackStatus + $this->processInformation;
1319 }
1320
1321 if (!$running) {
1322 $this->close();
1323 }
1324 }
1325
1331 protected function isSigchildEnabled()
1332 {
1333 if (null !== self::$sigchild) {
1334 return self::$sigchild;
1335 }
1336
1337 if (!function_exists('phpinfo') || defined('HHVM_VERSION')) {
1338 return self::$sigchild = false;
1339 }
1340
1341 ob_start();
1342 phpinfo(INFO_GENERAL);
1343
1344 return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
1345 }
1346
1355 private function readPipesForOutput($caller, $blocking = false)
1356 {
1357 if ($this->outputDisabled) {
1358 throw new LogicException('Output has been disabled.');
1359 }
1360
1361 $this->requireProcessIsStarted($caller);
1362
1363 $this->updateStatus($blocking);
1364 }
1365
1375 private function validateTimeout($timeout)
1376 {
1377 $timeout = (float) $timeout;
1378
1379 if (0.0 === $timeout) {
1380 $timeout = null;
1381 } elseif ($timeout < 0) {
1382 throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
1383 }
1384
1385 return $timeout;
1386 }
1387
1394 private function readPipes($blocking, $close)
1395 {
1396 $result = $this->processPipes->readAndWrite($blocking, $close);
1397
1399 foreach ($result as $type => $data) {
1400 if (3 !== $type) {
1401 $callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
1402 } elseif (!isset($this->fallbackStatus['signaled'])) {
1403 $this->fallbackStatus['exitcode'] = (int) $data;
1404 }
1405 }
1406 }
1407
1413 private function close()
1414 {
1415 $this->processPipes->close();
1416 if (is_resource($this->process)) {
1417 proc_close($this->process);
1418 }
1419 $this->exitcode = $this->processInformation['exitcode'];
1420 $this->status = self::STATUS_TERMINATED;
1421
1422 if (-1 === $this->exitcode) {
1423 if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
1424 // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
1425 $this->exitcode = 128 + $this->processInformation['termsig'];
1426 } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
1427 $this->processInformation['signaled'] = true;
1428 $this->processInformation['termsig'] = -1;
1429 }
1430 }
1431
1432 // Free memory from self-reference callback created by buildCallback
1433 // Doing so in other contexts like __destruct or by garbage collector is ineffective
1434 // Now pipes are closed, so the callback is no longer necessary
1435 $this->callback = null;
1436
1437 return $this->exitcode;
1438 }
1439
1443 private function resetProcessData()
1444 {
1445 $this->starttime = null;
1446 $this->callback = null;
1447 $this->exitcode = null;
1448 $this->fallbackStatus = array();
1449 $this->processInformation = null;
1450 $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
1451 $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
1452 $this->process = null;
1453 $this->latestSignal = null;
1454 $this->status = self::STATUS_READY;
1455 $this->incrementalOutputOffset = 0;
1456 $this->incrementalErrorOutputOffset = 0;
1457 }
1458
1471 private function doSignal($signal, $throwException)
1472 {
1473 if (null === $pid = $this->getPid()) {
1474 if ($throwException) {
1475 throw new LogicException('Can not send signal on a non running process.');
1476 }
1477
1478 return false;
1479 }
1480
1481 if ('\\' === DIRECTORY_SEPARATOR) {
1482 exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
1483 if ($exitCode && $this->isRunning()) {
1484 if ($throwException) {
1485 throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
1486 }
1487
1488 return false;
1489 }
1490 } else {
1491 if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
1492 $ok = @proc_terminate($this->process, $signal);
1493 } elseif (function_exists('posix_kill')) {
1494 $ok = @posix_kill($pid, $signal);
1495 } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) {
1496 $ok = false === fgets($pipes[2]);
1497 }
1498 if (!$ok) {
1499 if ($throwException) {
1500 throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
1501 }
1502
1503 return false;
1504 }
1505 }
1506
1507 $this->latestSignal = (int) $signal;
1508 $this->fallbackStatus['signaled'] = true;
1509 $this->fallbackStatus['exitcode'] = -1;
1510 $this->fallbackStatus['termsig'] = $this->latestSignal;
1511
1512 return true;
1513 }
1514
1522 private function requireProcessIsStarted($functionName)
1523 {
1524 if (!$this->isStarted()) {
1525 throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
1526 }
1527 }
1528
1536 private function requireProcessIsTerminated($functionName)
1537 {
1538 if (!$this->isTerminated()) {
1539 throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
1540 }
1541 }
1542}
sprintf('%.4f', $callTime)
$result
An exception for terminatinating execution or to throw for unit testing.
InvalidArgumentException for the Process Component.
LogicException for the Process Component.
Exception that is thrown when a process times out.
RuntimeException for the Process Component.
UnixPipes implementation uses unix pipes as handles.
Definition: UnixPipes.php:24
WindowsPipes implementation uses temporary files as handles.
static validateInput($caller, $input)
Validates and normalizes a Process input.
static escapeArgument($argument)
Escapes a string to be used as a shell argument.
Process is a thin wrapper around proc_* functions to easily start independent PHP processes.
Definition: Process.php:31
isTty()
Checks if the TTY mode is enabled.
Definition: Process.php:993
hasBeenSignaled()
Returns true if the child process has been terminated by an uncaught signal.
Definition: Process.php:695
getDescriptors()
Creates the descriptors needed by the proc_open.
Definition: Process.php:1252
setCommandLine($commandline)
Sets the command line to be executed.
Definition: Process.php:897
stop($timeout=10, $signal=null)
Stops the process.
Definition: Process.php:819
isPty()
Returns PTY state.
Definition: Process.php:1017
getIterator($flags=0)
Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
Definition: Process.php:518
readPipesForOutput($caller, $blocking=false)
Reads pipes for the freshest output.
Definition: Process.php:1355
enableOutput()
Enables fetching output and error output from the underlying process.
Definition: Process.php:443
wait(callable $callback=null)
Waits for the process to terminate.
Definition: Process.php:355
setEnv(array $env)
Sets the environment variables.
Definition: Process.php:1075
isTerminated()
Checks if the process is terminated.
Definition: Process.php:790
validateTimeout($timeout)
Validates and returns the filtered timeout.
Definition: Process.php:1375
run($callback=null)
Runs the process.
Definition: Process.php:205
getIncrementalOutput()
Returns the output incrementally.
Definition: Process.php:494
clearOutput()
Clears the process output.
Definition: Process.php:569
getInput()
Gets the Process input.
Definition: Process.php:1095
setPty($bool)
Sets PTY mode.
Definition: Process.php:1005
addOutput($line)
Adds a line to the STDOUT stream.
Definition: Process.php:855
getExitCode()
Returns the exit code returned by the process.
Definition: Process.php:644
clearErrorOutput()
Clears the process output.
Definition: Process.php:628
setOptions(array $options)
Sets the options for proc_open.
Definition: Process.php:1139
getErrorOutput()
Returns the current error output of the process (STDERR).
Definition: Process.php:586
doSignal($signal, $throwException)
Sends a POSIX signal to the process.
Definition: Process.php:1471
getEnhanceWindowsCompatibility()
Gets whether or not Windows compatibility is enabled.
Definition: Process.php:1153
updateStatus($blocking)
Updates the status of the process, reads pipes.
Definition: Process.php:1306
isOutputDisabled()
Returns true in case the output is disabled, false otherwise.
Definition: Process.php:459
getStopSignal()
Returns the number of the signal that caused the child process to stop its execution.
Definition: Process.php:752
isStarted()
Checks if the process has been started with no regard to the current state.
Definition: Process.php:780
isSuccessful()
Checks if the process ended successfully.
Definition: Process.php:680
restart(callable $callback=null)
Restarts the process.
Definition: Process.php:328
getCommandLine()
Gets the command line to be executed.
Definition: Process.php:885
setEnhanceWindowsCompatibility($enhance)
Sets whether or not Windows compatibility is enabled.
Definition: Process.php:1165
buildCallback(callable $callback=null)
Builds up the callback used by wait().
Definition: Process.php:1276
hasBeenStopped()
Returns true if the child process has been stopped by a signal.
Definition: Process.php:736
setInput($input)
Sets the input.
Definition: Process.php:1111
close()
Closes process resource, closes file handles, sets the exitcode.
Definition: Process.php:1413
mustRun(callable $callback=null)
Runs the process.
Definition: Process.php:225
start(callable $callback=null)
Starts the process and returns after writing the input to STDIN.
Definition: Process.php:257
getEnhanceSigchildCompatibility()
Returns whether sigchild compatibility mode is activated or not.
Definition: Process.php:1177
setIdleTimeout($timeout)
Sets the process idle timeout (max.
Definition: Process.php:954
setTty($tty)
Enables or disables the TTY mode.
Definition: Process.php:974
addErrorOutput($line)
Adds a line to the STDERR stream.
Definition: Process.php:871
isRunning()
Checks if the process is currently running.
Definition: Process.php:764
getPid()
Returns the Pid (process identifier), if applicable.
Definition: Process.php:391
setWorkingDirectory($cwd)
Sets the current working directory.
Definition: Process.php:1045
getOptions()
Gets the options for proc_open.
Definition: Process.php:1127
getWorkingDirectory()
Gets the working directory.
Definition: Process.php:1027
requireProcessIsStarted($functionName)
Ensures the process is running or terminated, throws a LogicException if the process has a not starte...
Definition: Process.php:1522
getTermSignal()
Returns the number of the signal that caused the child process to terminate its execution.
Definition: Process.php:716
checkTimeout()
Performs a check between the timeout definition and the time the process started.
Definition: Process.php:1208
getIncrementalErrorOutput()
Returns the errorOutput incrementally.
Definition: Process.php:609
getStatus()
Gets the process status.
Definition: Process.php:804
getIdleTimeout()
Gets the process idle timeout (max.
Definition: Process.php:919
setTimeout($timeout)
Sets the process timeout (max.
Definition: Process.php:935
requireProcessIsTerminated($functionName)
Ensures the process is terminated, throws a LogicException if the process has a status different than...
Definition: Process.php:1536
getTimeout()
Gets the process timeout (max.
Definition: Process.php:909
isSigchildEnabled()
Returns whether PHP has been compiled with the '–enable-sigchild' option or not.
Definition: Process.php:1331
static isPtySupported()
Returns whether PTY is supported on the current operating system.
Definition: Process.php:1232
disableOutput()
Disables fetching output and error output from the underlying process.
Definition: Process.php:422
resetProcessData()
Resets data related to the latest run of the process.
Definition: Process.php:1443
getExitCodeText()
Returns a string representation for the exit code returned by the process.
Definition: Process.php:666
setEnhanceSigchildCompatibility($enhance)
Activates sigchild compatibility mode.
Definition: Process.php:1193
readPipes($blocking, $close)
Reads pipes, executes callback.
Definition: Process.php:1394
signal($signal)
Sends a POSIX signal to the process.
Definition: Process.php:407
__construct($commandline, $cwd=null, array $env=null, $input=null, $timeout=60, array $options=array())
Constructor.
Definition: Process.php:147
getEnv()
Gets the environment variables.
Definition: Process.php:1057
getOutput()
Returns the current output of the process (STDOUT).
Definition: Process.php:472
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\s+" &#(? foreach( $entity_files as $file) $output
PipesInterface manages descriptors and pipes for the use of proc_open.
input
Definition: langcheck.php:166
$ret
Definition: parser.php:6
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27