Completed
Push — master ( da994a...19a34e )
by Tobias
02:48
created

Stream   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 92.93%

Importance

Changes 0
Metric Value
wmc 43
lcom 1
cbo 0
dl 0
loc 240
ccs 92
cts 99
cp 0.9293
rs 8.96
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A __destruct() 0 4 1
A __toString() 0 12 3
A close() 0 9 3
A detach() 0 13 2
A getSize() 0 24 5
A create() 0 26 4
A tell() 0 8 2
A eof() 0 4 2
A isSeekable() 0 4 1
A seek() 0 8 3
A rewind() 0 4 1
A isWritable() 0 4 1
A write() 0 15 3
A isReadable() 0 4 1
A read() 0 8 2
A getContents() 0 12 3
A getMetadata() 0 12 5

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Psr\Http\Message\StreamInterface;
8
9
/**
10
 * @author Michael Dowling and contributors to guzzlehttp/psr7
11
 * @author Tobias Nyholm <[email protected]>
12
 */
13
final class Stream implements StreamInterface
14
{
15
    /** @var resource A resource reference */
16
    private $stream;
17
18
    /** @var bool */
19
    private $seekable;
20
21
    /** @var bool */
22
    private $readable;
23
24
    /** @var bool */
25
    private $writable;
26
27
    /** @var array|mixed|null|void */
28
    private $uri;
29
30
    /** @var int */
31
    private $size;
32
33
    /** @var array Hash of readable and writable stream types */
34
    private static $readWriteHash = [
35
        'read' => [
36
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
37
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
38
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
39
            'x+t' => true, 'c+t' => true, 'a+' => true,
40
        ],
41
        'write' => [
42
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
43
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
44
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
45
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
46
        ],
47
    ];
48
49 43
    private function __construct()
50
    {
51 43
    }
52
53
    /**
54
     * Creates a new PSR-7 stream.
55
     *
56
     * @param string|resource|StreamInterface $body
57
     *
58
     * @return StreamInterface
59
     *
60
     * @throws \InvalidArgumentException
61
     */
62 43
    public static function create($body = ''): StreamInterface
63
    {
64 43
        if ($body instanceof StreamInterface) {
65
            return $body;
66
        }
67
68 43
        if (is_string($body)) {
69 15
            $resource = fopen('php://temp', 'rw+');
70 15
            fwrite($resource, $body);
71 15
            $body = $resource;
72
        }
73
74 43
        if ('resource' === gettype($body)) {
75 43
            $obj = new self();
76 43
            $obj->stream = $body;
77 43
            $meta = stream_get_meta_data($obj->stream);
78 43
            $obj->seekable = $meta['seekable'];
79 43
            $obj->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
80 43
            $obj->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
81 43
            $obj->uri = $obj->getMetadata('uri');
82
83 43
            return $obj;
84
        }
85
86
        throw new \InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.');
87
    }
88
89
    /**
90
     * Closes the stream when the destructed.
91
     */
92 43
    public function __destruct()
93
    {
94 43
        $this->close();
95 43
    }
96
97 9
    public function __toString(): string
98
    {
99
        try {
100 9
            if ($this->isSeekable()) {
101 8
                $this->seek(0);
102
            }
103
104 9
            return $this->getContents();
105 1
        } catch (\Exception $e) {
106 1
            return '';
107
        }
108
    }
109
110 43
    public function close(): void
111
    {
112 43
        if (isset($this->stream)) {
113 41
            if (is_resource($this->stream)) {
114 41
                fclose($this->stream);
115
            }
116 41
            $this->detach();
117
        }
118 43
    }
119
120 43
    public function detach()
121
    {
122 43
        if (!isset($this->stream)) {
123 1
            return null;
124
        }
125
126 43
        $result = $this->stream;
127 43
        unset($this->stream);
128 43
        $this->size = $this->uri = null;
129 43
        $this->readable = $this->writable = $this->seekable = false;
130
131 43
        return $result;
132
    }
133
134 18
    public function getSize(): ?int
135
    {
136 18
        if (null !== $this->size) {
137 2
            return $this->size;
138
        }
139
140 18
        if (!isset($this->stream)) {
141 2
            return null;
142
        }
143
144
        // Clear the stat cache if the stream has a URI
145 16
        if ($this->uri) {
146 16
            clearstatcache(true, $this->uri);
147
        }
148
149 16
        $stats = fstat($this->stream);
150 16
        if (isset($stats['size'])) {
151 16
            $this->size = $stats['size'];
152
153 16
            return $this->size;
154
        }
155
156
        return null;
157
    }
158
159 3
    public function tell(): int
160
    {
161 3
        if (false === $result = ftell($this->stream)) {
162
            throw new \RuntimeException('Unable to determine stream position');
163
        }
164
165 2
        return $result;
166
    }
167
168 8
    public function eof(): bool
169
    {
170 8
        return !$this->stream || feof($this->stream);
171
    }
172
173 16
    public function isSeekable(): bool
174
    {
175 16
        return $this->seekable;
176
    }
177
178 22
    public function seek($offset, $whence = SEEK_SET): void
179
    {
180 22
        if (!$this->seekable) {
181 2
            throw new \RuntimeException('Stream is not seekable');
182 20
        } elseif (fseek($this->stream, $offset, $whence) === -1) {
183
            throw new \RuntimeException('Unable to seek to stream position '.$offset.' with whence '.var_export($whence, true));
184
        }
185 20
    }
186
187 8
    public function rewind(): void
188
    {
189 8
        $this->seek(0);
190 7
    }
191
192 4
    public function isWritable(): bool
193
    {
194 4
        return $this->writable;
195
    }
196
197 8
    public function write($string): int
198
    {
199 8
        if (!$this->writable) {
200 1
            throw new \RuntimeException('Cannot write to a non-writable stream');
201
        }
202
203
        // We can't know the size after writing anything
204 8
        $this->size = null;
205
206 8
        if (false === $result = fwrite($this->stream, $string)) {
207
            throw new \RuntimeException('Unable to write to stream');
208
        }
209
210 8
        return $result;
211
    }
212
213 4
    public function isReadable(): bool
214
    {
215 4
        return $this->readable;
216
    }
217
218 8
    public function read($length): string
219
    {
220 8
        if (!$this->readable) {
221 1
            throw new \RuntimeException('Cannot read from non-readable stream');
222
        }
223
224 7
        return fread($this->stream, $length);
225
    }
226
227 11
    public function getContents(): string
228
    {
229 11
        if (!isset($this->stream)) {
230 1
            throw new \RuntimeException('Unable to read stream contents');
231
        }
232
233 10
        if (false === $contents = stream_get_contents($this->stream)) {
234
            throw new \RuntimeException('Unable to read stream contents');
235
        }
236
237 10
        return $contents;
238
    }
239
240 43
    public function getMetadata($key = null)
241
    {
242 43
        if (!isset($this->stream)) {
243 1
            return $key ? null : [];
244 43
        } elseif (null === $key) {
245 1
            return stream_get_meta_data($this->stream);
246
        }
247
248 43
        $meta = stream_get_meta_data($this->stream);
249
250 43
        return isset($meta[$key]) ? $meta[$key] : null;
251
    }
252
}
253