Passed
Push — master ( 4ac306...d2aa89 )
by Mihail
05:30
created

Stream::getMetadata()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 2
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use Koded\Http\Interfaces\HttpStatus;
16
use Psr\Http\Message\StreamInterface;
17
use RuntimeException;
18
use Throwable;
19
20
21
class Stream implements StreamInterface
22
{
23
    protected const MODES = [
24
        'w+'  => 1,
25
        'r+'  => 1,
26
        'x+'  => 1,
27
        'c+'  => 1,
28
        'a+'  => 1,
29
        'w+b' => 1,
30
        'r+b' => 1,
31
        'x+b' => 1,
32
        'c+b' => 1,
33
        'w+t' => 1,
34
        'r+t' => 1,
35
        'x+t' => 1,
36
        'c+t' => 1
37
    ];
38
39
    /** @var resource The underlying stream resource */
40
    protected $stream;
41
    protected $mode     = 'w+b';
42
    protected $seekable = false;
43
44 210
    public function __construct($stream)
45
    {
46 210
        if (false === is_resource($stream) || 'stream' !== get_resource_type($stream)) {
47 1
            throw new RuntimeException(
48 1
                'The provided resource is not a valid stream resource, ' . gettype($stream) . ' given.',
49 1
                HttpStatus::UNPROCESSABLE_ENTITY
50
            );
51
        }
52
53 209
        $metadata       = stream_get_meta_data($stream);
54 209
        $this->mode     = $metadata['mode'] ?? 'w+b';
55 209
        $this->seekable = $metadata['seekable'] ?? false;
56 209
        $this->stream   = $stream;
57 209
    }
58
59 142
    public function __destruct()
60
    {
61 142
        $this->close();
62 142
    }
63
64 30
    public function __toString()
65
    {
66
        try {
67 30
            $this->seek(0);
68
69 29
            return $this->getContents();
70 1
        } catch (Throwable $e) {
71 1
            return '';
72
        }
73
    }
74
75 142
    public function close(): void
76
    {
77 142
        if ($this->stream) {
78 139
            fclose($this->stream);
79 139
            $this->detach();
80
        }
81 142
    }
82
83 142
    public function detach()
84
    {
85 142
        if (empty($this->stream)) {
86 1
            return null;
87
        }
88
89 142
        $resource       = $this->stream;
90 142
        $this->stream   = null;
91 142
        $this->mode     = 'w+b';
92 142
        $this->seekable = false;
93
94 142
        return $resource;
95
    }
96
97 36
    public function getSize(): ?int
98
    {
99 36
        if (empty($this->stream)) {
100 1
            return null;
101
        }
102
103 35
        return fstat($this->stream)['size'] ?? null;
104
    }
105
106 4
    public function tell(): int
107
    {
108 4
        if (false === $position = ftell($this->stream)) {
109 1
            throw new RuntimeException('Failed to find the position of the file pointer');
110
        }
111
112 3
        return $position;
113
    }
114
115 6
    public function eof(): bool
116
    {
117 6
        return feof($this->stream);
118
    }
119
120 51
    public function seek($offset, $whence = SEEK_SET): void
121
    {
122 51
        if (0 !== @fseek($this->stream, $offset, $whence)) {
123 3
            throw new RuntimeException('Failed to seek to file pointer');
124
        }
125 48
    }
126
127 16
    public function rewind(): void
128
    {
129 16
        if (false === $this->seekable) {
130 2
            throw new RuntimeException('The stream is not seekable');
131
        }
132
133 14
        $this->seek(0);
134 14
    }
135
136 8
    public function write($string): int
137
    {
138 8
        if (false === $this->isWritable()) {
139 1
            throw new RuntimeException('The stream is not writable');
140
        }
141
142 7
        if (false === $bytes = fwrite($this->stream, $string)) {
143
            throw new RuntimeException('Failed to write data to the stream');
144
        }
145
146 7
        return $bytes;
147
    }
148
149 9
    public function read($length): string
150
    {
151 9
        if (false === $this->isReadable()) {
152 2
            throw new RuntimeException('The stream is not readable');
153
        }
154
155 7
        if (empty($length)) {
156 1
            return '';
157
        }
158
159 6
        if (false === $data = fread($this->stream, $length)) {
160
            throw new RuntimeException('Failed to read the data from stream');
161
        }
162
163 6
        return $data;
164
    }
165
166 48
    public function getContents(): string
167
    {
168 48
        if (false === $content = stream_get_contents($this->stream)) {
169
            throw new RuntimeException('Unable to read the stream content');
170
        }
171
172 48
        return $content;
173
    }
174
175 4
    public function getMetadata($key = null)
176
    {
177 4
        $metadata = stream_get_meta_data($this->stream);
178
179 4
        if (null === $key) {
180 1
            return $metadata;
181
        }
182
183 4
        return $metadata[$key] ?? null;
184
    }
185
186 7
    public function isSeekable(): bool
187
    {
188 7
        return $this->seekable;
189
    }
190
191 15
    public function isReadable(): bool
192
    {
193 15
        return isset((self::MODES + ['r' => 1, 'rb' => 1, 'rt' => 1])[$this->mode]);
194
    }
195
196 14
    public function isWritable(): bool
197
    {
198 14
        return isset((self::MODES + ['w' => 1, 'rw' => 1, 'a' => 1, 'wb' => 1])[$this->mode]);
199
    }
200
}
201