Stream::__destruct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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