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