ProcessStream   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 264
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 264
ccs 0
cts 121
cp 0
rs 9.6
c 0
b 0
f 0
wmc 32
lcom 1
cbo 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 2
A close() 0 8 2
A detach() 0 6 1
A eof() 0 4 2
A getContents() 0 11 3
A getMetadata() 0 9 2
A getSize() 0 4 1
A isReadable() 0 4 1
A isSeekable() 0 4 1
A isWritable() 0 4 1
A read() 0 11 3
A rewind() 0 7 2
A seek() 0 7 4
A tell() 0 11 3
A write() 0 4 1
A __toString() 0 12 3
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Minotaur
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
 * use this file except in compliance with the License. You may obtain a copy of
8
 * the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 * License for the specific language governing permissions and limitations under
16
 * the License.
17
 *
18
 * @copyright 2015-2017 Appertly
19
 * @license   Apache-2.0
20
 */
21
namespace Minotaur\Io;
22
23
/**
24
 * Implementation of PSR HTTP streams for processes
25
 */
26
class ProcessStream implements \Psr\Http\Message\StreamInterface
27
{
28
    /**
29
     * @var resource
30
     */
31
    private $process;
32
    /**
33
     * @var resource|null
34
     */
35
    private $stream;
36
37
    /**
38
     * @param string $input The data to write to the process' stdin
39
     * @param string $process The process to execute
40
     * @throws \RuntimeException
41
     */
42
    public function __construct(string $input, string $process)
43
    {
44
        $descriptors = [
45
            0 => ["pipe", "r"],  // stdin is a pipe that the child will read from
46
            1 => ["pipe", "w"],  // stdout is a pipe that the child will write to
47
            //2 => ["pipe", "w"],  // stderr is a pipe that the child will write to
48
        ];
49
        $pipes = [];
50
        $this->process = proc_open($process, $descriptors, $pipes);
51
        if (!is_resource($this->process)) {
52
            throw new \RuntimeException("Could not execute process");
53
        }
54
        fwrite($pipes[0], $input);
55
        fclose($pipes[0]);
56
        $this->stream = $pipes[1];
57
    }
58
59
    /**
60
     * Closes the stream and any underlying resources.
61
     */
62
    public function close(): void
63
    {
64
        if ($this->stream !== null) {
65
            $resource = $this->detach();
66
            fclose($resource);
67
        }
68
        proc_close($this->process);
69
    }
70
71
    /**
72
     * Separates any underlying resources from the stream.
73
     *
74
     * After the stream has been detached, the stream is in an unusable state.
75
     *
76
     * @return resource|null Underlying PHP stream, if any
77
     */
78
    public function detach()
79
    {
80
        $resource = $this->stream;
81
        $this->stream = null;
82
        return $resource;
83
    }
84
85
    /**
86
     * Returns true if the stream is at the end of the stream.
87
     *
88
     * @return - Whether the stream pointer is at the end
0 ignored issues
show
Documentation introduced by
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
89
     */
90
    public function eof(): bool
91
    {
92
        return $this->stream === null ? true : feof($this->stream);
93
    }
94
95
    /**
96
     * Returns the remaining contents in a string.
97
     *
98
     * @return - The stream contents
0 ignored issues
show
Documentation introduced by
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
99
     * @throws \RuntimeException if unable to read or an error occurs while reading
100
     */
101
    public function getContents(): string
102
    {
103
        if (!$this->isReadable()) {
104
            throw new \RuntimeException('Cannot read from stream');
105
        }
106
        $result = stream_get_contents($this->stream);
107
        if ($result === false) {
108
            throw new \RuntimeException('Error reading from stream');
109
        }
110
        return $result;
111
    }
112
113
    /**
114
     * Get stream metadata as an associative array or retrieve a specific key.
115
     *
116
     * The keys returned are identical to the keys returned from PHP's
117
     * `stream_get_meta_data` function.
118
     *
119
     * @param string|null $key Specific metadata to retrieve.
120
     * @return array<string,mixed>|mixed|null Returns an associative array if no key is provided. Returns a
121
     *     specific key value if a key is provided and the value is found, or
122
     *     null if the key is not found.
123
     */
124
    public function getMetadata($key = null)
125
    {
126
        if ($key === null) {
127
            return stream_get_meta_data($this->stream);
128
        } else {
129
            $metadata = stream_get_meta_data($this->stream);
130
            return $metadata[$key] ?? null;
131
        }
132
    }
133
134
    /**
135
     * Get the size of the stream if known.
136
     *
137
     * @return int|null Returns the size in bytes if known, or null if unknown.
138
     */
139
    public function getSize(): ?int
140
    {
141
        return null;
142
    }
143
144
    /**
145
     * Returns whether or not the stream is readable.
146
     *
147
     * @return bool Whether or not the stream is readable
148
     */
149
    public function isReadable(): bool
150
    {
151
        return is_resource($this->stream);
152
    }
153
154
    /**
155
     * Returns whether or not the stream is seekable.
156
     *
157
     * @return bool Whether the stream is seekable
158
     */
159
    public function isSeekable(): bool
160
    {
161
        return false;
162
    }
163
164
    /**
165
     * Returns whether or not the stream is writable.
166
     *
167
     * @return bool Whether the stream is writable
168
     */
169
    public function isWritable(): bool
170
    {
171
        return false;
172
    }
173
174
    /**
175
     * Read data from the stream.
176
     *
177
     * @param $length - Read up to $length bytes from the object and return
178
     *     them. Fewer than $length bytes may be returned if underlying stream
179
     *     call returns fewer bytes.
180
     * @return string Returns the data read from the stream, or an empty string
181
     *     if no bytes are available
182
     * @throws \RuntimeException if an error occurs
183
     */
184
    public function read($length): string
185
    {
186
        if (!$this->isReadable()) {
187
            throw new \RuntimeException('Cannot read from stream');
188
        }
189
        $result = fread($this->stream, $length);
190
        if ($result === false) {
191
            throw new \RuntimeException('Error reading stream');
192
        }
193
        return $result;
194
    }
195
196
    /**
197
     * Seek to the beginning of the stream.
198
     *
199
     * This stream is not seekable, this method will raise an exception if the
200
     * stream pointer is not at the beginning.
201
     *
202
     * @throws \RuntimeException on failure.
203
     */
204
    public function rewind(): void
205
    {
206
        if ($this->tell() === 0) {
207
            return;
208
        }
209
        throw new \BadMethodCallException('Stream is not seekable');
210
    }
211
212
    /**
213
     * Seek to a position in the stream.
214
     *
215
     * @param $offset - Stream offset
216
     * @param $whence - Specifies how the cursor position will be calculated
217
     *     based on the seek offset. Valid values are identical to the built-in
218
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
219
     *     offset bytes SEEK_CUR: Set position to current location plus offset
220
     *     SEEK_END: Set position to end-of-stream plus offset.
221
     * @throws \RuntimeException on failure.
222
     */
223
    public function seek($offset, $whence = SEEK_SET): void
224
    {
225
        if ($offset === 0 && ($this->tell() === 0 || $whence !== SEEK_SET)) {
226
            return;
227
        }
228
        throw new \BadMethodCallException('Stream is not seekable');
229
    }
230
231
    /**
232
     * Returns the current position of the file read/write pointer
233
     *
234
     * @return int Position of the file pointer
235
     * @throws \RuntimeException on error.
236
     */
237
    public function tell(): int
238
    {
239
        if ($this->stream === null) {
240
            throw new \RuntimeException('Cannot determine stream pointer position');
241
        }
242
        $result = ftell($this->stream);
243
        if ($result === false) {
244
            throw new \RuntimeException('Cannot determine stream pointer position');
245
        }
246
        return $result;
247
    }
248
249
    /**
250
     * Write data to the stream.
251
     *
252
     * This stream is not writable, this method will raise an exception.
253
     *
254
     * @param $string - The string that is to be written
255
     * @return int Returns the number of bytes written to the stream.
256
     * @throws \RuntimeException on failure
257
     */
258
    public function write($string): int
259
    {
260
        throw new \BadMethodCallException('Stream is not writable');
261
    }
262
263
    /**
264
     * Reads all data from the stream into a string, from the beginning to end.
265
     *
266
     * This method MUST attempt to seek to the beginning of the stream before
267
     * reading data and read the stream until the end is reached.
268
     *
269
     * Warning: This could attempt to load a large amount of data into memory.
270
     *
271
     * This method MUST NOT raise an exception in order to conform with PHP's
272
     * string casting operations.
273
     *
274
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
275
     * @return - string
0 ignored issues
show
Documentation introduced by
The doc-type - could not be parsed: Unknown type name "-" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
276
     */
277
    public function __toString(): string
278
    {
279
        if (!$this->isReadable()) {
280
            return '';
281
        }
282
        try {
283
            $this->rewind();
284
            return $this->getContents();
285
        } catch (\Exception $e) {
286
            return '';
287
        }
288
    }
289
}
290