Completed
Pull Request — master (#52)
by Joel
02:18
created

BufferedStream   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 76.53%

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 40
c 1
b 0
f 1
lcom 1
cbo 1
dl 0
loc 252
ccs 75
cts 98
cp 0.7653
rs 8.2608

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 3
A __toString() 0 12 3
A close() 0 9 2
A detach() 0 15 2
A getSize() 0 12 4
A tell() 0 8 2
A eof() 0 9 3
A isSeekable() 0 8 2
A seek() 0 8 2
A rewind() 0 8 2
A isWritable() 0 4 1
A write() 0 4 1
A isReadable() 0 4 1
B read() 0 25 4
A getContents() 0 14 3
B getMetadata() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like BufferedStream 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 BufferedStream, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Http\Message\Stream;
4
5
use Psr\Http\Message\StreamInterface;
6
7
/**
8
 * A buffered stream allow to buffer over an existing.
9
 *
10
 * You should use this decorator when you want to seek over a not seekable stream.
11
 * This stream is however read-only.
12
 */
13
class BufferedStream implements StreamInterface
14
{
15
    /** @var resource The buffered resource used to seek previous data */
16
    private $resource;
17
18
    /** @var integer size of the stream if available */
19
    private $size;
20
21
    /** @var StreamInterface The underlying stream decorated by this class */
22
    private $stream;
23
24
    /** @var int How many bytes were written */
25
    private $writed = 0;
26
27 13
    public function __construct(StreamInterface $stream, $useFileBuffer = true, $memoryBuffer = 2097152)
28
    {
29 13
        $this->stream = $stream;
30 13
        $this->size = $stream->getSize();
31
32 13
        if ($useFileBuffer) {
33 13
            $this->resource = fopen('php://temp/maxmemory:'.$memoryBuffer, 'rw+');
34 13
        } else {
35
            $this->resource = fopen('php://memory', 'rw+');
36
        }
37
38 13
        if (false === $this->resource) {
39
            throw new \RuntimeException('Cannot create a resource over temp or memory implementation');
40
        }
41 13
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46 1
    public function __toString()
47
    {
48
        try {
49 1
            $this->rewind();
50
51 1
            return $this->getContents();
52
        } catch (\Throwable $throwable) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
53
            return '';
54
        } catch (\Exception $exception) {
55
            return '';
56
        }
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function close()
63
    {
64
        if (null === $this->resource) {
65
            throw new \RuntimeException('Cannot close on a detached stream');
66
        }
67
68
        $this->stream->close();
69
        fclose($this->resource);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 1
    public function detach()
76
    {
77 1
        if (null === $this->resource) {
78 1
            return null;
79
        }
80
81
        // Force reading the remaining data of the stream
82 1
        $this->getContents();
83
84 1
        $resource = $this->resource;
85 1
        $this->stream = null;
86 1
        $this->resource = null;
87
88 1
        return $resource;
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 1
    public function getSize()
95
    {
96 1
        if (null === $this->size && $this->stream->eof()) {
97 1
            if (null === $this->resource) {
98
                return null;
99
            }
100
101 1
            return $this->writed;
102
        }
103
104 1
        return $this->size;
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110 3
    public function tell()
111
    {
112 3
        if (null === $this->resource) {
113
            throw new \RuntimeException('Cannot tell on a detached stream');
114
        }
115
116 3
        return ftell($this->resource);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 2
    public function eof()
123
    {
124 2
        if (null === $this->resource) {
125
            throw new \RuntimeException('Cannot call eof on a detached stream');
126
        }
127
128
        // We are at the end only when both our resource and underlying stream are at eof
129 2
        return $this->stream->eof() && (ftell($this->resource) === $this->writed);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135 1
    public function isSeekable()
136
    {
137 1
        if (null === $this->resource) {
138
            return false;
139
        }
140
141 1
        return true;
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147 2
    public function seek($offset, $whence = SEEK_SET)
148
    {
149 2
        if (null === $this->resource) {
150
            throw new \RuntimeException('Cannot seek on a detached stream');
151
        }
152
153 2
        fseek($this->resource, $offset, $whence);
154 2
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 3
    public function rewind()
160
    {
161 3
        if (null === $this->resource) {
162
            throw new \RuntimeException('Cannot rewind on a detached stream');
163
        }
164
165 3
        rewind($this->resource);
166 3
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 1
    public function isWritable()
172
    {
173 1
        return false;
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 1
    public function write($string)
180
    {
181 1
        throw new \RuntimeException('Cannot write on this stream');
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 1
    public function isReadable()
188
    {
189 1
        return (null !== $this->resource);
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     */
195 8
    public function read($length)
196
    {
197 8
        if (null === $this->resource) {
198
            throw new \RuntimeException('Cannot read on a detached stream');
199
        }
200
201 8
        $read = "";
202
203
        // First read from the resource
204 8
        if (ftell($this->resource) !== $this->writed) {
205 1
            $read = fread($this->resource, $length);
206 1
        }
207
208 8
        $bytesRead = strlen($read);
209
210 8
        if ($bytesRead < $length) {
211 8
            $streamRead = $this->stream->read($length - $bytesRead);
212
213
            // Write on the underlying stream what we read
214 8
            $this->writed += fwrite($this->resource, $streamRead);
215 8
            $read .= $streamRead;
216 8
        }
217
218 8
        return $read;
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224 8
    public function getContents()
225
    {
226 8
        if (null === $this->resource) {
227
            throw new \RuntimeException('Cannot read on a detached stream');
228
        }
229
230 8
        $read = "";
231
232 8
        while (!$this->stream->eof()) {
233 7
            $read .= $this->read(8192);
234 7
        }
235
236 8
        return $read;
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242 1
    public function getMetadata($key = null)
243
    {
244 1
        if (null === $this->resource) {
245
            if (null === $key) {
246
                return [];
247
            }
248
249
            return null;
250
        }
251
252 1
        $metadata = stream_get_meta_data($this->resource);
253
254 1
        if (null === $key) {
255 1
            return $metadata;
256
        }
257
258 1
        if (!array_key_exists($key, $metadata)) {
259 1
            return null;
260
        }
261
262 1
        return $metadata[$key];
263
    }
264
}
265