Completed
Push — master ( e78b35...c448e0 )
by Tobias
18:18 queued 08:25
created

Stream   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 92.93%

Importance

Changes 0
Metric Value
wmc 42
lcom 1
cbo 0
dl 0
loc 244
ccs 92
cts 99
cp 0.9293
rs 9.0399
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A isReadable() 0 4 1
A read() 0 8 2
A __construct() 0 3 1
A create() 0 26 4
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 tell() 0 8 2
A eof() 0 4 2
A isSeekable() 0 4 1
A seek() 0 10 3
A rewind() 0 4 1
A isWritable() 0 4 1
A write() 0 15 3
A getContents() 0 12 3
A getMetadata() 0 14 4

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