Completed
Push — master ( af1339...c893cf )
by Bohuslav
03:14
created

Stream::atach()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
namespace Kambo\HttpMessage;
3
4
// \Spl
5
use InvalidArgumentException;
6
use RuntimeException;
7
8
// \Psr
9
use Psr\Http\Message\StreamInterface;
10
11
/**
12
 * Describes a data stream.
13
 *
14
 * It provides a wrapper around the most common operations, including serialization of
15
 * the entire stream to a string.
16
 *
17
 * @package Kambo\HttpMessage
18
 * @author  Bohuslav Simek <[email protected]>
19
 * @license MIT
20
 */
21
class Stream implements StreamInterface
22
{
23
    /**
24
     * Underline stream
25
     *
26
     * @var Resource
27
     */
28
    private $stream = null;
29
30
    /**
31
     * Size of file
32
     *
33
     * @var int|null
34
     */
35
    private $size = null;
36
37
    /**
38
     * Metadata of file
39
     *
40
     * @var array
41
     */
42
    private $meta = null;
43
44
    /**
45
     * Resource modes
46
     *
47
     * @var  array
48
     * @link http://php.net/manual/function.fopen.php
49
     */
50
    private $modes = [
51
        'readable' => ['r', 'r+', 'w+', 'a+', 'x+', 'c+', 'w+b'],
52
        'writable' => ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+', 'w+b'],
53
    ];
54
55
    /**
56
     * Constructor
57
     *
58
     * @param Resource $stream Underline resource stream
59
     */
60
    public function __construct($stream)
61
    {
62
        $this->stream = $stream;
63
    }
64
65
    /**
66
     * Closes the stream and any underlying resources.
67
     *
68
     * @return void
69
     */
70
    public function close()
71
    {
72
        if ($this->stream !== null) {
73
            fclose($this->stream);
74
        }
75
76
        $this->detach();
77
    }
78
79
    /**
80
     * Separates any underlying resources from the stream.
81
     *
82
     * After the stream has been detached, the stream is in an unusable state.
83
     *
84
     * @return resource|null Underlying PHP stream, if any
85
     */
86
    public function detach()
87
    {
88
        $oldStream = $this->stream;
89
90
        $this->stream = null;
91
        $this->size   = null;
92
        $this->meta   = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $meta.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
93
94
        return $oldStream;
95
    }
96
97
    /**
98
     * Get the size of the stream if known.
99
     *
100
     * @return int|null Returns the size in bytes if known, or null if unknown.
101
     */
102
    public function getSize()
103
    {
104
        if ($this->stream !== null && $this->size === null) {
105
            $stats = fstat($this->stream);
106
            $this->size = isset($stats['size']) ? $stats['size'] : null;
107
        }
108
109
        return $this->size;
110
    }
111
112
    /**
113
     * Returns the current position of the file read/write pointer
114
     *
115
     * @return int Position of the file pointer
116
     * @throws \RuntimeException on error.
117
     */
118
    public function tell()
119
    {
120
        if ($this->stream === null || ($position = ftell($this->stream)) === false) {
121
            throw new RuntimeException('Could not get the position of the pointer in stream');
122
        }
123
124
        return $position;
125
    }
126
127
    /**
128
     * Returns true if the stream is at the end of the stream.
129
     *
130
     * @return bool
131
     */
132
    public function eof()
133
    {
134
        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
    public function isSeekable()
143
    {
144
        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
     * @param int $offset Stream offset
152
     * @param int $whence Specifies how the cursor position will be calculated
153
     *     based on the seek offset. Valid values are identical to the built-in
154
     *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
155
     *     offset bytes SEEK_CUR: Set position to current location plus offset
156
     *     SEEK_END: Set position to end-of-stream plus offset.
157
     * @throws \RuntimeException on failure.
158
     */
159
    public function seek($offset, $whence = SEEK_SET)
160
    {
161
        if (!$this->isSeekable() || fseek($this->stream, $offset, $whence) === -1) {
162
            throw new RuntimeException('Could not seek in stream');
163
        }
164
    }
165
166
    /**
167
     * Seek to the beginning of the stream.
168
     *
169
     * If the stream is not seekable, this method will raise an exception;
170
     * otherwise, it will perform a seek(0).
171
     *
172
     * @see seek()
173
     * @link http://www.php.net/manual/en/function.fseek.php
174
     * @throws \RuntimeException on failure.
175
     */
176
    public function rewind()
177
    {
178
        $this->seek(0);
179
    }
180
181
    /**
182
     * Returns whether or not the stream is writable.
183
     *
184
     * @return bool
185
     */
186
    public function isWritable()
187
    {
188
        $fileMode = $this->getMetadata('mode');
189
        return (in_array($fileMode, $this->modes['writable']));
190
    }
191
192
    /**
193
     * Write data to the stream.
194
     *
195
     * @param string $string The string that is to be written.
196
     * @return int Returns the number of bytes written to the stream.
197
     * @throws \RuntimeException on failure.
198
     */
199
    public function write($string)
200
    {
201
        if (!$this->isWritable() || ($written = fwrite($this->stream, $string)) === false) {
202
            throw new RuntimeException('Could not write to stream');
203
        }
204
205
        // clear size
206
        $this->size = null;
207
208
        return $written;
209
    }
210
211
    /**
212
     * Returns whether or not the stream is readable.
213
     *
214
     * @return bool
215
     */
216
    public function isReadable()
217
    {
218
        $fileMode = $this->getMetadata('mode');
219
        return (in_array($fileMode, $this->modes['readable']));
220
    }
221
222
    /**
223
     * Read data from the stream.
224
     *
225
     * @param int $length Read up to $length bytes from the object and return
226
     *     them. Fewer than $length bytes may be returned if underlying stream
227
     *     call returns fewer bytes.
228
     * @return string Returns the data read from the stream, or an empty string
229
     *     if no bytes are available.
230
     * @throws \RuntimeException if an error occurs.
231
     */
232
    public function read($length)
233
    {
234
        if (!$this->isReadable() || ($data = fread($this->stream, $length)) === false) {
235
            throw new RuntimeException('Could not read from stream');
236
        }
237
238
        return $data;
239
    }
240
241
    /**
242
     * Returns the remaining contents in a string
243
     *
244
     * @return string
245
     * @throws \RuntimeException if unable to read or an error occurs while
246
     *     reading.
247
     */
248
    public function getContents()
249
    {
250
        if (!$this->isReadable() || ($contents = stream_get_contents($this->stream)) === false) {
251
            throw new RuntimeException('Could not get contents of stream');
252
        }
253
254
        return $contents;
255
    }
256
257
    /**
258
     * Get stream metadata as an associative array or retrieve a specific key.
259
     *
260
     * The keys returned are identical to the keys returned from PHP's
261
     * stream_get_meta_data() function.
262
     *
263
     * @link http://php.net/manual/en/function.stream-get-meta-data.php
264
     *
265
     * @param string $key Specific metadata to retrieve.
266
     *
267
     * @return array|mixed|null Returns an associative array if no key is
268
     *     provided. Returns a specific key value if a key is provided and the
269
     *     value is found, or null if the key is not found.
270
     */
271
    public function getMetadata($key = null)
272
    {
273
        if (isset($this->stream)) {
274
            $this->meta = stream_get_meta_data($this->stream);
275
            if (is_null($key) === true) {
276
                return $this->meta;
277
            }
278
        }
279
280
        return isset($this->meta[$key]) ? $this->meta[$key] : null;
281
    }
282
283
284
    /**
285
     * Reads all data from the stream into a string, from the beginning to end.
286
     *
287
     * This method MUST attempt to seek to the beginning of the stream before
288
     * reading data and read the stream until the end is reached.
289
     *
290
     * Warning: This could attempt to load a large amount of data into memory.
291
     *
292
     * This method MUST NOT raise an exception in order to conform with PHP's
293
     * string casting operations.
294
     *
295
     * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
296
     * @return string
297
     */
298
    public function __toString()
299
    {
300
        if ($this->stream === null) {
301
            return '';
302
        }
303
304
        try {
305
            $this->rewind();
306
            return $this->getContents();
307
        } catch (RuntimeException $e) {
308
            return '';
309
        }
310
    }
311
}
312