Passed
Push — master ( 157485...b7615a )
by Nikolaos
09:23
created

Stream   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 365
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 46
eloc 101
dl 0
loc 365
ccs 0
cts 166
cp 0
rs 8.72
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A write() 0 14 2
A isSeekable() 0 3 1
A eof() 0 7 2
A __toString() 0 15 4
A checkHandle() 0 4 2
A getSize() 0 11 3
A read() 0 15 2
A checkSeekable() 0 4 2
A getMetadata() 0 13 3
A __destruct() 0 3 1
A rewind() 0 3 1
A checkReadable() 0 4 2
A isReadable() 0 5 1
A getContents() 0 14 2
A tell() 0 13 2
A close() 0 6 3
A detach() 0 6 1
A setStream() 0 30 6
A checkWritable() 0 4 2
A __construct() 0 3 1
A seek() 0 12 2
A isWritable() 0 5 1

How to fix   Complexity   

Complex Class

Complex classes like Stream often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Stream, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 *
11
 * Implementation of this file has been influenced by Zend Diactoros
12
 *
13
 * @link    https://github.com/zendframework/zend-diactoros
14
 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phalcon\Http\Message;
20
21
use Exception;
22
use Phalcon\Helper\Arr;
23
use Psr\Http\Message\StreamInterface;
24
use RuntimeException;
25
26
use function fclose;
27
use function feof;
28
use function fopen;
29
use function fread;
30
use function fseek;
31
use function fstat;
32
use function ftell;
33
use function fwrite;
34
use function get_resource_type;
35
use function is_resource;
36
use function is_string;
37
use function restore_error_handler;
38
use function set_error_handler;
39
use function stream_get_contents;
40
use function stream_get_meta_data;
41
use function strpbrk;
42
43
use const E_WARNING;
44
45
/**
46
 * PSR-7 Stream
47
 *
48
 * @property resource|null   $handle
49
 * @property resource|string $stream
50
 */
51
class Stream implements StreamInterface
52
{
53
    /**
54
     * @var resource | null
55
     */
56
    protected $handle = null;
57
58
    /**
59
     * @var resource | string
60
     */
61
    private $stream;
62
63
    /**
64
     * Stream constructor.
65
     *
66
     * @param mixed  $stream
67
     * @param string $mode
68
     */
69
    public function __construct($stream, string $mode = "rb")
70
    {
71
        $this->setStream($stream, $mode);
72
    }
73
74
    /**
75
     * Closes the stream when the destructed.
76
     */
77
    public function __destruct()
78
    {
79
        $this->close();
80
    }
81
82
    /**
83
     * Reads all data from the stream into a string, from the beginning to end.
84
     *
85
     * This method MUST attempt to seek to the beginning of the stream before
86
     * reading data and read the stream until the end is reached.
87
     *
88
     * Warning: This could attempt to load a large amount of data into memory.
89
     *
90
     * This method MUST NOT raise an exception in order to conform with PHP"s
91
     * string casting operations.
92
     *
93
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
94
     */
95
    public function __toString(): string
96
    {
97
        try {
98
            if ($this->isReadable()) {
99
                if ($this->isSeekable()) {
100
                    $this->rewind();
101
                }
102
103
                return $this->getContents();
104
            }
105
        } catch (Exception $e) {
106
            unset($e);
107
        }
108
109
        return "";
110
    }
111
112
    /**
113
     * Closes the stream and any underlying resources.
114
     */
115
    public function close(): void
116
    {
117
        if (null !== $this->handle) {
118
            $handle = $this->detach();
119
            if (null !== $handle) {
0 ignored issues
show
introduced by
The condition null !== $handle is always true.
Loading history...
120
                fclose($handle);
121
            }
122
        }
123
    }
124
125
    /**
126
     * Separates any underlying resources from the stream.
127
     *
128
     * After the stream has been detached, the stream is in an unusable state.
129
     *
130
     * @return resource | null
131
     */
132
    public function detach()
133
    {
134
        $handle       = $this->handle;
135
        $this->handle = null;
136
137
        return $handle;
138
    }
139
140
    /**
141
     * Returns true if the stream is at the end of the stream.
142
     */
143
    public function eof(): bool
144
    {
145
        if ($this->handle) {
146
            return feof($this->handle);
147
        }
148
149
        return true;
150
    }
151
152
    /**
153
     * Returns the remaining contents in a string
154
     */
155
    public function getContents(): string
156
    {
157
        $this->checkHandle();
158
        $this->checkReadable();
159
160
        $data = stream_get_contents($this->handle);
161
162
        if (false === $data) {
163
            throw new RuntimeException(
164
                "Could not read from the file/stream"
165
            );
166
        }
167
168
        return $data;
169
    }
170
171
    /**
172
     * Get stream metadata as an associative array or retrieve a specific key.
173
     *
174
     * The keys returned are identical to the keys returned from PHP's
175
     * stream_get_meta_data() function.
176
     *
177
     * @param mixed|null $key
178
     *
179
     * @return array|mixed|null
180
     */
181
    public function getMetadata($key = null)
182
    {
183
        if (null === $this->handle) {
184
            return null;
185
        }
186
187
        $metadata = stream_get_meta_data($this->handle);
188
189
        if (null === $key) {
190
            return $metadata;
191
        }
192
193
        return Arr::get($metadata, $key, []);
194
    }
195
196
    /**
197
     * Get the size of the stream if known.
198
     */
199
    public function getSize(): ?int
200
    {
201
        if (null !== $this->handle) {
202
            $stats = fstat($this->handle);
203
204
            if (false !== $stats) {
205
                return Arr::get($stats, "size", null);
206
            }
207
        }
208
209
        return null;
210
    }
211
212
    /**
213
     * Returns whether or not the stream is readable.
214
     */
215
    public function isReadable(): bool
216
    {
217
        $mode = (string) $this->getMetadata("mode");
218
219
        return false !== strpbrk($mode, "r+");
220
    }
221
222
    /**
223
     * Returns whether or not the stream is seekable.
224
     */
225
    public function isSeekable(): bool
226
    {
227
        return (bool) $this->getMetadata("seekable");
228
    }
229
230
    /**
231
     * Returns whether or not the stream is writable.
232
     */
233
    public function isWritable(): bool
234
    {
235
        $mode = (string) $this->getMetadata("mode");
236
237
        return false !== strpbrk($mode, "xwca+");
238
    }
239
240
    /**
241
     * Read data from the stream.
242
     *
243
     * @param int $length
244
     *
245
     * @return string
246
     */
247
    public function read($length): string
248
    {
249
        $this->checkHandle();
250
        $this->checkReadable();
251
252
        $length = (int) $length;
253
        $data   = fread($this->handle, $length);
254
255
        if (false === $data) {
256
            throw new RuntimeException(
257
                "Could not read from the file/stream"
258
            );
259
        }
260
261
        return $data;
262
    }
263
264
    /**
265
     * Seek to the beginning of the stream.
266
     *
267
     * If the stream is not seekable, this method will raise an exception;
268
     * otherwise, it will perform a seek(0).
269
     */
270
    public function rewind(): void
271
    {
272
        $this->seek(0);
273
    }
274
275
    /**
276
     * Seek to a position in the stream.
277
     *
278
     * @param int $offset
279
     * @param int $whence
280
     */
281
    public function seek($offset, $whence = 0): void
282
    {
283
        $this->checkHandle();
284
        $this->checkSeekable();
285
286
        $offset = (int) $offset;
287
        $whence = (int) $whence;
288
        $seeker = fseek($this->handle, $offset, $whence);
289
290
        if (0 !== $seeker) {
291
            throw new RuntimeException(
292
                "Could not seek on the file pointer"
293
            );
294
        }
295
    }
296
297
    /**
298
     * Sets the stream - existing instance
299
     *
300
     * @param mixed  $stream
301
     * @param string $mode
302
     */
303
    public function setStream($stream, string $mode = "rb"): void
304
    {
305
        $warning = false;
306
        $handle  = $stream;
307
        if (is_string($stream)) {
308
            set_error_handler(
309
                function ($error) use (&$warning) {
310
                    if ($error === E_WARNING) {
311
                        $warning = true;
312
                    }
313
                }
314
            );
315
316
            $handle = fopen($stream, $mode);
317
318
            restore_error_handler();
319
        }
320
        if (
321
            $warning ||
322
            !is_resource($handle) ||
323
            "stream" !== get_resource_type($handle)
324
        ) {
325
            throw new RuntimeException(
326
                "The stream provided is not valid " .
327
                "(string/resource) or could not be opened."
328
            );
329
        }
330
331
        $this->handle = $handle;
332
        $this->stream = $stream;
333
    }
334
335
    /**
336
     * Returns the current position of the file read/write pointer
337
     *
338
     * @return int
339
     * @throws Exception
340
     */
341
    public function tell(): int
342
    {
343
        $this->checkHandle();
344
345
        $position = ftell($this->handle);
346
347
        if (false === $position) {
348
            throw new RuntimeException(
349
                "Could not retrieve the pointer position"
350
            );
351
        }
352
353
        return $position;
354
    }
355
356
    /**
357
     * Write data to the stream.
358
     *
359
     * @param string $data
360
     *
361
     * @return int
362
     */
363
    public function write($data): int
364
    {
365
        $this->checkHandle();
366
        $this->checkWritable();
367
368
        $bytes = fwrite($this->handle, $data);
369
370
        if (false === $bytes) {
371
            throw new RuntimeException(
372
                "Could not write to the file/stream"
373
            );
374
        }
375
376
        return $bytes;
377
    }
378
379
    /**
380
     * Checks if a handle is available and throws an exception otherwise
381
     */
382
    private function checkHandle(): void
383
    {
384
        if (null === $this->handle) {
385
            throw new RuntimeException("A valid resource is required.");
386
        }
387
    }
388
389
    /**
390
     * Checks if a handle is readable and throws an exception otherwise
391
     */
392
    private function checkReadable(): void
393
    {
394
        if (true !== $this->isReadable()) {
395
            throw new RuntimeException("The resource is not readable.");
396
        }
397
    }
398
399
    /**
400
     * Checks if a handle is seekable and throws an exception otherwise
401
     */
402
    private function checkSeekable(): void
403
    {
404
        if (true !== $this->isSeekable()) {
405
            throw new RuntimeException("The resource is not seekable.");
406
        }
407
    }
408
409
    /**
410
     * Checks if a handle is writeable and throws an exception otherwise
411
     */
412
    private function checkWritable(): void
413
    {
414
        if (true !== $this->isWritable()) {
415
            throw new RuntimeException("The resource is not writable.");
416
        }
417
    }
418
}
419