Stream::getContents()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 16
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Valkyrja Framework package.
7
 *
8
 * (c) Melech Mizrachi <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Valkyrja\Http\Message\Stream;
15
16
use Override;
0 ignored issues
show
Bug introduced by
The type Override 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...
17
use Throwable;
18
use Valkyrja\Http\Message\Stream\Contract\Stream as Contract;
19
use Valkyrja\Http\Message\Stream\Enum\Mode;
20
use Valkyrja\Http\Message\Stream\Enum\ModeTranslation;
21
use Valkyrja\Http\Message\Stream\Enum\PhpWrapper;
22
use Valkyrja\Http\Message\Stream\Exception\InvalidLengthException;
23
use Valkyrja\Http\Message\Stream\Exception\InvalidStreamException;
24
25
use function fclose;
26
use function feof;
27
use function fread;
28
use function fseek;
29
use function fstat;
30
use function ftell;
31
use function fwrite;
32
use function stream_get_contents;
33
use function stream_get_meta_data;
34
35
use const SEEK_SET;
36
37
/**
38
 * Class Stream.
39
 *
40
 * @author Melech Mizrachi
41
 */
42
class Stream implements Contract
43
{
44
    use StreamHelpers;
0 ignored issues
show
Bug introduced by
The trait Valkyrja\Http\Message\Stream\StreamHelpers requires the property $value which is not provided by Valkyrja\Http\Message\Stream\Stream.
Loading history...
45
46
    /**
47
     * StreamImpl constructor.
48
     *
49
     * @param PhpWrapper|string $stream          The stream
50
     * @param Mode              $mode            [optional] The mode
51
     * @param ModeTranslation   $modeTranslation [optional] The mode translation
52
     *
53
     * @throws InvalidStreamException
54
     */
55
    public function __construct(
56
        protected PhpWrapper|string $stream = PhpWrapper::temp,
57
        protected Mode $mode = Mode::WRITE_READ,
58
        protected ModeTranslation $modeTranslation = ModeTranslation::BINARY_SAFE
59
    ) {
60
        $this->setStream($stream, $mode, $modeTranslation);
61
    }
62
63
    /**
64
     * @inheritDoc
65
     */
66
    #[Override]
67
    public function isSeekable(): bool
68
    {
69
        // If there is no stream
70
        if ($this->isInvalidStream()) {
71
            // Don't do anything
72
            return false;
73
        }
74
75
        return (bool) $this->getMetadata('seekable');
76
    }
77
78
    /**
79
     * @inheritDoc
80
     */
81
    #[Override]
82
    public function seek(int $offset, int $whence = SEEK_SET): void
83
    {
84
        $this->verifyStream();
85
        $this->verifySeekable();
86
87
        /** @var resource $stream */
88
        $stream = $this->resource;
89
90
        // Get the results of the seek attempt
91
        $result = $this->seekStream($stream, $offset, $whence);
92
93
        $this->verifySeekResult($result);
94
    }
95
96
    /**
97
     * @inheritDoc
98
     */
99
    #[Override]
100
    public function rewind(): void
101
    {
102
        $this->seek(0);
103
    }
104
105
    /**
106
     * @inheritDoc
107
     */
108
    #[Override]
109
    public function isReadable(): bool
110
    {
111
        // If there is no stream
112
        if ($this->isInvalidStream()) {
113
            // It's not readable
114
            return false;
115
        }
116
117
        // Get the stream's mode
118
        /** @var string|null $mode */
119
        $mode = $this->getMetadata('mode');
120
121
        return $this->isModeReadable((string) $mode);
122
    }
123
124
    /**
125
     * @inheritDoc
126
     */
127
    #[Override]
128
    public function read(int $length): string
129
    {
130
        if ($length < 0) {
131
            throw new InvalidLengthException("Invalid length of $length provided. Length must be greater than 0");
132
        }
133
134
        /** @var int<1, max> $length */
135
        $this->verifyStream();
136
        $this->verifyReadable();
137
138
        /** @var resource $stream */
139
        $stream = $this->resource;
140
141
        // Read the stream
142
        $result = $this->readFromStream($stream, $length);
143
144
        $this->verifyReadResult($result);
145
146
        /** @var string $result */
147
148
        return $result;
149
    }
150
151
    /**
152
     * @inheritDoc
153
     */
154
    #[Override]
155
    public function isWritable(): bool
156
    {
157
        // If there is no stream
158
        if ($this->isInvalidStream()) {
159
            // The stream is definitely not writable
160
            return false;
161
        }
162
163
        // Get the stream's mode
164
        /** @var string|null $mode */
165
        $mode = $this->getMetadata('mode');
166
167
        return $this->isModeWriteable((string) $mode);
168
    }
169
170
    /**
171
     * @inheritDoc
172
     */
173
    #[Override]
174
    public function write(string $string): int
175
    {
176
        $this->verifyStream();
177
        $this->verifyWritable();
178
179
        /** @var resource $stream */
180
        $stream = $this->resource;
181
182
        // Attempt to write to the stream
183
        $result = $this->writeToStream($stream, $string);
184
185
        $this->verifyWriteResult($result);
186
187
        /** @var int $result */
188
189
        return $result;
190
    }
191
192
    /**
193
     * @inheritDoc
194
     */
195
    public function __toString(): string
196
    {
197
        // If the stream is not readable
198
        if (! $this->isReadable()) {
199
            // Return an empty string
200
            return '';
201
        }
202
203
        try {
204
            // Rewind the stream to the start
205
            $this->rewind();
206
207
            // Get the stream's contents
208
            return $this->getContents();
209
        } catch (Throwable) {
210
            // Return a string
211
            return '';
212
        }
213
    }
214
215
    /**
216
     * @inheritDoc
217
     */
218
    #[Override]
219
    public function close(): void
220
    {
221
        // If there is no stream
222
        if ($this->isInvalidStream()) {
223
            // Don't do anything
224
            return;
225
        }
226
227
        // Detach the stream
228
        /** @var resource $resource */
229
        $resource = $this->detach();
230
231
        // Close the stream
232
        $this->closeStream($resource);
233
    }
234
235
    /**
236
     * @inheritDoc
237
     */
238
    #[Override]
239
    public function detach()
240
    {
241
        $resource = $this->resource ?? null;
242
243
        $this->resource = null;
244
245
        return $resource;
246
    }
247
248
    /**
249
     * @inheritDoc
250
     */
251
    #[Override]
252
    public function getSize(): int|null
253
    {
254
        // If the stream isn't set
255
        if ($this->isInvalidStream()) {
256
            // Return without attempting to get the fstat
257
            return null;
258
        }
259
260
        /** @var resource $stream */
261
        $stream = $this->resource;
262
263
        // Get the stream's fstat
264
        $fstat = $this->getStreamStats($stream);
265
266
        if ($fstat === false) {
267
            return null;
268
        }
269
270
        return $fstat['size'];
271
    }
272
273
    /**
274
     * @inheritDoc
275
     */
276
    #[Override]
277
    public function tell(): int
278
    {
279
        $this->verifyStream();
280
281
        /** @var resource $stream */
282
        $stream = $this->resource;
283
284
        // Get the tell for the stream
285
        $result = $this->tellStream($stream);
286
287
        $this->verifyTellResult($result);
288
289
        /** @var int $result */
290
291
        return $result;
292
    }
293
294
    /**
295
     * @inheritDoc
296
     */
297
    #[Override]
298
    public function eof(): bool
299
    {
300
        // If there is no stream
301
        if ($this->isInvalidStream()) {
302
            // Don't do anything
303
            return true;
304
        }
305
306
        /** @var resource $stream */
307
        $stream = $this->resource;
308
309
        return feof($stream);
310
    }
311
312
    /**
313
     * @inheritDoc
314
     */
315
    #[Override]
316
    public function getContents(): string
317
    {
318
        $this->verifyReadable();
319
320
        /** @var resource $stream */
321
        $stream = $this->resource;
322
323
        // Get the stream contents
324
        $result = $this->getStreamContents($stream);
325
326
        $this->verifyReadResult($result);
327
328
        /** @var string $result */
329
330
        return $result;
331
    }
332
333
    /**
334
     * @inheritDoc
335
     */
336
    #[Override]
337
    public function getMetadata(string|null $key = null): mixed
338
    {
339
        // Ensure the stream is valid
340
        if ($this->isInvalidStream()) {
341
            return null;
342
        }
343
344
        /** @var resource $stream */
345
        $stream = $this->resource;
346
347
        // If no key was specified
348
        if ($key === null) {
349
            // Return all the meta data
350
            return $this->getStreamMetadata($stream);
351
        }
352
353
        // Get the meta data
354
        $metadata = $this->getStreamMetadata($stream);
355
356
        return $metadata[$key] ?? null;
357
    }
358
359
    // public function __clone()
360
    // {
361
    //     $this->rewind();
362
    //
363
    //     $contents = $this->getContents();
364
    //
365
    //     $this->setStream($this->stream, $this->mode, $this->modeTranslation);
366
    //
367
    //     if ($this->isWritable()) {
368
    //         $this->write($contents);
369
    //         $this->rewind();
370
    //     }
371
    // }
372
373
    /**
374
     * Seek the stream resource.
375
     *
376
     * @param resource $stream
377
     * @param int      $offset
378
     * @param int      $whence
379
     *
380
     * @return int
381
     */
382
    protected function seekStream($stream, int $offset, int $whence = SEEK_SET): int
383
    {
384
        // Get the results of the seek attempt
385
        return fseek($stream, $offset, $whence);
386
    }
387
388
    /**
389
     * Tell the stream resource.
390
     *
391
     * @param resource $stream
392
     *
393
     * @return int|false
394
     */
395
    protected function tellStream($stream): int|false
396
    {
397
        // Get the tell for the stream
398
        return ftell($stream);
399
    }
400
401
    /**
402
     * Write to a stream.
403
     *
404
     * @param resource $stream The stream
405
     */
406
    protected function writeToStream($stream, string $data): int|false
407
    {
408
        return fwrite($stream, $data);
409
    }
410
411
    /**
412
     * Read from stream.
413
     *
414
     * @param resource    $stream The stream
415
     * @param int<1, max> $length The length
416
     */
417
    protected function readFromStream($stream, int $length): string|false
418
    {
419
        return fread($stream, $length);
420
    }
421
422
    /**
423
     * Get a stream's metadata.
424
     *
425
     * @param resource $stream The stream
426
     *
427
     * @return array{blocked: bool, crypto?: array{cipher_bits: int, cipher_name: string, cipher_version: string, protocol: string}, eof: bool, mediatype?: string, mode: string, seekable: bool, stream_type: string, timed_out: bool, unread_bytes: int, uri: string, wrapper_data: mixed, wrapper_type: string}
428
     */
429
    protected function getStreamMetadata($stream): array
430
    {
431
        // @phpstan-ignore-next-line
432
        return stream_get_meta_data($stream);
433
    }
434
435
    /**
436
     * Get a stream's contents.
437
     *
438
     * @param resource $stream The stream
439
     *
440
     * @return string|false
441
     */
442
    protected function getStreamContents($stream): string|false
443
    {
444
        return stream_get_contents($stream);
445
    }
446
447
    /**
448
     * Close a stream.
449
     *
450
     * @param resource $stream The stream
451
     *
452
     * @return bool
453
     */
454
    protected function closeStream($stream): bool
455
    {
456
        return fclose($stream);
457
    }
458
459
    /**
460
     * Get a stream's stats.
461
     *
462
     * @param resource $stream The stream
463
     *
464
     * @return array{0: int, 10: int, 11: int, 12: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int, 8: int, 9: int, atime: int, blksize: int, blocks: int, ctime: int, dev: int, gid: int, ino: int, mode: int, mtime: int, nlink: int, rdev: int, size: int, uid: int}|false
465
     */
466
    protected function getStreamStats($stream): array|false
467
    {
468
        return fstat($stream);
469
    }
470
}
471