ILIAS  release_8 Revision v8.24
Stream.php
Go to the documentation of this file.
1<?php
2
3declare(strict_types=1);
4
6
8
9/******************************************************************************
10 *
11 * This file is part of ILIAS, a powerful learning management system.
12 *
13 * ILIAS is licensed with the GPL-3.0, you should have received a copy
14 * of said license along with the source code.
15 *
16 * If this is not the case or you just want to try ILIAS, you'll find
17 * us at:
18 * https://www.ilias.de
19 * https://github.com/ILIAS-eLearning
20 *
21 *****************************************************************************/
32class Stream implements FileStream
33{
34 public const MASK_ACCESS_READ = 01;
35 public const MASK_ACCESS_WRITE = 02;
36 public const MASK_ACCESS_READ_WRITE = 03;
37
38 private static array $accessMap = [
59 ];
60
61 private bool $readable;
62 private bool $writeable;
63 private bool $seekable;
67 private $stream;
68 private ?int $size = null;
69 private ?string $uri = null;
73 private array $customMetadata;
74
75
82 public function __construct($stream, StreamOptions $options = null)
83 {
84 if (!is_resource($stream)) {
85 throw new \InvalidArgumentException('Stream must be a valid resource but "' . gettype($stream) . '" was given.');
86 }
87
88 if ($options !== null) {
89 $this->customMetadata = $options->getMetadata();
90 $this->size = ($options->getSize() !== -1) ? $options->getSize() : null;
91 } else {
92 $this->customMetadata = [];
93 }
94
95 $this->stream = $stream;
96
97 $meta = stream_get_meta_data($this->stream);
98 $mode = $meta['mode'];
99
100 $this->readable = array_key_exists($mode, self::$accessMap) && boolval(self::$accessMap[$mode] & self::MASK_ACCESS_READ);
101 $this->writeable = array_key_exists($mode, self::$accessMap) && boolval(self::$accessMap[$mode] & self::MASK_ACCESS_WRITE);
102 $this->seekable = boolval($meta['seekable']);
103 $this->uri = $this->getMetadata('uri');
104 }
105
106
110 public function close(): void
111 {
112 if (is_resource($this->stream)) {
113 PHPStreamFunctions::fclose($this->stream);
114 }
115
116 $this->detach();
117 }
118
119
123 public function detach()
124 {
126 $this->stream = $this->size = $this->uri = null;
127
128 return $stream;
129 }
130
131
135 public function getSize(): ?int
136 {
137 //check if we know the size
138 if ($this->size !== null) {
139 return $this->size;
140 }
141
142 //check if stream is detached
143 if ($this->stream === null) {
144 return null;
145 }
146
147 //clear stat cache if we got a uri (indicates that we have a file resource)
148 if ($this->uri !== null) {
149 clearstatcache(true, $this->uri);
150 }
151
152 $stats = fstat($this->stream);
153 if (array_key_exists('size', $stats)) {
154 $this->size = $stats['size'];
155 return $this->size;
156 }
157
158 //unable to determine stream size
159 return null;
160 }
161
162
166 public function tell()
167 {
168 $this->assertStreamAttached();
169
170 $result = PHPStreamFunctions::ftell($this->stream);
171
172 if ($result === false) {
173 throw new \RuntimeException('Unable to determine stream position');
174 }
175
176 return $result;
177 }
178
179
183 public function eof(): bool
184 {
185 $this->assertStreamAttached();
186
187 return feof($this->stream);
188 }
189
190
194 public function isSeekable(): bool
195 {
196 return $this->seekable;
197 }
198
199
203 public function seek($offset, $whence = SEEK_SET): void
204 {
205 $this->assertStreamAttached();
206
207 if (!$this->isSeekable()) {
208 throw new \RuntimeException('Stream is not seekable');
209 }
210
211 if (PHPStreamFunctions::fseek($this->stream, $offset, $whence) === -1) {
212 throw new \RuntimeException("Unable to seek to stream position \"$offset\" with whence \"$whence\"");
213 }
214 }
215
216
220 public function rewind(): void
221 {
222 $this->seek(0);
223 }
224
225
229 public function isWritable(): bool
230 {
231 return $this->writeable;
232 }
233
234
238 public function write($string)
239 {
240 $this->assertStreamAttached();
241
242 if (!$this->isWritable()) {
243 throw new \RuntimeException('Can not write to a non-writable stream');
244 }
245
246 //we can't know the new size
247 $this->size = null;
248 $result = PHPStreamFunctions::fwrite($this->stream, $string);
249
250 if ($result === false) {
251 throw new \RuntimeException('Unable to write to stream');
252 }
253
254 return $result;
255 }
256
257
261 public function isReadable(): bool
262 {
263 return $this->readable;
264 }
265
266
270 public function read($length)
271 {
272 $this->assertStreamAttached();
273
274 if (!$this->isReadable()) {
275 throw new \RuntimeException('Can not read from non-readable stream');
276 }
277
278 if ($length < 0) {
279 throw new \RuntimeException('Length parameter must not be negative');
280 }
281
282 if ($length === 0) {
283 return '';
284 }
285
286 $junk = PHPStreamFunctions::fread($this->stream, $length);
287 if ($junk === false) {
288 throw new \RuntimeException('Unable to read from stream');
289 }
290
291 return $junk;
292 }
293
294
298 public function getContents()
299 {
300 $this->assertStreamAttached();
301
302 $content = PHPStreamFunctions::stream_get_contents($this->stream);
303
304 if ($content === false) {
305 throw new \RuntimeException('Unable to read stream contents');
306 }
307
308 return $content;
309 }
310
311
315 public function getMetadata($key = null)
316 {
317
318 //return empty array if stream is detached
319 if ($this->stream === null) {
320 return [];
321 }
322
323 //return merged metadata if key is missing
324 if ($key === null) {
325 return array_merge(stream_get_meta_data($this->stream), $this->customMetadata);
326 }
327
328 //return value if key was provided
329
330 //try fetch data from custom metadata
331 if (array_key_exists($key, $this->customMetadata)) {
332 return $this->customMetadata[$key];
333 }
334
335 //try to fetch data from php resource metadata
336 $meta = stream_get_meta_data($this->stream);
337 if (array_key_exists($key, $meta)) {
338 return $meta[$key];
339 }
340
341 //the key was not found in standard and custom metadata.
342 return null;
343 }
344
348 public function __toString()
349 {
350 try {
351 $this->rewind();
352 return strval($this->getContents());
353 } catch (\Exception $ex) {
354 //to string must not throw an error.
355 return '';
356 }
357 }
358
359
363 public function __destruct()
364 {
365
366 //cleanup the resource on object destruction if the stream is not detached.
367 if (!is_null($this->stream)) {
368 $this->close();
369 }
370 }
371
372
379 private function assertStreamAttached(): void
380 {
381 if ($this->stream === null) {
382 throw new \RuntimeException('Stream is detached');
383 }
384 }
385}
getMetadata($key=null)
@inheritDoc
Definition: Stream.php:315
__construct($stream, StreamOptions $options=null)
Stream constructor.
Definition: Stream.php:82
assertStreamAttached()
Checks if the stream is attached to the wrapper.
Definition: Stream.php:379
write($string)
@inheritDoc
Definition: Stream.php:238
read($length)
@inheritDoc
Definition: Stream.php:270
seek($offset, $whence=SEEK_SET)
@inheritDoc
Definition: Stream.php:203
static stream_get_contents($handle, $length=-1)
static fseek($stream, int $offset, int $whence)
static fwrite($handle, string $string, ?int $length=null)
string $key
Consumer key/client ID value.
Definition: System.php:193