Completed
Push — master ( 4fd434...e1a522 )
by Bohuslav
03:35
created

Stream   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 0
dl 0
loc 300
ccs 69
cts 69
cp 1
rs 8.8
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A close() 0 8 2
A detach() 0 10 1
A getSize() 0 9 4
A tell() 0 8 3
A eof() 0 4 2
A isSeekable() 0 4 1
A seek() 0 6 3
A rewind() 0 4 1
A isWritable() 0 5 1
A write() 0 11 3
A isReadable() 0 5 1
A read() 0 8 3
A getContents() 0 8 3
A getMetadata() 0 11 4
A __toString() 0 13 3
1
<?php
2
namespace Kambo\Http\Message;
3
4
// \Spl
5
use RuntimeException;
6
7
// \Psr
8
use Psr\Http\Message\StreamInterface;
9
10
/**
11
 * Describes a data stream.
12
 *
13
 * It provides a wrapper around the most common operations, including serialization of
14
 * the entire stream to a string.
15
 *
16
 * @package Kambo\Http\Message
17
 * @author  Bohuslav Simek <[email protected]>
18
 * @license MIT
19
 */
20
class Stream implements StreamInterface
21
{
22
    /**
23
     * Underline stream
24
     *
25
     * @var Resource|null
26
     */
27
    private $stream = null;
28
29
    /**
30
     * Size of file
31
     *
32
     * @var int|null
33
     */
34
    private $size = null;
35
36
    /**
37
     * Metadata of file
38
     *
39
     * @var array|null
40
     */
41
    private $meta = null;
42
43
    /**
44
     * Resource modes
45
     *
46
     * @var  array
47
     * @link http://php.net/manual/function.fopen.php
48
     */
49
    private $modes = [
50
        'readable' => ['r', 'r+', 'w+', 'a+', 'x+', 'c+', 'w+b'],
51
        'writable' => ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+', 'w+b'],
52
    ];
53
54
    /**
55
     * Constructor
56
     *
57
     * @param Resource $stream Underline resource stream
58
     */
59 53
    public function __construct($stream)
60
    {
61 53
        $this->stream = $stream;
62 53
    }
63
64
    /**
65
     * Closes the stream and any underlying resources.
66
     *
67
     * @return void
68
     */
69 1
    public function close()
70
    {
71 1
        if ($this->stream !== null) {
72 1
            fclose($this->stream);
73 1
        }
74
75 1
        $this->detach();
76 1
    }
77
78
    /**
79
     * Separates any underlying resources from the stream.
80
     *
81
     * After the stream has been detached, the stream is in an unusable state.
82
     *
83
     * @return resource|null Underlying PHP stream, if any
84
     */
85 7
    public function detach()
86
    {
87 7
        $oldStream = $this->stream;
88
89 7
        $this->stream = null;
90 7
        $this->size   = null;
91 7
        $this->meta   = null;
92
93 7
        return $oldStream;
94
    }
95
96
    /**
97
     * Get the size of the stream if known.
98
     *
99
     * @return int|null Returns the size in bytes if known, or null if unknown.
100
     */
101 1
    public function getSize()
102
    {
103 1
        if ($this->stream !== null && $this->size === null) {
104 1
            $stats = fstat($this->stream);
105 1
            $this->size = isset($stats['size']) ? $stats['size'] : null;
106 1
        }
107
108 1
        return $this->size;
109
    }
110
111
    /**
112
     * Returns the current position of the file read/write pointer
113
     *
114
     * @return int Position of the file pointer
115
     *
116
     * @throws \RuntimeException on error.
117
     */
118 2
    public function tell()
119
    {
120 2
        if ($this->stream === null || ($position = ftell($this->stream)) === false) {
121 1
            throw new RuntimeException('Could not get the position of the pointer in stream');
122
        }
123
124 1
        return $position;
125
    }
126
127
    /**
128
     * Returns true if the stream is at the end of the stream.
129
     *
130
     * @return bool
131
     */
132 2
    public function eof()
133
    {
134 2
        return $this->stream !== null ? feof($this->stream) : true;
135
    }
136
137
    /**
138
     * Returns whether or not the stream is seekable.
139
     *
140
     * @return bool 
141
     */
142 8
    public function isSeekable()
143
    {
144 8
        return $this->getMetadata('seekable');
145
    }
146
147
    /**
148
     * Seek to a position in the stream.
149
     *
150
     * @link http://www.php.net/manual/en/function.fseek.php
151
     *
152
     * @param int $offset Stream offset
153
     * @param int $whence Specifies how the cursor position will be calculated
154
     *                    based on the seek offset. Valid values are identical to the built-in
155
     *                    PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
156
     *                    offset bytes SEEK_CUR: Set position to current location plus offset
157
     *                    SEEK_END: Set position to end-of-stream plus offset.
158
     *
159
     * @throws \RuntimeException on failure.
160
     */
161 8
    public function seek($offset, $whence = SEEK_SET)
162
    {
163 8
        if (!$this->isSeekable() || fseek($this->stream, $offset, $whence) === -1) {
164 1
            throw new RuntimeException('Could not seek in stream');
165
        }
166 7
    }
167
168
    /**
169
     * Seek to the beginning of the stream.
170
     *
171
     * If the stream is not seekable, this method will raise an exception;
172
     * otherwise, it will perform a seek(0).
173
     *
174
     * @see seek()
175
     * @link http://www.php.net/manual/en/function.fseek.php
176
     *
177
     * @throws \RuntimeException on failure.
178
     */
179 5
    public function rewind()
180
    {
181 5
        $this->seek(0);
182 5
    }
183
184
    /**
185
     * Returns whether or not the stream is writable.
186
     *
187
     * @return bool
188
     */
189 4
    public function isWritable()
190
    {
191 4
        $fileMode = $this->getMetadata('mode');
192 4
        return (in_array($fileMode, $this->modes['writable']));
193
    }
194
195
    /**
196
     * Write data to the stream.
197
     *
198
     * @param string $string The string that is to be written.
199
     *
200
     * @return int Returns the number of bytes written to the stream.
201
     *
202
     * @throws \RuntimeException on failure.
203
     */
204 4
    public function write($string)
205
    {
206 4
        if (!$this->isWritable() || ($written = fwrite($this->stream, $string)) === false) {
207 1
            throw new RuntimeException('Could not write to stream');
208
        }
209
210
        // clear size
211 3
        $this->size = null;
212
213 3
        return $written;
214
    }
215
216
    /**
217
     * Returns whether or not the stream is readable.
218
     *
219
     * @return bool
220
     */
221 10
    public function isReadable()
222
    {
223 10
        $fileMode = $this->getMetadata('mode');
224 10
        return (in_array($fileMode, $this->modes['readable']));
225
    }
226
227
    /**
228
     * Read data from the stream.
229
     *
230
     * @param int $length Read up to $length bytes from the object and return
231
     *                    them. Fewer than $length bytes may be returned if underlying stream
232
     *                    call returns fewer bytes.
233
     *
234
     * @return string Returns the data read from the stream, or an empty string
235
     *     if no bytes are available.
236
     *
237
     * @throws \RuntimeException if an error occurs.
238
     */
239 2
    public function read($length)
240
    {
241 2
        if (!$this->isReadable() || ($data = fread($this->stream, $length)) === false) {
242 1
            throw new RuntimeException('Could not read from stream');
243
        }
244
245 1
        return $data;
246
    }
247
248
    /**
249
     * Returns the remaining contents in a string
250
     *
251
     * @return string
252
     *
253
     * @throws \RuntimeException if unable to read or an error occurs while reading.
254
     */
255 8
    public function getContents()
256
    {
257 8
        if (!$this->isReadable() || ($contents = stream_get_contents($this->stream)) === false) {
258 2
            throw new RuntimeException('Could not get contents of stream');
259
        }
260
261 6
        return $contents;
262
    }
263
264
    /**
265
     * Get stream metadata as an associative array or retrieve a specific key.
266
     *
267
     * The keys returned are identical to the keys returned from PHP's
268
     * stream_get_meta_data() function.
269
     *
270
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
271
     *
272
     * @param string $key Specific metadata to retrieve.
273
     *
274
     * @return array|mixed|null Returns an associative array if no key is provided.
275
     *                          Returns a specific key value if a key is provided and the
276
     *                          value is found, or null if the key is not found.
277
     */
278 17
    public function getMetadata($key = null)
279
    {
280 17
        if (isset($this->stream)) {
281 14
            $this->meta = stream_get_meta_data($this->stream);
282 14
            if (is_null($key) === true) {
283 1
                return $this->meta;
284
            }
285 13
        }
286
287 16
        return isset($this->meta[$key]) ? $this->meta[$key] : null;
288
    }
289
290
291
    /**
292
     * Reads all data from the stream into a string, from the beginning to end.
293
     *
294
     * This method attempt to seek to the beginning of the stream before
295
     * reading data and read the stream until the end is reached.
296
     *
297
     * Warning: This could attempt to load a large amount of data into memory.
298
     *
299
     * This method does not raise an exception in order to conform with PHP's
300
     * string casting operations.
301
     *
302
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
303
     *
304
     * @return string
305
     */
306 4
    public function __toString()
307
    {
308 4
        if ($this->stream === null) {
309 1
            return '';
310
        }
311
312
        try {
313 3
            $this->rewind();
314 3
            return $this->getContents();
315 1
        } catch (RuntimeException $e) {
316 1
            return '';
317
        }
318
    }
319
}
320