Passed
Push — master ( c529f4...5fb311 )
by Zlatin
01:44
created

Stream::detach()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
namespace DevOp\Core\Http;
3
4
use Psr\Http\Message\StreamInterface;
5
6
class Stream implements StreamInterface
7
{
8
9
    /**
10
     * @var array
11
     */
12
    private static $modes = [
13
        'readable' => [
14
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
15
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
16
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
17
            'x+t' => true, 'c+t' => true, 'a+' => true
18
        ],
19
        'writable' => [
20
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
21
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
22
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
23
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
24
        ],
25
    ];
26
27
    /**
28
     * @var resource
29
     */
30
    private $stream;
31
32
    /**
33
     * @param resource|string $handle
34
     * @param string|null $mode
35
     * @throws \InvalidArgumentException
36
     */
37 16
    public function __construct($handle, $mode = 'r')
38
    {
39
40 16
        if (is_string($handle)) {
41 16
            $handle = fopen($handle, $mode);
42
        }
43
44 16
        if (!is_resource($handle)) {
45
            throw new \InvalidArgumentException('Argument must be a valid stream.');
46
        }
47
48 16
        $this->stream = $handle;
49 16
    }
50
51
    /**
52
     * @return string
53
     */
54
    public function __toString()
55
    {
56
        try {
57
            if ($this->isSeekable()) {
58
                $this->rewind();
59
            }
60
            return $this->getContents();
61
        } catch (\Exception $e) {
62
            return '';
63
        }
64
    }
65
66
    /**
67
     * @return boolean
68
     */
69
    public function close()
70
    {
71
        if (!$this->stream) {
72
            return;
73
        }
74
75
        $handle = $this->detach();
76
        fclose($handle);
77
    }
78
79
    /**
80
     * @return resource
81
     */
82
    public function detach()
83
    {
84
        $handle = $this->stream;
85
        $this->stream = null;
86
87
        return $handle;
88
    }
89
90
    /**
91
     * @return boolean
92
     */
93
    public function eof()
94
    {
95
        if (!$this->stream) {
96
            return true;
97
        }
98
99
        return feof($this->stream);
100
    }
101
102
    /**
103
     * @return string
104
     * @throws \RuntimeException
105
     */
106
    public function getContents()
107
    {
108
        if (!$this->stream) {
109
            throw new \RuntimeException('Empty stream');
110
        }
111
112
        if ($this->isReadable()) {
113
            throw new \RuntimeException('Unable to read stream');
114
        }
115
116
        return (string) stream_get_contents($this->stream);
117
    }
118
119
    /**
120
     * @param string|null $key
121
     * @return mixed
122
     */
123 6
    public function getMetadata($key = null)
124
    {
125 6
        $metadata = stream_get_meta_data($this->stream);
126
127 6
        if (null === $key) {
128
            return $metadata;
129
        }
130
131 6
        if (isset($metadata[$key])) {
132 6
            return $metadata[$key];
133
        }
134
135
        return null;
136
    }
137
138
    /**
139
     * @return int
140
     */
141
    public function getSize()
142
    {
143
        $fstats = fstat($this->stream);
144
        if (isset($fstats['size'])) {
145
            return $fstats['size'];
146
        }
147
148
        return null;
149
    }
150
151
    /**
152
     * @return boolean
153
     */
154 2
    public function isReadable()
155
    {
156 2
        if (!$this->stream) {
157
            return false;
158
        }
159
160 2
        $mode = $this->getMetadata('mode');
161 2
        return isset(self::$modes['readable'][$mode]);
162
    }
163
164
    /**
165
     * @return boolean
166
     */
167 2
    public function isSeekable()
168
    {
169 2
        if (!$this->stream) {
170
            return false;
171
        }
172
173 2
        if ($this->getMetadata('seekable')) {
174 2
            return true;
175
        }
176
177
        return false;
178
    }
179
180
    /**
181
     * @return boolean
182
     */
183 2
    public function isWritable()
184
    {
185 2
        if (!$this->stream) {
186
            return false;
187
        }
188
189 2
        $mode = $this->getMetadata('mode');
190 2
        return isset(self::$modes['writable'][$mode]);
191
    }
192
193
    /**
194
     * @param int $length
195
     * @return string
196
     * @throws \RuntimeException
197
     */
198
    public function read($length)
199
    {
200
        if (!$this->isReadable()) {
201
            throw new \RuntimeException('Stream is not readable');
202
        }
203
204
        return fread($this->stream, $length);
205
    }
206
207
    /**
208
     * @throws \RuntimeException
209
     */
210
    public function rewind()
211
    {
212
        if ($this->isSeekable()) {
213
            throw new \RuntimeException('Stream is not seekable');
214
        }
215
216
        $this->seek(0);
217
    }
218
219
    /**
220
     * @param int $offset
221
     * @param int $whence
222
     * @throws \RuntimeException
223
     */
224
    public function seek($offset, $whence = SEEK_SET)
225
    {
226
        if (!$this->isSeekable()) {
227
            throw new \RuntimeException('Stream is not seekable');
228
        }
229
230
        return fseek($this->stream, $offset, $whence);
231
    }
232
233
    /**
234
     * @return int
235
     * @throws \RuntimeException
236
     */
237
    public function tell()
238
    {
239
        if (!$this->stream) {
240
            throw new \RuntimeException('Invalid stream handle');
241
        }
242
243
        return ftell($this->stream);
244
    }
245
246
    /**
247
     * @param string $string
248
     * @return int
249
     * @throws \RuntimeException
250
     */
251
    public function write($string)
252
    {
253
        if (!$this->isWritable()) {
254
            throw new \RuntimeException('Stream is not writable');
255
        }
256
257
        return fwrite($this->stream, $string);
258
    }
259
}
260