Passed
Push — master ( 90372d...e80252 )
by Nikolay
25:24
created

Stream   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 318
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 54
eloc 106
dl 0
loc 318
c 0
b 0
f 0
rs 6.4799

18 Methods

Rating   Name   Duplication   Size   Complexity  
B setStream() 0 32 7
A getContents() 0 11 3
A eof() 0 7 2
A __toString() 0 14 4
A getSize() 0 12 3
A attach() 0 3 1
A __construct() 0 3 1
A isSeekable() 0 8 2
A isReadable() 0 10 3
A write() 0 17 4
A close() 0 8 2
A seek() 0 14 4
A isWritable() 0 15 6
A rewind() 0 3 1
A tell() 0 12 3
A detach() 0 5 1
A read() 0 17 4
A getMetadata() 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
 * @see       https://github.com/zendframework/zend-diactoros for the canonical source repository
4
 * @copyright Copyright (c) 2015-2018 Zend Technologies USA Inc. (http://www.zend.com)
5
 * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6
 */
7
8
declare(strict_types=1);
9
10
namespace Zend\Diactoros;
11
12
use Psr\Http\Message\StreamInterface;
13
use RuntimeException;
14
15
use function array_key_exists;
16
use function fclose;
17
use function feof;
18
use function fopen;
19
use function fread;
20
use function fseek;
21
use function fstat;
22
use function ftell;
23
use function fwrite;
24
use function get_resource_type;
25
use function is_int;
26
use function is_resource;
27
use function is_string;
28
use function restore_error_handler;
29
use function set_error_handler;
30
use function stream_get_contents;
31
use function stream_get_meta_data;
32
use function strstr;
33
34
use const E_WARNING;
35
use const SEEK_SET;
36
37
/**
38
 * Implementation of PSR HTTP streams
39
 */
40
class Stream implements StreamInterface
41
{
42
    /**
43
     * @var resource|null
44
     */
45
    protected $resource;
46
47
    /**
48
     * @var string|resource
49
     */
50
    protected $stream;
51
52
    /**
53
     * @param string|resource $stream
54
     * @param string $mode Mode with which to open stream
55
     * @throws Exception\InvalidArgumentException
56
     */
57
    public function __construct($stream, string $mode = 'r')
58
    {
59
        $this->setStream($stream, $mode);
60
    }
61
62
    /**
63
     * {@inheritdoc}
64
     */
65
    public function __toString() : string
66
    {
67
        if (! $this->isReadable()) {
68
            return '';
69
        }
70
71
        try {
72
            if ($this->isSeekable()) {
73
                $this->rewind();
74
            }
75
76
            return $this->getContents();
77
        } catch (RuntimeException $e) {
78
            return '';
79
        }
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function close() : void
86
    {
87
        if (! $this->resource) {
88
            return;
89
        }
90
91
        $resource = $this->detach();
92
        fclose($resource);
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    public function detach()
99
    {
100
        $resource = $this->resource;
101
        $this->resource = null;
102
        return $resource;
103
    }
104
105
    /**
106
     * Attach a new stream/resource to the instance.
107
     *
108
     * @param string|resource $resource
109
     * @param string $mode
110
     * @throws Exception\InvalidArgumentException for stream identifier that cannot be
111
     *     cast to a resource
112
     * @throws Exception\InvalidArgumentException for non-resource stream
113
     */
114
    public function attach($resource, string $mode = 'r') : void
115
    {
116
        $this->setStream($resource, $mode);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function getSize() : ?int
123
    {
124
        if (null === $this->resource) {
125
            return null;
126
        }
127
128
        $stats = fstat($this->resource);
129
        if ($stats !== false) {
130
            return $stats['size'];
131
        }
132
133
        return null;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139
    public function tell() : int
140
    {
141
        if (! $this->resource) {
142
            throw Exception\UntellableStreamException::dueToMissingResource();
143
        }
144
145
        $result = ftell($this->resource);
146
        if (! is_int($result)) {
0 ignored issues
show
introduced by
The condition is_int($result) is always true.
Loading history...
147
            throw Exception\UntellableStreamException::dueToPhpError();
148
        }
149
150
        return $result;
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function eof() : bool
157
    {
158
        if (! $this->resource) {
159
            return true;
160
        }
161
162
        return feof($this->resource);
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function isSeekable() : bool
169
    {
170
        if (! $this->resource) {
171
            return false;
172
        }
173
174
        $meta = stream_get_meta_data($this->resource);
175
        return $meta['seekable'];
176
    }
177
178
    /**
179
     * {@inheritdoc}
180
     */
181
    public function seek($offset, $whence = SEEK_SET) : void
182
    {
183
        if (! $this->resource) {
184
            throw Exception\UnseekableStreamException::dueToMissingResource();
185
        }
186
187
        if (! $this->isSeekable()) {
188
            throw Exception\UnseekableStreamException::dueToConfiguration();
189
        }
190
191
        $result = fseek($this->resource, $offset, $whence);
192
193
        if (0 !== $result) {
194
            throw Exception\UnseekableStreamException::dueToPhpError();
195
        }
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201
    public function rewind() : void
202
    {
203
        $this->seek(0);
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function isWritable() : bool
210
    {
211
        if (! $this->resource) {
212
            return false;
213
        }
214
215
        $meta = stream_get_meta_data($this->resource);
216
        $mode = $meta['mode'];
217
218
        return (
219
            strstr($mode, 'x')
220
            || strstr($mode, 'w')
221
            || strstr($mode, 'c')
222
            || strstr($mode, 'a')
223
            || strstr($mode, '+')
224
        );
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function write($string) : int
231
    {
232
        if (! $this->resource) {
233
            throw Exception\UnwritableStreamException::dueToMissingResource();
234
        }
235
236
        if (! $this->isWritable()) {
237
            throw Exception\UnwritableStreamException::dueToConfiguration();
238
        }
239
240
        $result = fwrite($this->resource, $string);
241
242
        if (false === $result) {
243
            throw Exception\UnwritableStreamException::dueToPhpError();
244
        }
245
246
        return $result;
247
    }
248
249
    /**
250
     * {@inheritdoc}
251
     */
252
    public function isReadable() : bool
253
    {
254
        if (! $this->resource) {
255
            return false;
256
        }
257
258
        $meta = stream_get_meta_data($this->resource);
259
        $mode = $meta['mode'];
260
261
        return (strstr($mode, 'r') || strstr($mode, '+'));
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     */
267
    public function read($length) : string
268
    {
269
        if (! $this->resource) {
270
            throw Exception\UnreadableStreamException::dueToMissingResource();
271
        }
272
273
        if (! $this->isReadable()) {
274
            throw Exception\UnreadableStreamException::dueToConfiguration();
275
        }
276
277
        $result = fread($this->resource, $length);
278
279
        if (false === $result) {
280
            throw Exception\UnreadableStreamException::dueToPhpError();
281
        }
282
283
        return $result;
284
    }
285
286
    /**
287
     * {@inheritdoc}
288
     */
289
    public function getContents() : string
290
    {
291
        if (! $this->isReadable()) {
292
            throw Exception\UnreadableStreamException::dueToConfiguration();
293
        }
294
295
        $result = stream_get_contents($this->resource);
296
        if (false === $result) {
297
            throw Exception\UnreadableStreamException::dueToPhpError();
298
        }
299
        return $result;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function getMetadata($key = null)
306
    {
307
        if (null === $key) {
308
            return stream_get_meta_data($this->resource);
309
        }
310
311
        $metadata = stream_get_meta_data($this->resource);
312
        if (! array_key_exists($key, $metadata)) {
313
            return null;
314
        }
315
316
        return $metadata[$key];
317
    }
318
319
    /**
320
     * Set the internal stream resource.
321
     *
322
     * @param string|resource $stream String stream target or stream resource.
323
     * @param string $mode Resource mode for stream target.
324
     * @throws Exception\InvalidArgumentException for invalid streams or resources.
325
     */
326
    private function setStream($stream, string $mode = 'r') : void
327
    {
328
        $error    = null;
329
        $resource = $stream;
330
331
        if (is_string($stream)) {
332
            set_error_handler(function ($e) use (&$error) {
333
                if ($e !== E_WARNING) {
334
                    return;
335
                }
336
337
                $error = $e;
338
            });
339
            $resource = fopen($stream, $mode);
340
            restore_error_handler();
341
        }
342
343
        if ($error) {
344
            throw new Exception\InvalidArgumentException('Invalid stream reference provided');
345
        }
346
347
        if (! is_resource($resource) || 'stream' !== get_resource_type($resource)) {
348
            throw new Exception\InvalidArgumentException(
349
                'Invalid stream provided; must be a string stream identifier or stream resource'
350
            );
351
        }
352
353
        if ($stream !== $resource) {
354
            $this->stream = $stream;
355
        }
356
357
        $this->resource = $resource;
358
    }
359
}
360