Completed
Push — master ( c22a1a...c65aed )
by
unknown
02:00
created

Stream::tell()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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