Stream::seek()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 8
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cakasim\Payone\Sdk\Http\Message;
6
7
use Exception;
8
use Psr\Http\Message\StreamInterface;
9
use RuntimeException;
10
11
/**
12
 * Implements the PSR-7 stream interface.
13
 *
14
 * @author Fabian Böttcher <[email protected]>
15
 * @since 0.1.0
16
 */
17
class Stream implements StreamInterface
18
{
19
    /**
20
     * Contains all readable modes.
21
     */
22
    protected const READABLE_MODES = ['r', 'r+', 'w+', 'a+', 'x+', 'c+'];
23
24
    /**
25
     * Contains all writeable modes.
26
     */
27
    protected const WRITEABLE_MODES = ['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+'];
28
29
    /**
30
     * @var resource|null The underlying stream resource or null if the stream is invalid.
31
     */
32
    protected $stream;
33
34
    /**
35
     * Constructs the Stream.
36
     *
37
     * @param resource $stream The resource to construct the stream with.
38
     */
39
    public function __construct($stream)
40
    {
41
        $this->stream = $stream;
42
    }
43
44
    /**
45
     * Checks whether the stream is valid.
46
     * The stream is valid if the underlying
47
     * resource is valid.
48
     *
49
     * @return bool True if the stream is valid.
50
     */
51
    protected function isValid(): bool
52
    {
53
        return is_resource($this->stream);
54
    }
55
56
    /**
57
     * @inheritDoc
58
     */
59
    public function getMetadata($key = null)
60
    {
61
        if (!$this->isValid()) {
62
            return null;
63
        }
64
65
        $meta = stream_get_meta_data($this->stream);
66
67
        return is_string($key)
68
            ? ($meta[$key] ?? null)
69
            : $meta;
70
    }
71
72
    /**
73
     * Returns stats about the underlying stream.
74
     *
75
     * @param string|null $key The key of the concrete stat to return.
76
     * @return array|mixed|null All stats if key is null, the requested stat or null if the stat does not exist.
77
     */
78
    protected function getStats(string $key = null)
79
    {
80
        if (!$this->isValid()) {
81
            return null;
82
        }
83
84
        $stats = fstat($this->stream);
85
86
        return is_string($key)
87
            ? ($stats[$key] ?? null)
88
            : $stats;
89
    }
90
91
    /**
92
     * @inheritDoc
93
     */
94
    public function close(): void
95
    {
96
        // Attempt to close the valid stream.
97
        if ($this->isValid()) {
98
            fclose($this->stream);
99
        }
100
101
        // Set stream reference to null.
102
        // The stream is now invalid.
103
        $this->stream = null;
104
    }
105
106
    /**
107
     * @inheritDoc
108
     */
109
    public function detach()
110
    {
111
        $stream = null;
112
113
        if ($this->isValid()) {
114
            $stream = $this->stream;
115
            $this->stream = null;
116
        }
117
118
        return $stream;
119
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124
    public function getSize()
125
    {
126
        return $this->getStats('size');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getStats('size') also could return the type array which is incompatible with the return type mandated by Psr\Http\Message\StreamInterface::getSize() of integer|null.
Loading history...
127
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132
    public function tell()
133
    {
134
        if (!$this->isValid()) {
135
            throw new RuntimeException('The stream is invalid.');
136
        }
137
138
        $pos = ftell($this->stream);
139
140
        if (!is_int($pos)) {
0 ignored issues
show
introduced by
The condition is_int($pos) is always true.
Loading history...
141
            throw new RuntimeException('Failed to tell file pointer position.');
142
        }
143
144
        return $pos;
145
    }
146
147
    /**
148
     * @inheritDoc
149
     */
150
    public function eof()
151
    {
152
        // Return feof() result for valid streams, otherwise true.
153
        return $this->isValid() ? feof($this->stream) : true;
154
    }
155
156
    /**
157
     * @inheritDoc
158
     */
159
    public function isSeekable()
160
    {
161
        return $this->getMetadata('seekable') === true;
162
    }
163
164
    /**
165
     * @inheritDoc
166
     */
167
    public function seek($offset, $whence = SEEK_SET): void
168
    {
169
        if (!$this->isSeekable()) {
170
            throw new \RuntimeException('The stream is not seekable.');
171
        }
172
173
        if (fseek($this->stream, $offset, $whence) !== 0) {
174
            throw new \RuntimeException('Failed to seek in the stream.');
175
        }
176
    }
177
178
    /**
179
     * @inheritDoc
180
     */
181
    public function rewind(): void
182
    {
183
        if (!$this->isSeekable() || !rewind($this->stream)) {
184
            throw new RuntimeException('Failed to rewind stream.');
185
        }
186
    }
187
188
    /**
189
     * @inheritDoc
190
     */
191
    public function isWritable()
192
    {
193
        $mode = $this->getMetadata('mode');
194
195
        if ($mode === null) {
196
            return false;
197
        }
198
199
        foreach (static::WRITEABLE_MODES as $m) {
200
            if (strstr($mode, $m) !== false) {
201
                return true;
202
            }
203
        }
204
205
        return false;
206
    }
207
208
    /**
209
     * @inheritDoc
210
     */
211
    public function write($string)
212
    {
213
        if (!$this->isWritable()) {
214
            throw new RuntimeException('The stream is not writable.');
215
        }
216
217
        $writtenBytes = fwrite($this->stream, $string);
218
219
        if (!is_int($writtenBytes)) {
0 ignored issues
show
introduced by
The condition is_int($writtenBytes) is always true.
Loading history...
220
            throw new RuntimeException('Failed writing to stream.');
221
        }
222
223
        return $writtenBytes;
224
    }
225
226
    /**
227
     * @inheritDoc
228
     */
229
    public function isReadable()
230
    {
231
        $mode = $this->getMetadata('mode');
232
233
        if ($mode === null) {
234
            return false;
235
        }
236
237
        foreach (static::READABLE_MODES as $m) {
238
            if (strstr($mode, $m) !== false) {
239
                return true;
240
            }
241
        }
242
243
        return false;
244
    }
245
246
    /**
247
     * @inheritDoc
248
     */
249
    public function read($length)
250
    {
251
        if (!$this->isReadable()) {
252
            throw new RuntimeException('The stream is not readable.');
253
        }
254
255
        $data = fread($this->stream, $length);
256
257
        if (!is_string($data)) {
0 ignored issues
show
introduced by
The condition is_string($data) is always true.
Loading history...
258
            throw new RuntimeException('Failed to read from stream.');
259
        }
260
261
        return $data;
262
    }
263
264
    /**
265
     * @inheritDoc
266
     */
267
    public function getContents()
268
    {
269
        if (!$this->isValid()) {
270
            throw new RuntimeException('The stream is invalid.');
271
        }
272
273
        $contents = stream_get_contents($this->stream);
274
275
        if (!is_string($contents)) {
0 ignored issues
show
introduced by
The condition is_string($contents) is always true.
Loading history...
276
            throw new RuntimeException('Failed to read remaining stream contents.');
277
        }
278
279
        return $contents;
280
    }
281
282
    /**
283
     * @inheritDoc
284
     */
285
    public function __toString()
286
    {
287
        try {
288
            $this->rewind();
289
            return $this->getContents();
290
        } catch (Exception $e) {
291
            // Do not raise any exceptions!
292
            return '';
293
        }
294
    }
295
}
296