Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace GuzzleHttp\Psr7;
6
 
7
use Psr\Http\Message\StreamInterface;
8
 
9
/**
10
 * PHP stream implementation.
11
 */
12
class Stream implements StreamInterface
13
{
14
    /**
1441 ariadna 15
     * @see https://www.php.net/manual/en/function.fopen.php
16
     * @see https://www.php.net/manual/en/function.gzopen.php
1 efrain 17
     */
18
    private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
19
    private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';
20
 
21
    /** @var resource */
22
    private $stream;
23
    /** @var int|null */
24
    private $size;
25
    /** @var bool */
26
    private $seekable;
27
    /** @var bool */
28
    private $readable;
29
    /** @var bool */
30
    private $writable;
31
    /** @var string|null */
32
    private $uri;
33
    /** @var mixed[] */
34
    private $customMetadata;
35
 
36
    /**
37
     * This constructor accepts an associative array of options.
38
     *
39
     * - size: (int) If a read stream would otherwise have an indeterminate
40
     *   size, but the size is known due to foreknowledge, then you can
41
     *   provide that size, in bytes.
42
     * - metadata: (array) Any additional metadata to return when the metadata
43
     *   of the stream is accessed.
44
     *
45
     * @param resource                            $stream  Stream resource to wrap.
46
     * @param array{size?: int, metadata?: array} $options Associative array of options.
47
     *
48
     * @throws \InvalidArgumentException if the stream is not a stream resource
49
     */
50
    public function __construct($stream, array $options = [])
51
    {
52
        if (!is_resource($stream)) {
53
            throw new \InvalidArgumentException('Stream must be a resource');
54
        }
55
 
56
        if (isset($options['size'])) {
57
            $this->size = $options['size'];
58
        }
59
 
60
        $this->customMetadata = $options['metadata'] ?? [];
61
        $this->stream = $stream;
62
        $meta = stream_get_meta_data($this->stream);
63
        $this->seekable = $meta['seekable'];
1441 ariadna 64
        $this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
65
        $this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
1 efrain 66
        $this->uri = $this->getMetadata('uri');
67
    }
68
 
69
    /**
70
     * Closes the stream when the destructed
71
     */
72
    public function __destruct()
73
    {
74
        $this->close();
75
    }
76
 
77
    public function __toString(): string
78
    {
79
        try {
80
            if ($this->isSeekable()) {
81
                $this->seek(0);
82
            }
1441 ariadna 83
 
1 efrain 84
            return $this->getContents();
85
        } catch (\Throwable $e) {
86
            if (\PHP_VERSION_ID >= 70400) {
87
                throw $e;
88
            }
89
            trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
1441 ariadna 90
 
1 efrain 91
            return '';
92
        }
93
    }
94
 
95
    public function getContents(): string
96
    {
97
        if (!isset($this->stream)) {
98
            throw new \RuntimeException('Stream is detached');
99
        }
100
 
101
        if (!$this->readable) {
102
            throw new \RuntimeException('Cannot read from non-readable stream');
103
        }
104
 
105
        return Utils::tryGetContents($this->stream);
106
    }
107
 
108
    public function close(): void
109
    {
110
        if (isset($this->stream)) {
111
            if (is_resource($this->stream)) {
112
                fclose($this->stream);
113
            }
114
            $this->detach();
115
        }
116
    }
117
 
118
    public function detach()
119
    {
120
        if (!isset($this->stream)) {
121
            return null;
122
        }
123
 
124
        $result = $this->stream;
125
        unset($this->stream);
126
        $this->size = $this->uri = null;
127
        $this->readable = $this->writable = $this->seekable = false;
128
 
129
        return $result;
130
    }
131
 
132
    public function getSize(): ?int
133
    {
134
        if ($this->size !== null) {
135
            return $this->size;
136
        }
137
 
138
        if (!isset($this->stream)) {
139
            return null;
140
        }
141
 
142
        // Clear the stat cache if the stream has a URI
143
        if ($this->uri) {
144
            clearstatcache(true, $this->uri);
145
        }
146
 
147
        $stats = fstat($this->stream);
148
        if (is_array($stats) && isset($stats['size'])) {
149
            $this->size = $stats['size'];
1441 ariadna 150
 
1 efrain 151
            return $this->size;
152
        }
153
 
154
        return null;
155
    }
156
 
157
    public function isReadable(): bool
158
    {
159
        return $this->readable;
160
    }
161
 
162
    public function isWritable(): bool
163
    {
164
        return $this->writable;
165
    }
166
 
167
    public function isSeekable(): bool
168
    {
169
        return $this->seekable;
170
    }
171
 
172
    public function eof(): bool
173
    {
174
        if (!isset($this->stream)) {
175
            throw new \RuntimeException('Stream is detached');
176
        }
177
 
178
        return feof($this->stream);
179
    }
180
 
181
    public function tell(): int
182
    {
183
        if (!isset($this->stream)) {
184
            throw new \RuntimeException('Stream is detached');
185
        }
186
 
187
        $result = ftell($this->stream);
188
 
189
        if ($result === false) {
190
            throw new \RuntimeException('Unable to determine stream position');
191
        }
192
 
193
        return $result;
194
    }
195
 
196
    public function rewind(): void
197
    {
198
        $this->seek(0);
199
    }
200
 
201
    public function seek($offset, $whence = SEEK_SET): void
202
    {
203
        $whence = (int) $whence;
204
 
205
        if (!isset($this->stream)) {
206
            throw new \RuntimeException('Stream is detached');
207
        }
208
        if (!$this->seekable) {
209
            throw new \RuntimeException('Stream is not seekable');
210
        }
211
        if (fseek($this->stream, $offset, $whence) === -1) {
212
            throw new \RuntimeException('Unable to seek to stream position '
1441 ariadna 213
                .$offset.' with whence '.var_export($whence, true));
1 efrain 214
        }
215
    }
216
 
217
    public function read($length): string
218
    {
219
        if (!isset($this->stream)) {
220
            throw new \RuntimeException('Stream is detached');
221
        }
222
        if (!$this->readable) {
223
            throw new \RuntimeException('Cannot read from non-readable stream');
224
        }
225
        if ($length < 0) {
226
            throw new \RuntimeException('Length parameter cannot be negative');
227
        }
228
 
229
        if (0 === $length) {
230
            return '';
231
        }
232
 
233
        try {
234
            $string = fread($this->stream, $length);
235
        } catch (\Exception $e) {
236
            throw new \RuntimeException('Unable to read from stream', 0, $e);
237
        }
238
 
239
        if (false === $string) {
240
            throw new \RuntimeException('Unable to read from stream');
241
        }
242
 
243
        return $string;
244
    }
245
 
246
    public function write($string): int
247
    {
248
        if (!isset($this->stream)) {
249
            throw new \RuntimeException('Stream is detached');
250
        }
251
        if (!$this->writable) {
252
            throw new \RuntimeException('Cannot write to a non-writable stream');
253
        }
254
 
255
        // We can't know the size after writing anything
256
        $this->size = null;
257
        $result = fwrite($this->stream, $string);
258
 
259
        if ($result === false) {
260
            throw new \RuntimeException('Unable to write to stream');
261
        }
262
 
263
        return $result;
264
    }
265
 
266
    /**
267
     * @return mixed
268
     */
269
    public function getMetadata($key = null)
270
    {
271
        if (!isset($this->stream)) {
272
            return $key ? null : [];
273
        } elseif (!$key) {
274
            return $this->customMetadata + stream_get_meta_data($this->stream);
275
        } elseif (isset($this->customMetadata[$key])) {
276
            return $this->customMetadata[$key];
277
        }
278
 
279
        $meta = stream_get_meta_data($this->stream);
280
 
281
        return $meta[$key] ?? null;
282
    }
283
}