Stream::getStreamMetadata()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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
        } // On a runtime exception
210
        catch (Throwable) {
211
            // Return a string
212
            return '';
213
        }
214
    }
215
216
    /**
217
     * @inheritDoc
218
     */
219
    #[Override]
220
    public function close(): void
221
    {
222
        // If there is no stream
223
        if ($this->isInvalidStream()) {
224
            // Don't do anything
225
            return;
226
        }
227
228
        // Detach the stream
229
        /** @var resource $resource */
230
        $resource = $this->detach();
231
232
        // Close the stream
233
        $this->closeStream($resource);
234
    }
235
236
    /**
237
     * @inheritDoc
238
     */
239
    #[Override]
240
    public function detach()
241
    {
242
        $resource = $this->resource ?? null;
243
244
        $this->resource = null;
245
246
        return $resource;
247
    }
248
249
    /**
250
     * @inheritDoc
251
     */
252
    #[Override]
253
    public function getSize(): int|null
254
    {
255
        // If the stream isn't set
256
        if ($this->isInvalidStream()) {
257
            // Return without attempting to get the fstat
258
            return null;
259
        }
260
261
        /** @var resource $stream */
262
        $stream = $this->resource;
263
264
        // Get the stream's fstat
265
        $fstat = $this->getStreamStats($stream);
266
267
        if ($fstat === false) {
268
            return null;
269
        }
270
271
        return $fstat['size'];
272
    }
273
274
    /**
275
     * @inheritDoc
276
     */
277
    #[Override]
278
    public function tell(): int
279
    {
280
        $this->verifyStream();
281
282
        /** @var resource $stream */
283
        $stream = $this->resource;
284
285
        // Get the tell for the stream
286
        $result = $this->tellStream($stream);
287
288
        $this->verifyTellResult($result);
289
290
        /** @var int $result */
291
292
        return $result;
293
    }
294
295
    /**
296
     * @inheritDoc
297
     */
298
    #[Override]
299
    public function eof(): bool
300
    {
301
        // If there is no stream
302
        if ($this->isInvalidStream()) {
303
            // Don't do anything
304
            return true;
305
        }
306
307
        /** @var resource $stream */
308
        $stream = $this->resource;
309
310
        return feof($stream);
311
    }
312
313
    /**
314
     * @inheritDoc
315
     */
316
    #[Override]
317
    public function getContents(): string
318
    {
319
        $this->verifyReadable();
320
321
        /** @var resource $stream */
322
        $stream = $this->resource;
323
324
        // Get the stream contents
325
        $result = $this->getStreamContents($stream);
326
327
        $this->verifyReadResult($result);
328
329
        /** @var string $result */
330
331
        return $result;
332
    }
333
334
    /**
335
     * @inheritDoc
336
     */
337
    #[Override]
338
    public function getMetadata(string|null $key = null): mixed
339
    {
340
        // Ensure the stream is valid
341
        if ($this->isInvalidStream()) {
342
            return null;
343
        }
344
345
        /** @var resource $stream */
346
        $stream = $this->resource;
347
348
        // If no key was specified
349
        if ($key === null) {
350
            // Return all the meta data
351
            return $this->getStreamMetadata($stream);
352
        }
353
354
        // Get the meta data
355
        $metadata = $this->getStreamMetadata($stream);
356
357
        return $metadata[$key] ?? null;
358
    }
359
360
    // public function __clone()
361
    // {
362
    //     $this->rewind();
363
    //
364
    //     $contents = $this->getContents();
365
    //
366
    //     $this->setStream($this->stream, $this->mode, $this->modeTranslation);
367
    //
368
    //     if ($this->isWritable()) {
369
    //         $this->write($contents);
370
    //         $this->rewind();
371
    //     }
372
    // }
373
374
    /**
375
     * Seek the stream resource.
376
     *
377
     * @param resource $stream
378
     * @param int      $offset
379
     * @param int      $whence
380
     *
381
     * @return int
382
     */
383
    protected function seekStream($stream, int $offset, int $whence = SEEK_SET): int
384
    {
385
        // Get the results of the seek attempt
386
        return fseek($stream, $offset, $whence);
387
    }
388
389
    /**
390
     * Tell the stream resource.
391
     *
392
     * @param resource $stream
393
     *
394
     * @return int|false
395
     */
396
    protected function tellStream($stream): int|false
397
    {
398
        // Get the tell for the stream
399
        return ftell($stream);
400
    }
401
402
    /**
403
     * Write to a stream.
404
     *
405
     * @param resource $stream The stream
406
     */
407
    protected function writeToStream($stream, string $data): int|false
408
    {
409
        return fwrite($stream, $data);
410
    }
411
412
    /**
413
     * Read from stream.
414
     *
415
     * @param resource    $stream The stream
416
     * @param int<1, max> $length The length
417
     */
418
    protected function readFromStream($stream, int $length): string|false
419
    {
420
        return fread($stream, $length);
421
    }
422
423
    /**
424
     * Get a stream's metadata.
425
     *
426
     * @param resource $stream The stream
427
     *
428
     * @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}
429
     */
430
    protected function getStreamMetadata($stream): array
431
    {
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