Passed
Push — master ( d52486...c86ace )
by Akmal
01:11
created

Stream::write()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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