| Total Complexity | 173 | 
| Total Lines | 1245 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like Process often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Process, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 21 | class Process  | 
            ||
| 
                                                                                                    
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 22 | { | 
            ||
| 23 | |||
| 24 | const ERR = 'err';  | 
            ||
| 25 | const OUT = 'out';  | 
            ||
| 26 | |||
| 27 | const STATUS_READY = 'ready';  | 
            ||
| 28 | const STATUS_STARTED = 'started';  | 
            ||
| 29 | const STATUS_TERMINATED = 'terminated';  | 
            ||
| 30 | |||
| 31 | const STDIN = 0;  | 
            ||
| 32 | const STDOUT = 1;  | 
            ||
| 33 | const STDERR = 2;  | 
            ||
| 34 | |||
| 35 | const TIMEOUT_PRECISION = 0.2;  | 
            ||
| 36 | |||
| 37 | private $callback;  | 
            ||
| 38 | private $commandline;  | 
            ||
| 39 | private $cwd;  | 
            ||
| 40 | private $env;  | 
            ||
| 41 | private $input;  | 
            ||
| 42 | private $starttime;  | 
            ||
| 43 | private $lastOutputTime;  | 
            ||
| 44 | private $timeout;  | 
            ||
| 45 | private $idleTimeout;  | 
            ||
| 46 | private $options;  | 
            ||
| 47 | private $exitcode;  | 
            ||
| 48 | private $fallbackExitcode;  | 
            ||
| 49 | private $processInformation;  | 
            ||
| 50 | private $outputDisabled = false;  | 
            ||
| 51 | private $stdout;  | 
            ||
| 52 | private $stderr;  | 
            ||
| 53 | private $enhanceWindowsCompatibility = true;  | 
            ||
| 54 | private $enhanceSigchildCompatibility;  | 
            ||
| 55 | private $process;  | 
            ||
| 56 | private $status = self::STATUS_READY;  | 
            ||
| 57 | private $incrementalOutputOffset = 0;  | 
            ||
| 58 | private $incrementalErrorOutputOffset = 0;  | 
            ||
| 59 | private $tty;  | 
            ||
| 60 | private $pty;  | 
            ||
| 61 | |||
| 62 | private $useFileHandles = false;  | 
            ||
| 63 | |||
| 64 | /** @var Pipes */  | 
            ||
| 65 | private $processPipes;  | 
            ||
| 66 | |||
| 67 | private $latestSignal;  | 
            ||
| 68 | |||
| 69 | private static $sigchild;  | 
            ||
| 70 | |||
| 71 | /**  | 
            ||
| 72 | * @var array  | 
            ||
| 73 | */  | 
            ||
| 74 | public static $exitCodes = [  | 
            ||
| 75 | 0 => 'OK',  | 
            ||
| 76 | 1 => 'General error',  | 
            ||
| 77 | 2 => 'Misuse of shell builtins',  | 
            ||
| 78 | 126 => 'Invoked command cannot execute',  | 
            ||
| 79 | 127 => 'Command not found',  | 
            ||
| 80 | 128 => 'Invalid exit argument',  | 
            ||
| 81 | // signals  | 
            ||
| 82 | 129 => 'Hangup',  | 
            ||
| 83 | 130 => 'Interrupt',  | 
            ||
| 84 | 131 => 'Quit and dump core',  | 
            ||
| 85 | 132 => 'Illegal instruction',  | 
            ||
| 86 | 133 => 'Trace/breakpoint trap',  | 
            ||
| 87 | 134 => 'Process aborted',  | 
            ||
| 88 | 135 => 'Bus error: "access to undefined portion of memory object"',  | 
            ||
| 89 | 136 => 'Floating point exception: "erroneous arithmetic operation"',  | 
            ||
| 90 | 137 => 'Kill (terminate immediately)',  | 
            ||
| 91 | 138 => 'User-defined 1',  | 
            ||
| 92 | 139 => 'Segmentation violation',  | 
            ||
| 93 | 140 => 'User-defined 2',  | 
            ||
| 94 | 141 => 'Write to pipe with no one reading',  | 
            ||
| 95 | 142 => 'Signal raised by alarm',  | 
            ||
| 96 | 143 => 'Termination (request to terminate)',  | 
            ||
| 97 | // 144 - not defined  | 
            ||
| 98 | 145 => 'Child process terminated, stopped (or continued*)',  | 
            ||
| 99 | 146 => 'Continue if stopped',  | 
            ||
| 100 | 147 => 'Stop executing temporarily',  | 
            ||
| 101 | 148 => 'Terminal stop signal',  | 
            ||
| 102 |         149 => 'Background process attempting to read from tty ("in")', | 
            ||
| 103 |         150 => 'Background process attempting to write to tty ("out")', | 
            ||
| 104 | 151 => 'Urgent data available on socket',  | 
            ||
| 105 | 152 => 'CPU time limit exceeded',  | 
            ||
| 106 | 153 => 'File size limit exceeded',  | 
            ||
| 107 | 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',  | 
            ||
| 108 | 155 => 'Profiling timer expired',  | 
            ||
| 109 | // 156 - not defined  | 
            ||
| 110 | 157 => 'Pollable event',  | 
            ||
| 111 | // 158 - not defined  | 
            ||
| 112 | 159 => 'Bad syscall',  | 
            ||
| 113 | ];  | 
            ||
| 114 | |||
| 115 | /**  | 
            ||
| 116 | * 构造方法  | 
            ||
| 117 | * @access public  | 
            ||
| 118 | * @param string $commandline 指令  | 
            ||
| 119 | * @param string|null $cwd 工作目录  | 
            ||
| 120 | * @param array|null $env 环境变量  | 
            ||
| 121 | * @param string|null $input 输入  | 
            ||
| 122 | * @param int|float|null $timeout 超时时间  | 
            ||
| 123 | * @param array $options proc_open的选项  | 
            ||
| 124 | * @throws \RuntimeException  | 
            ||
| 125 | * @api  | 
            ||
| 126 | */  | 
            ||
| 127 | public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = [])  | 
            ||
| 128 |     { | 
            ||
| 129 |         if (!function_exists('proc_open')) { | 
            ||
| 130 |             throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); | 
            ||
| 131 | }  | 
            ||
| 132 | |||
| 133 | $this->commandline = $commandline;  | 
            ||
| 134 | $this->cwd = $cwd;  | 
            ||
| 135 | |||
| 136 |         if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { | 
            ||
| 137 | $this->cwd = getcwd();  | 
            ||
| 138 | }  | 
            ||
| 139 |         if (null !== $env) { | 
            ||
| 140 | $this->setEnv($env);  | 
            ||
| 141 | }  | 
            ||
| 142 | |||
| 143 | $this->input = $input;  | 
            ||
| 144 | $this->setTimeout($timeout);  | 
            ||
| 145 | $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR;  | 
            ||
| 146 | $this->pty = false;  | 
            ||
| 147 | $this->enhanceWindowsCompatibility = true;  | 
            ||
| 148 | $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled();  | 
            ||
| 149 | $this->options = array_replace([  | 
            ||
| 150 | 'suppress_errors' => true,  | 
            ||
| 151 | 'binary_pipes' => true,  | 
            ||
| 152 | ], $options);  | 
            ||
| 153 | }  | 
            ||
| 154 | |||
| 155 | public function __destruct()  | 
            ||
| 156 |     { | 
            ||
| 157 | $this->stop();  | 
            ||
| 158 | }  | 
            ||
| 159 | |||
| 160 | public function __clone()  | 
            ||
| 161 |     { | 
            ||
| 162 | $this->resetProcessData();  | 
            ||
| 163 | }  | 
            ||
| 164 | |||
| 165 | /**  | 
            ||
| 166 | * 运行指令  | 
            ||
| 167 | * @access public  | 
            ||
| 168 | * @param callback|null $callback  | 
            ||
| 169 | * @return int  | 
            ||
| 170 | */  | 
            ||
| 171 | public function run($callback = null)  | 
            ||
| 172 |     { | 
            ||
| 173 | $this->start($callback);  | 
            ||
| 174 | |||
| 175 | return $this->wait();  | 
            ||
| 176 | }  | 
            ||
| 177 | |||
| 178 | /**  | 
            ||
| 179 | * 运行指令  | 
            ||
| 180 | * @access public  | 
            ||
| 181 | * @param callable|null $callback  | 
            ||
| 182 | * @return self  | 
            ||
| 183 | * @throws \RuntimeException  | 
            ||
| 184 | * @throws ProcessFailedException  | 
            ||
| 185 | */  | 
            ||
| 186 | public function mustRun($callback = null)  | 
            ||
| 187 |     { | 
            ||
| 188 |         if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { | 
            ||
| 189 |             throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); | 
            ||
| 190 | }  | 
            ||
| 191 | |||
| 192 |         if (0 !== $this->run($callback)) { | 
            ||
| 193 | throw new ProcessFailedException($this);  | 
            ||
| 194 | }  | 
            ||
| 195 | |||
| 196 | return $this;  | 
            ||
| 197 | }  | 
            ||
| 198 | |||
| 199 | /**  | 
            ||
| 200 | * 启动进程并写到 STDIN 输入后返回。  | 
            ||
| 201 | * @access public  | 
            ||
| 202 | * @param callable|null $callback  | 
            ||
| 203 | * @throws \RuntimeException  | 
            ||
| 204 | * @throws \RuntimeException  | 
            ||
| 205 | * @throws \LogicException  | 
            ||
| 206 | */  | 
            ||
| 207 | public function start($callback = null)  | 
            ||
| 208 |     { | 
            ||
| 209 |         if ($this->isRunning()) { | 
            ||
| 210 |             throw new \RuntimeException('Process is already running'); | 
            ||
| 211 | }  | 
            ||
| 212 |         if ($this->outputDisabled && null !== $callback) { | 
            ||
| 213 |             throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); | 
            ||
| 214 | }  | 
            ||
| 215 | |||
| 216 | $this->resetProcessData();  | 
            ||
| 217 | $this->starttime = $this->lastOutputTime = microtime(true);  | 
            ||
| 218 | $this->callback = $this->buildCallback($callback);  | 
            ||
| 219 | $descriptors = $this->getDescriptors();  | 
            ||
| 220 | |||
| 221 | $commandline = $this->commandline;  | 
            ||
| 222 | |||
| 223 |         if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { | 
            ||
| 224 |             $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; | 
            ||
| 225 |             foreach ($this->processPipes->getFiles() as $offset => $filename) { | 
            ||
| 226 | $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename);  | 
            ||
| 227 | }  | 
            ||
| 228 | $commandline .= '"';  | 
            ||
| 229 | |||
| 230 |             if (!isset($this->options['bypass_shell'])) { | 
            ||
| 231 | $this->options['bypass_shell'] = true;  | 
            ||
| 232 | }  | 
            ||
| 233 | }  | 
            ||
| 234 | |||
| 235 | $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options);  | 
            ||
| 236 | |||
| 237 |         if (!is_resource($this->process)) { | 
            ||
| 238 |             throw new \RuntimeException('Unable to launch a new process.'); | 
            ||
| 239 | }  | 
            ||
| 240 | $this->status = self::STATUS_STARTED;  | 
            ||
| 241 | |||
| 242 |         if ($this->tty) { | 
            ||
| 243 | return;  | 
            ||
| 244 | }  | 
            ||
| 245 | |||
| 246 | $this->updateStatus(false);  | 
            ||
| 247 | $this->checkTimeout();  | 
            ||
| 248 | }  | 
            ||
| 249 | |||
| 250 | /**  | 
            ||
| 251 | * 重启进程  | 
            ||
| 252 | * @access public  | 
            ||
| 253 | * @param callable|null $callback  | 
            ||
| 254 | * @return Process  | 
            ||
| 255 | * @throws \RuntimeException  | 
            ||
| 256 | * @throws \RuntimeException  | 
            ||
| 257 | */  | 
            ||
| 258 | public function restart($callback = null)  | 
            ||
| 259 |     { | 
            ||
| 260 |         if ($this->isRunning()) { | 
            ||
| 261 |             throw new \RuntimeException('Process is already running'); | 
            ||
| 262 | }  | 
            ||
| 263 | |||
| 264 | $process = clone $this;  | 
            ||
| 265 | $process->start($callback);  | 
            ||
| 266 | |||
| 267 | return $process;  | 
            ||
| 268 | }  | 
            ||
| 269 | |||
| 270 | /**  | 
            ||
| 271 | * 等待要终止的进程  | 
            ||
| 272 | * @access public  | 
            ||
| 273 | * @param callable|null $callback  | 
            ||
| 274 | * @return int  | 
            ||
| 275 | */  | 
            ||
| 276 | public function wait($callback = null)  | 
            ||
| 277 |     { | 
            ||
| 278 | $this->requireProcessIsStarted(__FUNCTION__);  | 
            ||
| 279 | |||
| 280 | $this->updateStatus(false);  | 
            ||
| 281 |         if (null !== $callback) { | 
            ||
| 282 | $this->callback = $this->buildCallback($callback);  | 
            ||
| 283 | }  | 
            ||
| 284 | |||
| 285 |         do { | 
            ||
| 286 | $this->checkTimeout();  | 
            ||
| 287 | $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();  | 
            ||
| 288 | $close = '\\' !== DIRECTORY_SEPARATOR || !$running;  | 
            ||
| 289 | $this->readPipes(true, $close);  | 
            ||
| 290 | } while ($running);  | 
            ||
| 291 | |||
| 292 |         while ($this->isRunning()) { | 
            ||
| 293 | usleep(1000);  | 
            ||
| 294 | }  | 
            ||
| 295 | |||
| 296 |         if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { | 
            ||
| 297 |             throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); | 
            ||
| 298 | }  | 
            ||
| 299 | |||
| 300 | return $this->exitcode;  | 
            ||
| 301 | }  | 
            ||
| 302 | |||
| 303 | /**  | 
            ||
| 304 | * 获取PID  | 
            ||
| 305 | * @access public  | 
            ||
| 306 | * @return int|null  | 
            ||
| 307 | * @throws \RuntimeException  | 
            ||
| 308 | */  | 
            ||
| 309 | public function getPid()  | 
            ||
| 310 |     { | 
            ||
| 311 |         if ($this->isSigchildEnabled()) { | 
            ||
| 312 |             throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); | 
            ||
| 313 | }  | 
            ||
| 314 | |||
| 315 | $this->updateStatus(false);  | 
            ||
| 316 | |||
| 317 | return $this->isRunning() ? $this->processInformation['pid'] : null;  | 
            ||
| 318 | }  | 
            ||
| 319 | |||
| 320 | /**  | 
            ||
| 321 | * 将一个 POSIX 信号发送到进程中  | 
            ||
| 322 | * @access public  | 
            ||
| 323 | * @param int $signal  | 
            ||
| 324 | * @return Process  | 
            ||
| 325 | */  | 
            ||
| 326 | public function signal($signal)  | 
            ||
| 327 |     { | 
            ||
| 328 | $this->doSignal($signal, true);  | 
            ||
| 329 | |||
| 330 | return $this;  | 
            ||
| 331 | }  | 
            ||
| 332 | |||
| 333 | /**  | 
            ||
| 334 | * 禁用从底层过程获取输出和错误输出。  | 
            ||
| 335 | * @access public  | 
            ||
| 336 | * @return Process  | 
            ||
| 337 | */  | 
            ||
| 338 | public function disableOutput()  | 
            ||
| 339 |     { | 
            ||
| 340 |         if ($this->isRunning()) { | 
            ||
| 341 |             throw new \RuntimeException('Disabling output while the process is running is not possible.'); | 
            ||
| 342 | }  | 
            ||
| 343 |         if (null !== $this->idleTimeout) { | 
            ||
| 344 |             throw new \LogicException('Output can not be disabled while an idle timeout is set.'); | 
            ||
| 345 | }  | 
            ||
| 346 | |||
| 347 | $this->outputDisabled = true;  | 
            ||
| 348 | |||
| 349 | return $this;  | 
            ||
| 350 | }  | 
            ||
| 351 | |||
| 352 | /**  | 
            ||
| 353 | * 开启从底层过程获取输出和错误输出。  | 
            ||
| 354 | * @access public  | 
            ||
| 355 | * @return Process  | 
            ||
| 356 | * @throws \RuntimeException  | 
            ||
| 357 | */  | 
            ||
| 358 | public function enableOutput()  | 
            ||
| 359 |     { | 
            ||
| 360 |         if ($this->isRunning()) { | 
            ||
| 361 |             throw new \RuntimeException('Enabling output while the process is running is not possible.'); | 
            ||
| 362 | }  | 
            ||
| 363 | |||
| 364 | $this->outputDisabled = false;  | 
            ||
| 365 | |||
| 366 | return $this;  | 
            ||
| 367 | }  | 
            ||
| 368 | |||
| 369 | /**  | 
            ||
| 370 | * 输出是否禁用  | 
            ||
| 371 | * @access public  | 
            ||
| 372 | * @return bool  | 
            ||
| 373 | */  | 
            ||
| 374 | public function isOutputDisabled()  | 
            ||
| 375 |     { | 
            ||
| 376 | return $this->outputDisabled;  | 
            ||
| 377 | }  | 
            ||
| 378 | |||
| 379 | /**  | 
            ||
| 380 | * 获取当前的输出管道  | 
            ||
| 381 | * @access public  | 
            ||
| 382 | * @return string  | 
            ||
| 383 | * @throws \LogicException  | 
            ||
| 384 | * @api  | 
            ||
| 385 | */  | 
            ||
| 386 | public function getOutput()  | 
            ||
| 387 |     { | 
            ||
| 388 |         if ($this->outputDisabled) { | 
            ||
| 389 |             throw new \LogicException('Output has been disabled.'); | 
            ||
| 390 | }  | 
            ||
| 391 | |||
| 392 | $this->requireProcessIsStarted(__FUNCTION__);  | 
            ||
| 393 | |||
| 394 | $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);  | 
            ||
| 395 | |||
| 396 | return $this->stdout;  | 
            ||
| 397 | }  | 
            ||
| 398 | |||
| 399 | /**  | 
            ||
| 400 | * 以增量方式返回的输出结果。  | 
            ||
| 401 | * @access public  | 
            ||
| 402 | * @return string  | 
            ||
| 403 | */  | 
            ||
| 404 | public function getIncrementalOutput()  | 
            ||
| 405 |     { | 
            ||
| 406 | $this->requireProcessIsStarted(__FUNCTION__);  | 
            ||
| 407 | |||
| 408 | $data = $this->getOutput();  | 
            ||
| 409 | |||
| 410 | $latest = substr($data, $this->incrementalOutputOffset);  | 
            ||
| 411 | |||
| 412 |         if (false === $latest) { | 
            ||
| 413 | return '';  | 
            ||
| 414 | }  | 
            ||
| 415 | |||
| 416 | $this->incrementalOutputOffset = strlen($data);  | 
            ||
| 417 | |||
| 418 | return $latest;  | 
            ||
| 419 | }  | 
            ||
| 420 | |||
| 421 | /**  | 
            ||
| 422 | * 清空输出  | 
            ||
| 423 | * @access public  | 
            ||
| 424 | * @return Process  | 
            ||
| 425 | */  | 
            ||
| 426 | public function clearOutput()  | 
            ||
| 427 |     { | 
            ||
| 428 | $this->stdout = '';  | 
            ||
| 429 | $this->incrementalOutputOffset = 0;  | 
            ||
| 430 | |||
| 431 | return $this;  | 
            ||
| 432 | }  | 
            ||
| 433 | |||
| 434 | /**  | 
            ||
| 435 | * 返回当前的错误输出的过程 (STDERR)。  | 
            ||
| 436 | * @access public  | 
            ||
| 437 | * @return string  | 
            ||
| 438 | */  | 
            ||
| 439 | public function getErrorOutput()  | 
            ||
| 440 |     { | 
            ||
| 441 |         if ($this->outputDisabled) { | 
            ||
| 442 |             throw new \LogicException('Output has been disabled.'); | 
            ||
| 443 | }  | 
            ||
| 444 | |||
| 445 | $this->requireProcessIsStarted(__FUNCTION__);  | 
            ||
| 446 | |||
| 447 | $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);  | 
            ||
| 448 | |||
| 449 | return $this->stderr;  | 
            ||
| 450 | }  | 
            ||
| 451 | |||
| 452 | /**  | 
            ||
| 453 | * 以增量方式返回 errorOutput  | 
            ||
| 454 | * @access public  | 
            ||
| 455 | * @return string  | 
            ||
| 456 | */  | 
            ||
| 457 | public function getIncrementalErrorOutput()  | 
            ||
| 458 |     { | 
            ||
| 459 | $this->requireProcessIsStarted(__FUNCTION__);  | 
            ||
| 460 | |||
| 461 | $data = $this->getErrorOutput();  | 
            ||
| 462 | |||
| 463 | $latest = substr($data, $this->incrementalErrorOutputOffset);  | 
            ||
| 464 | |||
| 465 |         if (false === $latest) { | 
            ||
| 466 | return '';  | 
            ||
| 467 | }  | 
            ||
| 468 | |||
| 469 | $this->incrementalErrorOutputOffset = strlen($data);  | 
            ||
| 470 | |||
| 471 | return $latest;  | 
            ||
| 472 | }  | 
            ||
| 473 | |||
| 474 | /**  | 
            ||
| 475 | * 清空 errorOutput  | 
            ||
| 476 | * @access public  | 
            ||
| 477 | * @return Process  | 
            ||
| 478 | */  | 
            ||
| 479 | public function clearErrorOutput()  | 
            ||
| 480 |     { | 
            ||
| 481 | $this->stderr = '';  | 
            ||
| 482 | $this->incrementalErrorOutputOffset = 0;  | 
            ||
| 483 | |||
| 484 | return $this;  | 
            ||
| 485 | }  | 
            ||
| 486 | |||
| 487 | /**  | 
            ||
| 488 | * 获取退出码  | 
            ||
| 489 | * @access public  | 
            ||
| 490 | * @return null|int  | 
            ||
| 491 | */  | 
            ||
| 492 | public function getExitCode()  | 
            ||
| 493 |     { | 
            ||
| 494 |         if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { | 
            ||
| 495 |             throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); | 
            ||
| 496 | }  | 
            ||
| 497 | |||
| 498 | $this->updateStatus(false);  | 
            ||
| 499 | |||
| 500 | return $this->exitcode;  | 
            ||
| 501 | }  | 
            ||
| 502 | |||
| 503 | /**  | 
            ||
| 504 | * 获取退出文本  | 
            ||
| 505 | * @access public  | 
            ||
| 506 | * @return null|string  | 
            ||
| 507 | */  | 
            ||
| 508 | public function getExitCodeText()  | 
            ||
| 509 |     { | 
            ||
| 510 |         if (null === $exitcode = $this->getExitCode()) { | 
            ||
| 511 | return;  | 
            ||
| 512 | }  | 
            ||
| 513 | |||
| 514 | return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';  | 
            ||
| 515 | }  | 
            ||
| 516 | |||
| 517 | /**  | 
            ||
| 518 | * 检查是否成功  | 
            ||
| 519 | * @access public  | 
            ||
| 520 | * @return bool  | 
            ||
| 521 | */  | 
            ||
| 522 | public function isSuccessful()  | 
            ||
| 523 |     { | 
            ||
| 524 | return 0 === $this->getExitCode();  | 
            ||
| 525 | }  | 
            ||
| 526 | |||
| 527 | /**  | 
            ||
| 528 | * 是否未捕获的信号已被终止子进程  | 
            ||
| 529 | * @access public  | 
            ||
| 530 | * @return bool  | 
            ||
| 531 | */  | 
            ||
| 532 | public function hasBeenSignaled()  | 
            ||
| 533 |     { | 
            ||
| 534 | $this->requireProcessIsTerminated(__FUNCTION__);  | 
            ||
| 535 | |||
| 536 |         if ($this->isSigchildEnabled()) { | 
            ||
| 537 |             throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); | 
            ||
| 538 | }  | 
            ||
| 539 | |||
| 540 | $this->updateStatus(false);  | 
            ||
| 541 | |||
| 542 | return $this->processInformation['signaled'];  | 
            ||
| 543 | }  | 
            ||
| 544 | |||
| 545 | /**  | 
            ||
| 546 | * 返回导致子进程终止其执行的数。  | 
            ||
| 547 | * @access public  | 
            ||
| 548 | * @return int  | 
            ||
| 549 | */  | 
            ||
| 550 | public function getTermSignal()  | 
            ||
| 551 |     { | 
            ||
| 552 | $this->requireProcessIsTerminated(__FUNCTION__);  | 
            ||
| 553 | |||
| 554 |         if ($this->isSigchildEnabled()) { | 
            ||
| 555 |             throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); | 
            ||
| 556 | }  | 
            ||
| 557 | |||
| 558 | $this->updateStatus(false);  | 
            ||
| 559 | |||
| 560 | return $this->processInformation['termsig'];  | 
            ||
| 561 | }  | 
            ||
| 562 | |||
| 563 | /**  | 
            ||
| 564 | * 检查子进程信号是否已停止  | 
            ||
| 565 | * @access public  | 
            ||
| 566 | * @return bool  | 
            ||
| 567 | */  | 
            ||
| 568 | public function hasBeenStopped()  | 
            ||
| 569 |     { | 
            ||
| 570 | $this->requireProcessIsTerminated(__FUNCTION__);  | 
            ||
| 571 | |||
| 572 | $this->updateStatus(false);  | 
            ||
| 573 | |||
| 574 | return $this->processInformation['stopped'];  | 
            ||
| 575 | }  | 
            ||
| 576 | |||
| 577 | /**  | 
            ||
| 578 | * 返回导致子进程停止其执行的数。  | 
            ||
| 579 | * @access public  | 
            ||
| 580 | * @return int  | 
            ||
| 581 | */  | 
            ||
| 582 | public function getStopSignal()  | 
            ||
| 583 |     { | 
            ||
| 584 | $this->requireProcessIsTerminated(__FUNCTION__);  | 
            ||
| 585 | |||
| 586 | $this->updateStatus(false);  | 
            ||
| 587 | |||
| 588 | return $this->processInformation['stopsig'];  | 
            ||
| 589 | }  | 
            ||
| 590 | |||
| 591 | /**  | 
            ||
| 592 | * 检查是否正在运行  | 
            ||
| 593 | * @access public  | 
            ||
| 594 | * @return bool  | 
            ||
| 595 | */  | 
            ||
| 596 | public function isRunning()  | 
            ||
| 597 |     { | 
            ||
| 598 |         if (self::STATUS_STARTED !== $this->status) { | 
            ||
| 599 | return false;  | 
            ||
| 600 | }  | 
            ||
| 601 | |||
| 602 | $this->updateStatus(false);  | 
            ||
| 603 | |||
| 604 | return $this->processInformation['running'];  | 
            ||
| 605 | }  | 
            ||
| 606 | |||
| 607 | /**  | 
            ||
| 608 | * 检查是否已开始  | 
            ||
| 609 | * @access public  | 
            ||
| 610 | * @return bool  | 
            ||
| 611 | */  | 
            ||
| 612 | public function isStarted()  | 
            ||
| 613 |     { | 
            ||
| 614 | return self::STATUS_READY != $this->status;  | 
            ||
| 615 | }  | 
            ||
| 616 | |||
| 617 | /**  | 
            ||
| 618 | * 检查是否已终止  | 
            ||
| 619 | * @access public  | 
            ||
| 620 | * @return bool  | 
            ||
| 621 | */  | 
            ||
| 622 | public function isTerminated()  | 
            ||
| 623 |     { | 
            ||
| 624 | $this->updateStatus(false);  | 
            ||
| 625 | |||
| 626 | return self::STATUS_TERMINATED == $this->status;  | 
            ||
| 627 | }  | 
            ||
| 628 | |||
| 629 | /**  | 
            ||
| 630 | * 获取当前的状态  | 
            ||
| 631 | * @access public  | 
            ||
| 632 | * @return string  | 
            ||
| 633 | */  | 
            ||
| 634 | public function getStatus()  | 
            ||
| 639 | }  | 
            ||
| 640 | |||
| 641 | /**  | 
            ||
| 642 | * 终止进程  | 
            ||
| 643 | * @access public  | 
            ||
| 644 | */  | 
            ||
| 645 | public function stop()  | 
            ||
| 646 |     { | 
            ||
| 647 |         if ($this->isRunning()) { | 
            ||
| 648 |             if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) { | 
            ||
| 649 |                 exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); | 
            ||
| 650 |                 if ($exitCode > 0) { | 
            ||
| 651 |                     throw new \RuntimeException('Unable to kill the process'); | 
            ||
| 652 | }  | 
            ||
| 653 |             } else { | 
            ||
| 654 |                 $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); | 
            ||
| 655 |                 foreach ($pids as $pid) { | 
            ||
| 656 |                     if (is_numeric($pid)) { | 
            ||
| 657 | posix_kill($pid, 9);  | 
            ||
| 658 | }  | 
            ||
| 659 | }  | 
            ||
| 660 | }  | 
            ||
| 661 | }  | 
            ||
| 662 | |||
| 663 | $this->updateStatus(false);  | 
            ||
| 664 |         if ($this->processInformation['running']) { | 
            ||
| 665 | $this->close();  | 
            ||
| 666 | }  | 
            ||
| 667 | |||
| 668 | return $this->exitcode;  | 
            ||
| 669 | }  | 
            ||
| 670 | |||
| 671 | /**  | 
            ||
| 672 | * 添加一行输出  | 
            ||
| 673 | * @access public  | 
            ||
| 674 | * @param string $line  | 
            ||
| 
                                                                                                                                                        
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 675 | */  | 
            ||
| 676 | public function addOutput($line)  | 
            ||
| 677 | { | 
            ||
| 678 | $this->lastOutputTime = microtime(true);  | 
            ||
| 679 | $this->stdout .= $line;  | 
            ||
| 680 | }  | 
            ||
| 681 | |||
| 682 | /**  | 
            ||
| 683 | * 添加一行错误输出  | 
            ||
| 684 | * @access public  | 
            ||
| 685 | * @param string $line  | 
            ||
| 
                                                                                                                                                        
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 686 | */  | 
            ||
| 687 | public function addErrorOutput($line)  | 
            ||
| 688 | { | 
            ||
| 689 | $this->lastOutputTime = microtime(true);  | 
            ||
| 690 | $this->stderr .= $line;  | 
            ||
| 691 | }  | 
            ||
| 692 | |||
| 693 | /**  | 
            ||
| 694 | * 获取被执行的指令  | 
            ||
| 695 | * @access public  | 
            ||
| 696 | * @return string  | 
            ||
| 697 | */  | 
            ||
| 698 | public function getCommandLine()  | 
            ||
| 699 | { | 
            ||
| 700 | return $this->commandline;  | 
            ||
| 701 | }  | 
            ||
| 702 | |||
| 703 | /**  | 
            ||
| 704 | * 设置指令  | 
            ||
| 705 | * @access public  | 
            ||
| 706 | * @param string $commandline  | 
            ||
| 
                                                                                                                                                        
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 707 | * @return self  | 
            ||
| 708 | */  | 
            ||
| 709 | public function setCommandLine($commandline)  | 
            ||
| 710 | { | 
            ||
| 711 | $this->commandline = $commandline;  | 
            ||
| 712 | |||
| 713 | return $this;  | 
            ||
| 714 | }  | 
            ||
| 715 | |||
| 716 | /**  | 
            ||
| 717 | * 获取超时时间  | 
            ||
| 718 | * @access public  | 
            ||
| 719 | * @return float|null  | 
            ||
| 720 | */  | 
            ||
| 721 | public function getTimeout()  | 
            ||
| 722 | { | 
            ||
| 723 | return $this->timeout;  | 
            ||
| 724 | }  | 
            ||
| 725 | |||
| 726 | /**  | 
            ||
| 727 | * 获取idle超时时间  | 
            ||
| 728 | * @access public  | 
            ||
| 729 | * @return float|null  | 
            ||
| 730 | */  | 
            ||
| 731 | public function getIdleTimeout()  | 
            ||
| 732 | { | 
            ||
| 733 | return $this->idleTimeout;  | 
            ||
| 734 | }  | 
            ||
| 735 | |||
| 736 | /**  | 
            ||
| 737 | * 设置超时时间  | 
            ||
| 738 | * @access public  | 
            ||
| 739 | * @param int|float|null $timeout  | 
            ||
| 740 | * @return self  | 
            ||
| 741 | */  | 
            ||
| 742 | public function setTimeout($timeout)  | 
            ||
| 743 | { | 
            ||
| 744 | $this->timeout = $this->validateTimeout($timeout);  | 
            ||
| 745 | |||
| 746 | return $this;  | 
            ||
| 747 | }  | 
            ||
| 748 | |||
| 749 | /**  | 
            ||
| 750 | * 设置idle超时时间  | 
            ||
| 751 | * @access public  | 
            ||
| 752 | * @param int|float|null $timeout  | 
            ||
| 753 | * @return self  | 
            ||
| 754 | */  | 
            ||
| 755 | public function setIdleTimeout($timeout)  | 
            ||
| 756 | { | 
            ||
| 757 |         if (null !== $timeout && $this->outputDisabled) { | 
            ||
| 758 |             throw new \LogicException('Idle timeout can not be set while the output is disabled.'); | 
            ||
| 759 | }  | 
            ||
| 760 | |||
| 761 | $this->idleTimeout = $this->validateTimeout($timeout);  | 
            ||
| 762 | |||
| 763 | return $this;  | 
            ||
| 764 | }  | 
            ||
| 765 | |||
| 766 | /**  | 
            ||
| 767 | * 设置TTY  | 
            ||
| 768 | * @access public  | 
            ||
| 769 | * @param bool $tty  | 
            ||
| 770 | * @return self  | 
            ||
| 771 | */  | 
            ||
| 772 | public function setTty($tty)  | 
            ||
| 773 | { | 
            ||
| 774 |         if ('\\' === DIRECTORY_SEPARATOR && $tty) { | 
            ||
| 775 |             throw new \RuntimeException('TTY mode is not supported on Windows platform.'); | 
            ||
| 776 | }  | 
            ||
| 777 |         if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { | 
            ||
| 778 |             throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); | 
            ||
| 779 | }  | 
            ||
| 780 | |||
| 781 | $this->tty = (bool) $tty;  | 
            ||
| 782 | |||
| 783 | return $this;  | 
            ||
| 784 | }  | 
            ||
| 785 | |||
| 786 | /**  | 
            ||
| 787 | * 检查是否是tty模式  | 
            ||
| 788 | * @access public  | 
            ||
| 789 | * @return bool  | 
            ||
| 790 | */  | 
            ||
| 791 | public function isTty()  | 
            ||
| 792 | { | 
            ||
| 793 | return $this->tty;  | 
            ||
| 794 | }  | 
            ||
| 795 | |||
| 796 | /**  | 
            ||
| 797 | * 设置pty模式  | 
            ||
| 798 | * @access public  | 
            ||
| 799 | * @param bool $bool  | 
            ||
| 800 | * @return self  | 
            ||
| 801 | */  | 
            ||
| 802 | public function setPty($bool)  | 
            ||
| 803 | { | 
            ||
| 804 | $this->pty = (bool) $bool;  | 
            ||
| 805 | |||
| 806 | return $this;  | 
            ||
| 807 | }  | 
            ||
| 808 | |||
| 809 | /**  | 
            ||
| 810 | * 是否是pty模式  | 
            ||
| 811 | * @access public  | 
            ||
| 812 | * @return bool  | 
            ||
| 813 | */  | 
            ||
| 814 | public function isPty()  | 
            ||
| 815 | { | 
            ||
| 816 | return $this->pty;  | 
            ||
| 817 | }  | 
            ||
| 818 | |||
| 819 | /**  | 
            ||
| 820 | * 获取工作目录  | 
            ||
| 821 | * @access public  | 
            ||
| 822 | * @return string|null  | 
            ||
| 823 | */  | 
            ||
| 824 | public function getWorkingDirectory()  | 
            ||
| 825 | { | 
            ||
| 826 |         if (null === $this->cwd) { | 
            ||
| 827 | return getcwd() ?: null;  | 
            ||
| 828 | }  | 
            ||
| 829 | |||
| 830 | return $this->cwd;  | 
            ||
| 831 | }  | 
            ||
| 832 | |||
| 833 | /**  | 
            ||
| 834 | * 设置工作目录  | 
            ||
| 835 | * @access public  | 
            ||
| 836 | * @param string $cwd  | 
            ||
| 837 | * @return self  | 
            ||
| 838 | */  | 
            ||
| 839 | public function setWorkingDirectory($cwd)  | 
            ||
| 840 | { | 
            ||
| 841 | $this->cwd = $cwd;  | 
            ||
| 842 | |||
| 843 | return $this;  | 
            ||
| 844 | }  | 
            ||
| 845 | |||
| 846 | /**  | 
            ||
| 847 | * 获取环境变量  | 
            ||
| 848 | * @access public  | 
            ||
| 849 | * @return array  | 
            ||
| 850 | */  | 
            ||
| 851 | public function getEnv()  | 
            ||
| 852 | { | 
            ||
| 853 | return $this->env;  | 
            ||
| 854 | }  | 
            ||
| 855 | |||
| 856 | /**  | 
            ||
| 857 | * 设置环境变量  | 
            ||
| 858 | * @access public  | 
            ||
| 859 | * @param array $env  | 
            ||
| 860 | * @return self  | 
            ||
| 861 | */  | 
            ||
| 862 | public function setEnv(array $env)  | 
            ||
| 863 | { | 
            ||
| 864 |         $env = array_filter($env, function ($value) { | 
            ||
| 865 | return !is_array($value);  | 
            ||
| 866 | });  | 
            ||
| 867 | |||
| 868 | $this->env = [];  | 
            ||
| 869 |         foreach ($env as $key => $value) { | 
            ||
| 870 | $this->env[(binary) $key] = (binary) $value;  | 
            ||
| 871 | }  | 
            ||
| 872 | |||
| 873 | return $this;  | 
            ||
| 874 | }  | 
            ||
| 875 | |||
| 876 | /**  | 
            ||
| 877 | * 获取输入  | 
            ||
| 878 | * @access public  | 
            ||
| 879 | * @return null|string  | 
            ||
| 880 | */  | 
            ||
| 881 | public function getInput()  | 
            ||
| 882 | { | 
            ||
| 883 | return $this->input;  | 
            ||
| 884 | }  | 
            ||
| 885 | |||
| 886 | /**  | 
            ||
| 887 | * 设置输入  | 
            ||
| 888 | * @access public  | 
            ||
| 889 | * @param mixed $input  | 
            ||
| 890 | * @return self  | 
            ||
| 891 | */  | 
            ||
| 892 | public function setInput($input)  | 
            ||
| 893 | { | 
            ||
| 894 |         if ($this->isRunning()) { | 
            ||
| 895 |             throw new \LogicException('Input can not be set while the process is running.'); | 
            ||
| 896 | }  | 
            ||
| 897 | |||
| 898 |         $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); | 
            ||
| 899 | |||
| 900 | return $this;  | 
            ||
| 901 | }  | 
            ||
| 902 | |||
| 903 | /**  | 
            ||
| 904 | * 获取proc_open的选项  | 
            ||
| 905 | * @access public  | 
            ||
| 906 | * @return array  | 
            ||
| 907 | */  | 
            ||
| 908 | public function getOptions()  | 
            ||
| 909 | { | 
            ||
| 910 | return $this->options;  | 
            ||
| 911 | }  | 
            ||
| 912 | |||
| 913 | /**  | 
            ||
| 914 | * 设置proc_open的选项  | 
            ||
| 915 | * @access public  | 
            ||
| 916 | * @param array $options  | 
            ||
| 917 | * @return self  | 
            ||
| 918 | */  | 
            ||
| 919 | public function setOptions(array $options)  | 
            ||
| 920 | { | 
            ||
| 921 | $this->options = $options;  | 
            ||
| 922 | |||
| 923 | return $this;  | 
            ||
| 924 | }  | 
            ||
| 925 | |||
| 926 | /**  | 
            ||
| 927 | * 是否兼容windows  | 
            ||
| 928 | * @access public  | 
            ||
| 929 | * @return bool  | 
            ||
| 930 | */  | 
            ||
| 931 | public function getEnhanceWindowsCompatibility()  | 
            ||
| 932 | { | 
            ||
| 933 | return $this->enhanceWindowsCompatibility;  | 
            ||
| 934 | }  | 
            ||
| 935 | |||
| 936 | /**  | 
            ||
| 937 | * 设置是否兼容windows  | 
            ||
| 938 | * @access public  | 
            ||
| 939 | * @param bool $enhance  | 
            ||
| 940 | * @return self  | 
            ||
| 941 | */  | 
            ||
| 942 | public function setEnhanceWindowsCompatibility($enhance)  | 
            ||
| 943 | { | 
            ||
| 944 | $this->enhanceWindowsCompatibility = (bool) $enhance;  | 
            ||
| 945 | |||
| 946 | return $this;  | 
            ||
| 947 | }  | 
            ||
| 948 | |||
| 949 | /**  | 
            ||
| 950 | * 返回是否 sigchild 兼容模式激活  | 
            ||
| 951 | * @access public  | 
            ||
| 952 | * @return bool  | 
            ||
| 953 | */  | 
            ||
| 954 | public function getEnhanceSigchildCompatibility()  | 
            ||
| 955 | { | 
            ||
| 956 | return $this->enhanceSigchildCompatibility;  | 
            ||
| 957 | }  | 
            ||
| 958 | |||
| 959 | /**  | 
            ||
| 960 | * 激活 sigchild 兼容性模式。  | 
            ||
| 961 | * @access public  | 
            ||
| 962 | * @param bool $enhance  | 
            ||
| 963 | * @return self  | 
            ||
| 964 | */  | 
            ||
| 965 | public function setEnhanceSigchildCompatibility($enhance)  | 
            ||
| 966 | { | 
            ||
| 967 | $this->enhanceSigchildCompatibility = (bool) $enhance;  | 
            ||
| 968 | |||
| 969 | return $this;  | 
            ||
| 970 | }  | 
            ||
| 971 | |||
| 972 | /**  | 
            ||
| 973 | * 是否超时  | 
            ||
| 974 | */  | 
            ||
| 975 | public function checkTimeout()  | 
            ||
| 976 | { | 
            ||
| 977 |         if (self::STATUS_STARTED !== $this->status) { | 
            ||
| 978 | return;  | 
            ||
| 979 | }  | 
            ||
| 980 | |||
| 981 |         if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { | 
            ||
| 982 | $this->stop();  | 
            ||
| 983 | |||
| 984 | throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL);  | 
            ||
| 985 | }  | 
            ||
| 986 | |||
| 987 |         if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { | 
            ||
| 988 | $this->stop();  | 
            ||
| 989 | |||
| 990 | throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE);  | 
            ||
| 991 | }  | 
            ||
| 992 | }  | 
            ||
| 993 | |||
| 994 | /**  | 
            ||
| 995 | * 是否支持pty  | 
            ||
| 996 | * @access public  | 
            ||
| 997 | * @return bool  | 
            ||
| 998 | */  | 
            ||
| 999 | public static function isPtySupported()  | 
            ||
| 1000 | { | 
            ||
| 1001 | static $result;  | 
            ||
| 1002 | |||
| 1003 |         if (null !== $result) { | 
            ||
| 1004 | return $result;  | 
            ||
| 1005 | }  | 
            ||
| 1006 | |||
| 1007 |         if ('\\' === DIRECTORY_SEPARATOR) { | 
            ||
| 1008 | return $result = false;  | 
            ||
| 1009 | }  | 
            ||
| 1010 | |||
| 1011 |         $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); | 
            ||
| 1012 |         if (is_resource($proc)) { | 
            ||
| 1013 | proc_close($proc);  | 
            ||
| 1014 | |||
| 1015 | return $result = true;  | 
            ||
| 1016 | }  | 
            ||
| 1017 | |||
| 1018 | return $result = false;  | 
            ||
| 1019 | }  | 
            ||
| 1020 | |||
| 1021 | /**  | 
            ||
| 1022 | * 创建所需的 proc_open 的描述符  | 
            ||
| 1023 | * @access private  | 
            ||
| 1024 | * @return array  | 
            ||
| 1025 | */  | 
            ||
| 1026 | private function getDescriptors()  | 
            ||
| 1027 | { | 
            ||
| 1028 |         if ('\\' === DIRECTORY_SEPARATOR) { | 
            ||
| 1029 | $this->processPipes = WindowsPipes::create($this, $this->input);  | 
            ||
| 1030 |         } else { | 
            ||
| 1031 | $this->processPipes = UnixPipes::create($this, $this->input);  | 
            ||
| 1032 | }  | 
            ||
| 1033 | $descriptors = $this->processPipes->getDescriptors($this->outputDisabled);  | 
            ||
| 1034 | |||
| 1035 |         if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { | 
            ||
| 1036 | |||
| 1037 | $descriptors = array_merge($descriptors, [['pipe', 'w']]);  | 
            ||
| 1038 | |||
| 1039 |             $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; | 
            ||
| 1040 | }  | 
            ||
| 1041 | |||
| 1042 | return $descriptors;  | 
            ||
| 1043 | }  | 
            ||
| 1044 | |||
| 1045 | /**  | 
            ||
| 1046 | * 建立 wait () 使用的回调。  | 
            ||
| 1047 | * @access protected  | 
            ||
| 1048 | * @param callable|null $callback  | 
            ||
| 1049 | * @return callable  | 
            ||
| 1050 | */  | 
            ||
| 1051 | protected function buildCallback($callback)  | 
            ||
| 1052 | { | 
            ||
| 1053 | $out = self::OUT;  | 
            ||
| 1054 |         $callback = function ($type, $data) use ($callback, $out) { | 
            ||
| 1055 |             if ($out == $type) { | 
            ||
| 1056 | $this->addOutput($data);  | 
            ||
| 1057 |             } else { | 
            ||
| 1058 | $this->addErrorOutput($data);  | 
            ||
| 1059 | }  | 
            ||
| 1060 | |||
| 1061 |             if (null !== $callback) { | 
            ||
| 1062 | call_user_func($callback, $type, $data);  | 
            ||
| 1063 | }  | 
            ||
| 1064 | };  | 
            ||
| 1065 | |||
| 1066 | return $callback;  | 
            ||
| 1067 | }  | 
            ||
| 1068 | |||
| 1069 | /**  | 
            ||
| 1070 | * 更新状态  | 
            ||
| 1071 | * @access protected  | 
            ||
| 1072 | * @param bool $blocking  | 
            ||
| 
                                                                                                                                                        
                         1 ignored issue 
                            –
                            show
                         | 
                |||
| 1073 | */  | 
            ||
| 1074 | protected function updateStatus($blocking)  | 
            ||
| 1075 | { | 
            ||
| 1076 |         if (self::STATUS_STARTED !== $this->status) { | 
            ||
| 1077 | return;  | 
            ||
| 1078 | }  | 
            ||
| 1079 | |||
| 1080 | $this->processInformation = proc_get_status($this->process);  | 
            ||
| 1081 | $this->captureExitCode();  | 
            ||
| 1082 | |||
| 1083 | $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true);  | 
            ||
| 1084 | |||
| 1085 |         if (!$this->processInformation['running']) { | 
            ||
| 1086 | $this->close();  | 
            ||
| 1087 | }  | 
            ||
| 1088 | }  | 
            ||
| 1089 | |||
| 1090 | /**  | 
            ||
| 1091 | * 是否开启 '--enable-sigchild'  | 
            ||
| 1092 | * @access protected  | 
            ||
| 1093 | * @return bool  | 
            ||
| 1094 | */  | 
            ||
| 1095 | protected function isSigchildEnabled()  | 
            ||
| 1096 | { | 
            ||
| 1097 |         if (null !== self::$sigchild) { | 
            ||
| 1098 | return self::$sigchild;  | 
            ||
| 1099 | }  | 
            ||
| 1100 | |||
| 1101 |         if (!function_exists('phpinfo')) { | 
            ||
| 1102 | return self::$sigchild = false;  | 
            ||
| 1103 | }  | 
            ||
| 1104 | |||
| 1105 | ob_start();  | 
            ||
| 1106 | phpinfo(INFO_GENERAL);  | 
            ||
| 1107 | |||
| 1108 | return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');  | 
            ||
| 1109 | }  | 
            ||
| 1110 | |||
| 1111 | /**  | 
            ||
| 1112 | * 验证是否超时  | 
            ||
| 1113 | * @access private  | 
            ||
| 1114 | * @param int|float|null $timeout  | 
            ||
| 1115 | * @return float|null  | 
            ||
| 1116 | */  | 
            ||
| 1117 | private function validateTimeout($timeout)  | 
            ||
| 1128 | }  | 
            ||
| 1129 | |||
| 1130 | /**  | 
            ||
| 1131 | * 读取pipes  | 
            ||
| 1132 | * @access private  | 
            ||
| 1133 | * @param bool $blocking  | 
            ||
| 1134 | * @param bool $close  | 
            ||
| 1135 | */  | 
            ||
| 1136 | private function readPipes($blocking, $close)  | 
            ||
| 1137 | { | 
            ||
| 1138 | $result = $this->processPipes->readAndWrite($blocking, $close);  | 
            ||
| 1139 | |||
| 1140 | $callback = $this->callback;  | 
            ||
| 1141 |         foreach ($result as $type => $data) { | 
            ||
| 1142 |             if (3 == $type) { | 
            ||
| 1143 | $this->fallbackExitcode = (int) $data;  | 
            ||
| 1144 |             } else { | 
            ||
| 1145 | $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);  | 
            ||
| 1146 | }  | 
            ||
| 1147 | }  | 
            ||
| 1148 | }  | 
            ||
| 1149 | |||
| 1150 | /**  | 
            ||
| 1151 | * 捕获退出码  | 
            ||
| 1152 | */  | 
            ||
| 1153 | private function captureExitCode()  | 
            ||
| 1154 | { | 
            ||
| 1155 |         if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { | 
            ||
| 1156 | $this->exitcode = $this->processInformation['exitcode'];  | 
            ||
| 1157 | }  | 
            ||
| 1158 | }  | 
            ||
| 1159 | |||
| 1160 | /**  | 
            ||
| 1161 | * 关闭资源  | 
            ||
| 1162 | * @access private  | 
            ||
| 1163 | * @return int 退出码  | 
            ||
| 1164 | */  | 
            ||
| 1165 | private function close()  | 
            ||
| 1166 | { | 
            ||
| 1167 | $this->processPipes->close();  | 
            ||
| 1168 |         if (is_resource($this->process)) { | 
            ||
| 1169 | $exitcode = proc_close($this->process);  | 
            ||
| 1170 |         } else { | 
            ||
| 1171 | $exitcode = -1;  | 
            ||
| 1172 | }  | 
            ||
| 1173 | |||
| 1174 | $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1);  | 
            ||
| 1175 | $this->status = self::STATUS_TERMINATED;  | 
            ||
| 1176 | |||
| 1177 |         if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { | 
            ||
| 1178 | $this->exitcode = $this->fallbackExitcode;  | 
            ||
| 1179 | } elseif (-1 === $this->exitcode && $this->processInformation['signaled']  | 
            ||
| 1180 | && 0 < $this->processInformation['termsig']  | 
            ||
| 1181 |         ) { | 
            ||
| 1182 | $this->exitcode = 128 + $this->processInformation['termsig'];  | 
            ||
| 1183 | }  | 
            ||
| 1184 | |||
| 1185 | return $this->exitcode;  | 
            ||
| 1186 | }  | 
            ||
| 1187 | |||
| 1188 | /**  | 
            ||
| 1189 | * 重置数据  | 
            ||
| 1190 | */  | 
            ||
| 1191 | private function resetProcessData()  | 
            ||
| 1192 | { | 
            ||
| 1193 | $this->starttime = null;  | 
            ||
| 1194 | $this->callback = null;  | 
            ||
| 1195 | $this->exitcode = null;  | 
            ||
| 1196 | $this->fallbackExitcode = null;  | 
            ||
| 1197 | $this->processInformation = null;  | 
            ||
| 1198 | $this->stdout = null;  | 
            ||
| 1199 | $this->stderr = null;  | 
            ||
| 1200 | $this->process = null;  | 
            ||
| 1201 | $this->latestSignal = null;  | 
            ||
| 1202 | $this->status = self::STATUS_READY;  | 
            ||
| 1203 | $this->incrementalOutputOffset = 0;  | 
            ||
| 1204 | $this->incrementalErrorOutputOffset = 0;  | 
            ||
| 1205 | }  | 
            ||
| 1206 | |||
| 1207 | /**  | 
            ||
| 1208 | * 将一个 POSIX 信号发送到进程中。  | 
            ||
| 1209 | * @access private  | 
            ||
| 1210 | * @param int $signal  | 
            ||
| 1211 | * @param bool $throwException  | 
            ||
| 1212 | * @return bool  | 
            ||
| 1213 | */  | 
            ||
| 1214 | private function doSignal($signal, $throwException)  | 
            ||
| 1243 | }  | 
            ||
| 1244 | |||
| 1245 | /**  | 
            ||
| 1246 | * 确保进程已经开启  | 
            ||
| 1247 | * @access private  | 
            ||
| 1248 | * @param string $functionName  | 
            ||
| 1249 | */  | 
            ||
| 1250 | private function requireProcessIsStarted($functionName)  | 
            ||
| 1254 | }  | 
            ||
| 1255 | }  | 
            ||
| 1256 | |||
| 1257 | /**  | 
            ||
| 1258 | * 确保进程已经终止  | 
            ||
| 1259 | * @access private  | 
            ||
| 1260 | * @param string $functionName  | 
            ||
| 1261 | */  | 
            ||
| 1262 | private function requireProcessIsTerminated($functionName)  | 
            ||
| 1263 | { | 
            ||
| 1264 |         if (!$this->isTerminated()) { | 
            ||
| 1265 |             throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); | 
            ||
| 1266 | }  | 
            ||
| 1267 | }  | 
            ||
| 1268 | }  | 
            ||
| 1269 |