Stream::__toString()   A
last analyzed

Complexity

Conditions 4
Paths 7

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4.25

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 7
nop 0
dl 0
loc 14
ccs 6
cts 8
cp 0.75
crap 4.25
rs 10
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-message
10
 */
11
12
namespace Sunrise\Http\Message;
13
14
use App\Exception\ExceptionFactory;
0 ignored issues
show
Bug introduced by
The type App\Exception\ExceptionFactory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
15
use OverflowException;
16
use Psr\Http\Message\StreamInterface;
17
use Sunrise\Http\Message\Exception\InvalidArgumentException;
18
use Sunrise\Http\Message\Exception\RuntimeException;
19
use Throwable;
20
21
use function fclose;
22
use function feof;
23
use function fread;
24
use function fseek;
25
use function fstat;
26
use function ftell;
27
use function fwrite;
28
use function is_resource;
29
use function stream_get_contents;
30
use function stream_get_meta_data;
31
use function strpbrk;
32
33
use const SEEK_SET;
34
35
class Stream implements StreamInterface
36
{
37
    /**
38
     * @var resource|null
39
     */
40
    private $resource;
41
42
    private bool $autoClose;
43
44
    /**
45
     * @param mixed $resource
46
     *
47
     * @throws InvalidArgumentException
48
     */
49 166
    public function __construct($resource, bool $autoClose = true)
50
    {
51 166
        if (!is_resource($resource)) {
52 4
            throw new InvalidArgumentException('Unexpected stream resource');
53
        }
54
55 165
        $this->resource = $resource;
56 165
        $this->autoClose = $autoClose;
57
    }
58
59
    /**
60
     * @param mixed $resource
61
     *
62
     * @throws InvalidArgumentException
63
     */
64 3
    public static function create($resource): StreamInterface
65
    {
66 3
        if ($resource instanceof StreamInterface) {
67 1
            return $resource;
68
        }
69
70 2
        return new self($resource);
71
    }
72
73 117
    public function __destruct()
74
    {
75 117
        if ($this->autoClose) {
76 106
            $this->close();
77
        }
78
    }
79
80
    /**
81
     * @inheritDoc
82
     */
83 165
    public function detach()
84
    {
85 165
        $resource = $this->resource;
86 165
        $this->resource = null;
87
88 165
        return $resource;
89
    }
90
91
    /**
92
     * @inheritDoc
93
     */
94 165
    public function close(): void
95
    {
96 165
        $resource = $this->detach();
97 165
        if (!is_resource($resource)) {
98 42
            return;
99
        }
100
101 147
        fclose($resource);
102
    }
103
104
    /**
105
     * @inheritDoc
106
     */
107 6
    public function eof(): bool
108
    {
109 6
        if (!is_resource($this->resource)) {
110 2
            return true;
111
        }
112
113 4
        return feof($this->resource);
114
    }
115
116
    /**
117
     * @inheritDoc
118
     */
119 8
    public function tell(): int
120
    {
121 8
        if (!is_resource($this->resource)) {
122 2
            throw new RuntimeException('Stream has no resource');
123
        }
124
125 6
        $result = ftell($this->resource);
126 6
        if ($result === false) {
127 1
            throw new RuntimeException('Unable to get the stream pointer position');
128
        }
129
130 5
        return $result;
131
    }
132
133
    /**
134
     * @inheritDoc
135
     */
136 46
    public function isSeekable(): bool
137
    {
138 46
        if (!is_resource($this->resource)) {
139 2
            return false;
140
        }
141
142 44
        $metadata = stream_get_meta_data($this->resource);
143
144 44
        return $metadata['seekable'];
145
    }
146
147
    /**
148
     * @inheritDoc
149
     */
150 34
    public function rewind(): void
151
    {
152 34
        $this->seek(0);
153
    }
154
155
    /**
156
     * @inheritDoc
157
     */
158 41
    public function seek($offset, $whence = SEEK_SET): void
159
    {
160 41
        if (!is_resource($this->resource)) {
161 4
            throw new RuntimeException('Stream has no resource');
162
        }
163
164 37
        if (!$this->isSeekable()) {
165 3
            throw new RuntimeException('Stream is not seekable');
166
        }
167
168 34
        $result = fseek($this->resource, $offset, $whence);
169 34
        if ($result !== 0) {
170
            throw new RuntimeException('Unable to move the stream pointer position');
171
        }
172
    }
173
174
    /**
175
     * @inheritDoc
176
     */
177 41
    public function isWritable(): bool
178
    {
179 41
        if (!is_resource($this->resource)) {
180 2
            return false;
181
        }
182
183 39
        $metadata = stream_get_meta_data($this->resource);
184
185 39
        return strpbrk($metadata['mode'], '+acwx') !== false;
186
    }
187
188
    /**
189
     * @inheritDoc
190
     */
191 31
    public function write($string): int
192
    {
193 31
        if (!is_resource($this->resource)) {
194 2
            throw new RuntimeException('Stream has no resource');
195
        }
196
197 29
        if (!$this->isWritable()) {
198 1
            throw new RuntimeException('Stream is not writable');
199
        }
200
201 28
        $result = fwrite($this->resource, $string);
202 28
        if ($result === false) {
203
            throw new RuntimeException('Unable to write to the stream');
204
        }
205
206 28
        return $result;
207
    }
208
209
    /**
210
     * @since 3.7.0
211
     *
212
     * @throws OverflowException
213
     */
214 2
    public function writeStream(StreamInterface $stream, int $limit = -1): int
215
    {
216 2
        $written = 0;
217 2
        while (!$stream->eof()) {
218 2
            $written += $this->write($stream->read(4096));
219 2
            if ($limit > 0 && $written > $limit) {
220 1
                throw new OverflowException('Maximum stream size exceeded.');
221
            }
222
        }
223
224 1
        return $written;
225
    }
226
227
    /**
228
     * @inheritDoc
229
     */
230 42
    public function isReadable(): bool
231
    {
232 42
        if (!is_resource($this->resource)) {
233 4
            return false;
234
        }
235
236 38
        $metadata = stream_get_meta_data($this->resource);
237
238 38
        return strpbrk($metadata['mode'], '+r') !== false;
239
    }
240
241
    /**
242
     * @inheritDoc
243
     *
244
     * @psalm-param int $length
245
     * @phpstan-param int<1, max> $length
246
     */
247 10
    public function read($length): string
248
    {
249 10
        if (!is_resource($this->resource)) {
250 2
            throw new RuntimeException('Stream has no resource');
251
        }
252
253 8
        if (!$this->isReadable()) {
254 1
            throw new RuntimeException('Stream is not readable');
255
        }
256
257 7
        $result = fread($this->resource, $length);
258 7
        if ($result === false) {
259
            throw new RuntimeException('Unable to read from the stream');
260
        }
261
262 7
        return $result;
263
    }
264
265
    /**
266
     * @inheritDoc
267
     */
268 22
    public function getContents(): string
269
    {
270 22
        if (!is_resource($this->resource)) {
271 3
            throw new RuntimeException('Stream has no resource');
272
        }
273
274 19
        if (!$this->isReadable()) {
275 1
            throw new RuntimeException('Stream is not readable');
276
        }
277
278 18
        $result = stream_get_contents($this->resource);
279 18
        if ($result === false) {
280
            throw new RuntimeException('Unable to read the remainder of the stream');
281
        }
282
283 18
        return $result;
284
    }
285
286
    /**
287
     * @inheritDoc
288
     */
289 26
    public function getMetadata($key = null)
290
    {
291 26
        if (!is_resource($this->resource)) {
292 2
            return null;
293
        }
294
295 24
        $metadata = stream_get_meta_data($this->resource);
296 24
        if ($key === null) {
297 1
            return $metadata;
298
        }
299
300 23
        return $metadata[$key] ?? null;
301
    }
302
303
    /**
304
     * @inheritDoc
305
     */
306 7
    public function getSize(): ?int
307
    {
308 7
        if (!is_resource($this->resource)) {
309 2
            return null;
310
        }
311
312
        /** @var array{size: int}|false */
313 5
        $stats = fstat($this->resource);
314 5
        if ($stats === false) {
315
            return null;
316
        }
317
318 5
        return $stats['size'];
319
    }
320
321
    /**
322
     * @inheritDoc
323
     */
324 18
    public function __toString(): string
325
    {
326 18
        if (!$this->isReadable()) {
327 3
            return '';
328
        }
329
330
        try {
331 15
            if ($this->isSeekable()) {
332 15
                $this->rewind();
333
            }
334
335 15
            return $this->getContents();
336
        } catch (Throwable $e) {
337
            return '';
338
        }
339
    }
340
}
341