Completed
Push — master ( 56c359...717fd1 )
by Sebastian
04:15 queued 01:56
created

Stream   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 70
dl 0
loc 358
ccs 0
cts 139
cp 0
rs 8.72
c 0
b 0
f 0
wmc 46

18 Methods

Rating   Name   Duplication   Size   Complexity  
A can() 0 11 3
A detach() 0 10 2
A write() 0 15 3
A getMetadata() 0 10 3
A eof() 0 3 2
A checkFileMode() 0 12 2
A read() 0 15 3
A seek() 0 11 3
A getSize() 0 3 2
A rewind() 0 4 3
A close() 0 15 3
A isWritable() 0 3 2
A __toString() 0 9 2
A isSeekable() 0 3 2
A isReadable() 0 3 2
A tell() 0 11 3
A getContents() 0 11 3
A __construct() 0 12 3

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
 * Linna Psr7.
5
 *
6
 * @author Sebastian Rapetti <[email protected]>
7
 * @copyright (c) 2018, Sebastian Rapetti
8
 * @license http://opensource.org/licenses/MIT MIT License
9
 */
10
declare(strict_types=1);
11
12
namespace Linna\Http\Message;
13
14
use InvalidArgumentException;
15
use Psr\Http\Message\StreamInterface;
16
use RuntimeException;
17
18
/**
19
 * Psr7 Stream implementation.
20
 */
21
class Stream implements StreamInterface
22
{
23
    /**
24
     * @var mixed The streem resource.
25
     */
26
    protected $resource;
27
28
    /**
29
     * @var bool Is stream a proces file pointer?
30
     */
31
    protected $isPipe;
32
33
    /**
34
     * Constructor.
35
     *
36
     * @param mixed $resource
37
     */
38
    public function __construct($resource)
39
    {
40
        if (!is_resource($resource)) {
41
            throw new InvalidArgumentException(__CLASS__.': Invalid resource provided');
42
        }
43
44
        if ('stream' !== get_resource_type($resource)) {
45
            throw new InvalidArgumentException(__CLASS__.': Resource provided is not a stream');
46
        }
47
48
        $this->resource = $resource;
49
        $this->isPipe = $this->checkFileMode($resource);
50
    }
51
52
    /**
53
     * Check if file is a pipe.
54
     * http://man7.org/linux/man-pages/man7/inode.7.html.
55
     *
56
     * @param mixed $resource
57
     *
58
     * @return bool
59
     */
60
    protected function checkFileMode($resource): bool
61
    {
62
        //file modes
63
        //check if resource is a process file pointer.
64
        //0140000   socket
65
        //0120000   symbolic link
66
        //0100000   regular file
67
        //0060000   block device
68
        //0040000   directory
69
        //0020000   character device
70
        //0010000   FIFO
71
        return ((fstat($resource)['mode'] & 0010000) !== 0) ? true : false;
72
    }
73
74
    /**
75
     * Reads all data from the stream into a string, from the beginning to end.
76
     *
77
     * This method MUST attempt to seek to the beginning of the stream before
78
     * reading data and read the stream until the end is reached.
79
     *
80
     * Warning: This could attempt to load a large amount of data into memory.
81
     *
82
     * This method MUST NOT raise an exception in order to conform with PHP's
83
     * string casting operations.
84
     *
85
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
86
     *
87
     * @return string
88
     */
89
    public function __toString(): string
90
    {
91
        try {
92
            $this->rewind();
93
94
            return $this->getContents();
95
        } catch (RuntimeException $e) {
96
            unset($e);
97
            return '';
98
        }
99
    }
100
101
    /**
102
     * Closes the stream and any underlying resources.
103
     *
104
     * @return void
105
     */
106
    public function close()
107
    {
108
        if (!$this->resource) {
109
            return;
110
        }
111
112
        if ($this->isPipe) {
113
            pclose($this->resource);
114
            $this->resource = null;
115
116
            return;
117
        }
118
119
        fclose($this->resource);
120
        $this->resource = null;
121
    }
122
123
    /**
124
     * Separates any underlying resources from the stream.
125
     *
126
     * After the stream has been detached, the stream is in an unusable state.
127
     *
128
     * @return resource|null Underlying PHP stream, if any.
129
     */
130
    public function detach()
131
    {
132
        if (!$this->resource) {
133
            return;
134
        }
135
136
        $tmpResource = $this->resource;
137
        $this->resource = false;
138
139
        return $tmpResource;
140
    }
141
142
    /**
143
     * Get the size of the stream if known.
144
     *
145
     * @return int Returns the size in bytes if known, or zero if unknown.
146
     */
147
    public function getSize(): int
148
    {
149
        return (!$this->resource) ? 0 : fstat($this->resource)['size'];
150
    }
151
152
    /**
153
     * Returns the current position of the file read/write pointer
154
     *
155
     * @return int Position of the file pointer.
156
     *
157
     * @throws RuntimeException on error.
158
     */
159
    public function tell(): int
160
    {
161
        if (!$this->resource) {
162
            throw new RuntimeException(__CLASS__.': No resource available; cannot tell position');
163
        }
164
165
        if (($position = ftell($this->resource)) === false) {
166
            throw new RuntimeException(__CLASS__.': Error occurred during tell operation');
167
        }
168
169
        return $position;
170
    }
171
172
    /**
173
     * Returns true if the stream is at the end of the stream.
174
     *
175
     * @return bool
176
     */
177
    public function eof(): bool
178
    {
179
        return (!$this->resource) ? feof($this->resource) : true;
180
    }
181
182
    /**
183
     * Returns whether or not the stream is seekable.
184
     *
185
     * @return bool
186
     */
187
    public function isSeekable(): bool
188
    {
189
        return (!$this->resource) ? false : stream_get_meta_data($this->resource)['seekable'];
190
    }
191
192
    /**
193
     * Seek to a position in the stream.
194
     *
195
     * @link http://www.php.net/manual/en/function.fseek.php
196
     *
197
     * @param int $offset Stream offset
198
     * @param int $whence Specifies how the cursor position will be calculated based on the seek offset.
199
     *                    Valid values are identical to the built-in PHP $whence values for `fseek()`.
200
     *                    SEEK_SET: Set position equal to offset bytes.
201
     *                    SEEK_CUR: Set position to current location plus offset
202
     *                    SEEK_END: Set position to end-of-stream plus offset.
203
     *
204
     * @throws RuntimeException on failure.
205
     */
206
    public function seek(int $offset, int $whence = SEEK_SET)
207
    {
208
        if (!$this->isSeekable()) {
209
            throw new RuntimeException(__CLASS__.': Can not seek the stream');
210
        }
211
212
        if (fseek($this->resource, $offset, $whence) !== 0) {
213
            throw new RuntimeException(__CLASS__.': Error seeking within stream');
214
        }
215
216
        return true;
217
    }
218
219
    /**
220
     * Seek to the beginning of the stream.
221
     *
222
     * If the stream is not seekable, this method will raise an exception;
223
     * otherwise, it will perform a seek(0).
224
     *
225
     * @see seek()
226
     *
227
     * @link http://www.php.net/manual/en/function.fseek.php
228
     *
229
     * @throws RuntimeException on failure.
230
     */
231
    public function rewind()
232
    {
233
        if (!$this->isSeekable() || rewind($this->resource) === false) {
234
            throw new RuntimeException(__CLASS__.': Can not rewind the stream');
235
        }
236
    }
237
238
    /**
239
     * Check modes.
240
     *
241
     * @param array $modes
242
     *
243
     * @return bool
244
     */
245
    protected function can(array $modes): bool
246
    {
247
        $metaMode = stream_get_meta_data($this->resource)['mode'];
248
249
        foreach ($modes as $mode) {
250
            if (strpos($metaMode, $mode) !== false) {
251
                return true;
252
            }
253
        }
254
255
        return false;
256
    }
257
258
    /**
259
     * Returns whether or not the stream is writable.
260
     *
261
     * @return bool
262
     */
263
    public function isWritable(): bool
264
    {
265
        return (!$this->resource) ? false : $this->can(['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+']);
266
    }
267
268
    /**
269
     * Write data to the stream.
270
     *
271
     * @param string $string The string that is to be written.
272
     *
273
     * @return int Returns the number of bytes written to the stream.
274
     *
275
     * @throws RuntimeException on failure.
276
     */
277
    public function write(string $string): int
278
    {
279
        //if (!$this->resource) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
280
        //    throw new RuntimeException(__CLASS__.': Resource not available; '.__METHOD__);
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
281
        //}
282
283
        if (!$this->isWritable()) {
284
            throw new RuntimeException(__CLASS__.': Stream is not writable; '.__METHOD__);
285
        }
286
287
        if (($bytes = fwrite($this->resource, $string)) === false) {
288
            throw new RuntimeException(__CLASS__.': Error writing stream; '.__METHOD__);
289
        }
290
291
        return $bytes;
292
    }
293
294
    /**
295
     * Returns whether or not the stream is readable.
296
     *
297
     * @return bool
298
     */
299
    public function isReadable(): bool
300
    {
301
        return (!$this->resource) ? false : $this->can(['r', 'r+', 'w+', 'a+', 'x+', 'c+']);
302
    }
303
304
    /**
305
     * Read data from the stream.
306
     *
307
     * @param int $length Read up to $length bytes from the object and return them.
308
     *                    Fewer than $length bytes may be returned if underlying stream
309
     *                    call returns fewer bytes.
310
     *
311
     * @return string Returns the data read from the stream, or an empty string
312
     *                if no bytes are available.
313
     *
314
     * @throws RuntimeException if an error occurs.
315
     */
316
    public function read(int $length): string
317
    {
318
        //if (!$this->resource) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
319
        //    throw new RuntimeException(__CLASS__.': Resource not available; '.__METHOD__);
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
320
        //}
321
322
        if (!$this->isReadable()) {
323
            throw new RuntimeException(__CLASS__.': Stream is not readable; '.__METHOD__);
324
        }
325
326
        if (($data = fread($this->resource, $length)) === false) {
327
            throw new RuntimeException(__CLASS__.': Error reading stream; '.__METHOD__);
328
        }
329
330
        return $data;
331
    }
332
333
    /**
334
     * Returns the remaining contents in a string.
335
     *
336
     * @return string
337
     *
338
     * @throws RuntimeException if unable to read or an error occurs while
339
     *                          reading.
340
     */
341
    public function getContents(): string
342
    {
343
        if (!$this->isReadable()) {
344
            throw new RuntimeException(__CLASS__.': Stream is not readable; '.__METHOD__);
345
        }
346
347
        if (($content = stream_get_contents($this->resource)) === false) {
348
            throw new RuntimeException(__CLASS__.': Error reading stream; '.__METHOD__);
349
        }
350
351
        return $content;
352
    }
353
354
    /**
355
     * Get stream metadata as an associative array or retrieve a specific key.
356
     *
357
     * The keys returned are identical to the keys returned from PHP's
358
     * stream_get_meta_data() function.
359
     *
360
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
361
     *
362
     * @param string $key Specific metadata to retrieve.
363
     *
364
     * @return array Returns an associative array if no key is provided.
365
     *               Returns an associative array with the specific  key value
366
     *               if a key is provided and the value is found, or a void
367
     *               array if the key is not found.
368
     */
369
    public function getMetadata(string $key = ''): array
370
    {
371
        $metadata = stream_get_meta_data($this->resource);
372
373
        //if key is empty strung
374
        return ($key === '') ?
375
            //return metadata
376
            $metadata :
377
            //else check if key exist and if key exist return as array else return void array
378
            (isset($metadata[$key]) ? [$key => $metadata[$key]] : []);
379
    }
380
}
381