Stream::isReadable()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Thruster\Component\HttpMessage;
4
5
use Psr\Http\Message\StreamInterface;
6
7
/**
8
 * Class Stream
9
 *
10
 * @package Thruster\Component\HttpMessage
11
 * @author  Aurimas Niekis <[email protected]>
12
 */
13
class Stream implements StreamInterface
14
{
15
    /** @var array Hash of readable and writable stream types */
16
    const READ_WRITE_HASH = [
17
        'read'  => [
18
            'r'   => true,
19
            'w+'  => true,
20
            'r+'  => true,
21
            'x+'  => true,
22
            'c+'  => true,
23
            'rb'  => true,
24
            'w+b' => true,
25
            'r+b' => true,
26
            'x+b' => true,
27
            'c+b' => true,
28
            'rt'  => true,
29
            'w+t' => true,
30
            'r+t' => true,
31
            'x+t' => true,
32
            'c+t' => true,
33
            'a+'  => true
34
        ],
35
        'write' => [
36
            'w'   => true,
37
            'w+'  => true,
38
            'rw'  => true,
39
            'r+'  => true,
40
            'x+'  => true,
41
            'c+'  => true,
42
            'wb'  => true,
43
            'w+b' => true,
44
            'r+b' => true,
45
            'x+b' => true,
46
            'c+b' => true,
47
            'w+t' => true,
48
            'r+t' => true,
49
            'x+t' => true,
50
            'c+t' => true,
51
            'a'   => true,
52
            'a+'  => true
53
        ]
54
    ];
55
56
    /**
57
     * @var resource
58
     */
59
    private $stream;
60
61
    /**
62
     * @var int
63
     */
64
    private $size;
65
66
    /**
67
     * @var bool
68
     */
69
    private $seekable;
70
71
    /**
72
     * @var bool
73
     */
74
    private $readable;
75
76
    /**
77
     * @var bool
78
     */
79
    private $writable;
80
81
    /**
82
     * @var array|mixed|null
83
     */
84
    private $uri;
85
86
    /**
87
     * @var array
88
     */
89
    private $customMetadata;
90
91
    /**
92
     * This constructor accepts an associative array of options.
93
     *
94
     * - size: (int) If a read stream would otherwise have an indeterminate
95
     *   size, but the size is known due to foreknownledge, then you can
96
     *   provide that size, in bytes.
97
     * - metadata: (array) Any additional metadata to return when the metadata
98
     *   of the stream is accessed.
99
     *
100
     * @param resource $stream  Stream resource to wrap.
101
     * @param array    $options Associative array of options.
102
     *
103
     * @throws \InvalidArgumentException if the stream is not a stream resource
104
     */
105 92
    public function __construct($stream, $options = [])
106
    {
107 92
        if (false === is_resource($stream)) {
108 1
            throw new \InvalidArgumentException('Stream must be a resource');
109
        }
110
111 91
        if (isset($options['size'])) {
112
            $this->size = $options['size'];
113
        }
114
115 91
        $this->customMetadata = $options['metadata'] ?? [];
116
117 91
        $this->stream   = $stream;
118 91
        $meta           = stream_get_meta_data($this->stream);
119 91
        $this->seekable = $meta['seekable'];
120 91
        $this->readable = isset(static::READ_WRITE_HASH['read'][$meta['mode']]);
121 91
        $this->writable = isset(static::READ_WRITE_HASH['write'][$meta['mode']]);
122 91
        $this->uri = $this->getMetadata('uri');
123 91
    }
124
125 1
    public function __get($name)
126
    {
127 1
        if ('stream' == $name) {
128 1
            throw new \RuntimeException('The stream is detached');
129
        }
130
131
        throw new \BadMethodCallException('No value for ' . $name);
132
    }
133
134
    /**
135
     * Closes the stream when the destructed
136
     */
137 68
    public function __destruct()
138
    {
139 68
        $this->close();
140 68
    }
141
142 8
    public function __toString() : string
143
    {
144
        try {
145 8
            $this->seek(0);
146 7
            return (string) stream_get_contents($this->stream);
147 1
        } catch (\Exception $e) {
148 1
            return '';
149
        }
150
    }
151
152 4
    public function getContents() : string
153
    {
154 4
        $contents = stream_get_contents($this->stream);
155
156 3
        if ($contents === false) {
157
            throw new \RuntimeException('Unable to read stream contents');
158
        }
159
160 3
        return $contents;
161
    }
162
163 73
    public function close()
164
    {
165 73
        if (isset($this->stream)) {
166 69
            if (is_resource($this->stream)) {
167 69
                fclose($this->stream);
168
            }
169
170 69
            $this->detach();
171
        }
172 73
    }
173
174 74
    public function detach()
175
    {
176 74
        if (false === isset($this->stream)) {
177 1
            return null;
178
        }
179
180 74
        $result = $this->stream;
181 74
        unset($this->stream);
182 74
        $this->size     = $this->uri = null;
183 74
        $this->readable = $this->writable = $this->seekable = false;
184
185 74
        return $result;
186
    }
187
188 26
    public function getSize()
189
    {
190 26
        if (null !== $this->size) {
191 8
            return $this->size;
192
        }
193
194 26
        if (false === isset($this->stream)) {
195 2
            return null;
196
        }
197
198
        // Clear the stat cache if the stream has a URI
199 24
        if ($this->uri) {
200 24
            clearstatcache(true, $this->uri);
201
        }
202
203 24
        $stats = fstat($this->stream);
204 24
        if (isset($stats['size'])) {
205 24
            $this->size = $stats['size'];
206
207 24
            return $this->size;
208
        }
209
210
        return null;
211
    }
212
213 23
    public function isReadable() : bool
214
    {
215 23
        return $this->readable;
216
    }
217
218 8
    public function isWritable() : bool
219
    {
220 8
        return $this->writable;
221
    }
222
223 41
    public function isSeekable() : bool
224
    {
225 41
        return $this->seekable;
226
    }
227
228 33
    public function eof() : bool
229
    {
230 33
        return !$this->stream || feof($this->stream);
231
    }
232
233 28
    public function tell() : int
234
    {
235 28
        $result = ftell($this->stream);
236
237 27
        if ($result === false) {
238
            throw new \RuntimeException('Unable to determine stream position');
239
        }
240
241 27
        return $result;
242
    }
243
244 7
    public function rewind()
245
    {
246 7
        $this->seek(0);
247 7
    }
248
249 44
    public function seek($offset, $whence = SEEK_SET)
250
    {
251 44
        if (false === $this->seekable) {
252 1
            throw new \RuntimeException('Stream is not seekable');
253 43
        } elseif (-1 === fseek($this->stream, $offset, $whence)) {
254 1
            throw new \RuntimeException('Unable to seek to stream position '
255 1
                . $offset . ' with whence ' . var_export($whence, true));
256
        }
257 43
    }
258
259 38
    public function read($length)
260
    {
261 38
        if (false === $this->readable) {
262 1
            throw new \RuntimeException('Cannot read from non-readable stream');
263
        }
264
265 37
        return fread($this->stream, $length);
266
    }
267
268 18
    public function write($string)
269
    {
270 18
        if (false === $this->writable) {
271 2
            throw new \RuntimeException('Cannot write to a non-writable stream');
272
        }
273
274
        // We can't know the size after writing anything
275 17
        $this->size = null;
276 17
        $result     = fwrite($this->stream, $string);
277
278 17
        if (false === $result) {
279
            throw new \RuntimeException('Unable to write to stream');
280
        }
281
282 17
        return $result;
283
    }
284
285 91
    public function getMetadata($key = null)
286
    {
287 91
        if (false === isset($this->stream)) {
288 1
            return $key ? null : [];
289 91
        } elseif (null === $key) {
290 5
            return $this->customMetadata + stream_get_meta_data($this->stream);
291 91
        } elseif (isset($this->customMetadata[$key])) {
292
            return $this->customMetadata[$key];
293
        }
294
295 91
        $meta = stream_get_meta_data($this->stream);
296
297 91
        return isset($meta[$key]) ? $meta[$key] : null;
298
    }
299
}
300