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