Completed
Push — master ( 8cd7fb...ffad01 )
by David
03:16
created

BufferedStream::tell()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2.0625
1
<?php
2
3
namespace Http\Message\Stream;
4
5
use Psr\Http\Message\StreamInterface;
6
7
/**
8
 * Decorator to make any stream seekable.
9
 *
10
 * Internally it buffers an existing StreamInterface into a php://temp resource (or memory). By default it will use
11
 * 2 megabytes of memory before writing to a temporary disk file.
12
 *
13
 * Due to this, very large stream can suffer performance issue (i/o slowdown).
14
 */
15
class BufferedStream implements StreamInterface
16
{
17
    /** @var resource The buffered resource used to seek previous data */
18
    private $resource;
19
20
    /** @var int size of the stream if available */
21
    private $size;
22
23
    /** @var StreamInterface The underlying stream decorated by this class */
24
    private $stream;
25
26
    /** @var int How many bytes were written */
27
    private $written = 0;
28
29
    /**
30
     * @param StreamInterface $stream        Decorated stream
31
     * @param bool            $useFileBuffer Whether to use a file buffer (write to a file, if data exceed a certain size)
32
     *                                       by default, set this to false to only use memory
33
     * @param int             $memoryBuffer  In conjunction with using file buffer, limit (in bytes) from which it begins to buffer
34
     *                                       the data in a file
35
     */
36 13
    public function __construct(StreamInterface $stream, $useFileBuffer = true, $memoryBuffer = 2097152)
37
    {
38 13
        $this->stream = $stream;
39 13
        $this->size = $stream->getSize();
40
41 13
        if ($useFileBuffer) {
42 13
            $this->resource = fopen('php://temp/maxmemory:'.$memoryBuffer, 'rw+');
43 13
        } else {
44
            $this->resource = fopen('php://memory', 'rw+');
45
        }
46
47 13
        if (false === $this->resource) {
48
            throw new \RuntimeException('Cannot create a resource over temp or memory implementation');
49
        }
50 13
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 1
    public function __toString()
56
    {
57
        try {
58 1
            $this->rewind();
59
60 1
            return $this->getContents();
61
        } 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...
62
            return '';
63
        } catch (\Exception $exception) { // Layer to be BC with PHP 5, remove this when we only support PHP 7+
64
            return '';
65
        }
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function close()
72
    {
73
        if (null === $this->resource) {
74
            throw new \RuntimeException('Cannot close on a detached stream');
75
        }
76
77
        $this->stream->close();
78
        fclose($this->resource);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 1
    public function detach()
85
    {
86 1
        if (null === $this->resource) {
87 1
            return;
88
        }
89
90
        // Force reading the remaining data of the stream
91 1
        $this->getContents();
92
93 1
        $resource = $this->resource;
94 1
        $this->stream->close();
95 1
        $this->stream = null;
96 1
        $this->resource = null;
97
98 1
        return $resource;
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104 1
    public function getSize()
105
    {
106 1
        if (null === $this->resource) {
107
            return;
108
        }
109
110 1
        if (null === $this->size && $this->stream->eof()) {
111 1
            return $this->written;
112
        }
113
114 1
        return $this->size;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 3
    public function tell()
121
    {
122 3
        if (null === $this->resource) {
123
            throw new \RuntimeException('Cannot tell on a detached stream');
124
        }
125
126 3
        return ftell($this->resource);
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     */
132 8
    public function eof()
133
    {
134 8
        if (null === $this->resource) {
135
            throw new \RuntimeException('Cannot call eof on a detached stream');
136
        }
137
138
        // We are at the end only when both our resource and underlying stream are at eof
139 8
        return $this->stream->eof() && (ftell($this->resource) === $this->written);
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145 1
    public function isSeekable()
146
    {
147 1
        return null !== $this->resource;
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 2
    public function seek($offset, $whence = SEEK_SET)
154
    {
155 2
        if (null === $this->resource) {
156
            throw new \RuntimeException('Cannot seek on a detached stream');
157
        }
158
159 2
        fseek($this->resource, $offset, $whence);
160 2
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165 3
    public function rewind()
166
    {
167 3
        if (null === $this->resource) {
168
            throw new \RuntimeException('Cannot rewind on a detached stream');
169
        }
170
171 3
        rewind($this->resource);
172 3
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 1
    public function isWritable()
178
    {
179 1
        return false;
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185 1
    public function write($string)
186
    {
187 1
        throw new \RuntimeException('Cannot write on this stream');
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193 1
    public function isReadable()
194
    {
195 1
        return null !== $this->resource;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 8
    public function read($length)
202
    {
203 8
        if (null === $this->resource) {
204
            throw new \RuntimeException('Cannot read on a detached stream');
205
        }
206
207 8
        $read = '';
208
209
        // First read from the resource
210 8
        if (ftell($this->resource) !== $this->written) {
211 1
            $read = fread($this->resource, $length);
212 1
        }
213
214 8
        $bytesRead = strlen($read);
215
216 8
        if ($bytesRead < $length) {
217 8
            $streamRead = $this->stream->read($length - $bytesRead);
218
219
            // Write on the underlying stream what we read
220 8
            $this->written += fwrite($this->resource, $streamRead);
221 8
            $read .= $streamRead;
222 8
        }
223
224 8
        return $read;
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230 8
    public function getContents()
231
    {
232 8
        if (null === $this->resource) {
233
            throw new \RuntimeException('Cannot read on a detached stream');
234
        }
235
236 8
        $read = '';
237
238 8
        while (!$this->eof()) {
239 7
            $read .= $this->read(8192);
240 7
        }
241
242 8
        return $read;
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248 1
    public function getMetadata($key = null)
249
    {
250 1
        if (null === $this->resource) {
251
            if (null === $key) {
252
                return [];
253
            }
254
255
            return;
256
        }
257
258 1
        $metadata = stream_get_meta_data($this->resource);
259
260 1
        if (null === $key) {
261 1
            return $metadata;
262
        }
263
264 1
        if (!array_key_exists($key, $metadata)) {
265 1
            return;
266
        }
267
268 1
        return $metadata[$key];
269
    }
270
}
271