ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
Stream.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
24
29class Stream implements FileStream, \Stringable
30{
31 public const MASK_ACCESS_READ = 01;
32 public const MASK_ACCESS_WRITE = 02;
33 public const MASK_ACCESS_READ_WRITE = 03;
34
35 protected static array $accessMap = [
56 ];
57 protected ?string $_mode = null;
58
59 protected bool $readable;
60 protected bool $writeable;
61 protected bool $seekable;
65 protected $stream;
66 protected ?int $size = null;
67 protected ?string $uri = null;
71 protected array $customMetadata;
72
79 public function __construct($stream, ?StreamOptions $options = null)
80 {
81 if (!is_resource($stream)) {
82 throw new \InvalidArgumentException(
83 'Stream must be a valid resource but "' . gettype($stream) . '" was given.'
84 );
85 }
86
87 if ($options !== null) {
88 $this->customMetadata = $options->getMetadata();
89 $this->size = ($options->getSize() !== -1) ? $options->getSize() : null;
90 } else {
91 $this->customMetadata = [];
92 }
93
94 $this->stream = $stream;
95
96 $meta = stream_get_meta_data($this->stream);
97 $this->_mode = $mode = $meta['mode'];
98
99 $this->readable = array_key_exists(
100 $mode,
101 self::$accessMap
102 ) && (bool) (self::$accessMap[$mode] & self::MASK_ACCESS_READ);
103 $this->writeable = array_key_exists(
104 $mode,
105 self::$accessMap
106 ) && (bool) (self::$accessMap[$mode] & self::MASK_ACCESS_WRITE);
107 $this->seekable = $meta['seekable'];
108 $this->uri = $this->getMetadata('uri');
109 }
110
114 public function close(): void
115 {
116 if (is_resource($this->stream)) {
117 PHPStreamFunctions::fclose($this->stream);
118 }
119
120 $this->detach();
121 }
122
126 public function detach()
127 {
129 $this->stream = $this->size = $this->uri = null;
130
131 return $stream;
132 }
133
137 public function getSize(): ?int
138 {
139 //check if we know the size
140 if ($this->size !== null) {
141 return $this->size;
142 }
143
144 //check if stream is detached
145 if ($this->stream === null) {
146 return null;
147 }
148
149 //clear stat cache if we got a uri (indicates that we have a file resource)
150 if ($this->uri !== null) {
151 clearstatcache(true, $this->uri);
152 }
153
154 $stats = fstat($this->stream) ?: [];
155 if (array_key_exists('size', $stats)) {
156 $this->size = $stats['size'];
157 return $this->size;
158 }
159
160 //unable to determine stream size
161 return null;
162 }
163
167 public function tell(): int
168 {
169 $this->assertStreamAttached();
170
171 $result = PHPStreamFunctions::ftell($this->stream);
172
173 if ($result === false) {
174 throw new \RuntimeException('Unable to determine stream position');
175 }
176
177 return $result;
178 }
179
183 public function eof(): bool
184 {
185 $this->assertStreamAttached();
186
187 return feof($this->stream);
188 }
189
193 public function isSeekable(): bool
194 {
195 return $this->seekable;
196 }
197
201 public function seek($offset, $whence = SEEK_SET): void
202 {
203 $this->assertStreamAttached();
204
205 if (!$this->isSeekable()) {
206 throw new \RuntimeException('Stream is not seekable');
207 }
208
209 if (PHPStreamFunctions::fseek($this->stream, $offset, $whence) === -1) {
210 throw new \RuntimeException("Unable to seek to stream position \"$offset\" with whence \"$whence\"");
211 }
212 }
213
217 public function rewind(): void
218 {
219 $this->seek(0);
220 }
221
225 public function isWritable(): bool
226 {
227 return $this->writeable;
228 }
229
233 public function write($string): int
234 {
235 $this->assertStreamAttached();
236
237 if (!$this->isWritable()) {
238 throw new \RuntimeException('Can not write to a non-writable stream');
239 }
240
241 //we can't know the new size
242 $this->size = null;
243 $result = PHPStreamFunctions::fwrite($this->stream, $string);
244
245 if ($result === false) {
246 throw new \RuntimeException('Unable to write to stream');
247 }
248
249 return $result;
250 }
251
255 public function isReadable(): bool
256 {
257 return $this->readable;
258 }
259
263 public function read($length): string
264 {
265 $this->assertStreamAttached();
266
267 if (!$this->isReadable()) {
268 throw new \RuntimeException('Can not read from non-readable stream');
269 }
270
271 if ($length < 0) {
272 throw new \RuntimeException('Length parameter must not be negative');
273 }
274
275 if ($length === 0) {
276 return '';
277 }
278
279 $junk = PHPStreamFunctions::fread($this->stream, $length);
280 if ($junk === false) {
281 throw new \RuntimeException('Unable to read from stream');
282 }
283
284 return $junk;
285 }
286
290 public function getContents(): string
291 {
292 $this->assertStreamAttached();
293
294 $content = PHPStreamFunctions::stream_get_contents($this->stream);
295
296 if ($content === false) {
297 throw new \RuntimeException('Unable to read stream contents');
298 }
299
300 return $content;
301 }
302
306 public function getMetadata($key = null)
307 {
308 //return empty array if stream is detached
309 if ($this->stream === null) {
310 return [];
311 }
312
313 //return merged metadata if key is missing
314 if ($key === null) {
315 return array_merge(stream_get_meta_data($this->stream), $this->customMetadata);
316 }
317
318 //return value if key was provided
319
320 //try fetch data from custom metadata
321 if (array_key_exists($key, $this->customMetadata)) {
322 return $this->customMetadata[$key];
323 }
324
325 //try to fetch data from php resource metadata
326 $meta = stream_get_meta_data($this->stream);
327 if (array_key_exists($key, $meta)) {
328 return $meta[$key];
329 }
330
331 //the key was not found in standard and custom metadata.
332 return null;
333 }
334
338 public function __toString(): string
339 {
340 try {
341 $this->rewind();
342 return $this->getContents();
343 } catch (\Exception) {
344 //to string must not throw an error.
345 return '';
346 }
347 }
348
352 public function __destruct()
353 {
354 //cleanup the resource on object destruction if the stream is not detached.
355 if (!is_null($this->stream)) {
356 $this->close();
357 }
358 }
359
366 protected function assertStreamAttached(): void
367 {
368 if ($this->stream === null) {
369 throw new \RuntimeException('Stream is detached');
370 }
371 }
372}
The streaming options are used by the stream implementation.
getMetadata($key=null)
@inheritDoc
Definition: Stream.php:306
__construct($stream, ?StreamOptions $options=null)
Stream constructor.
Definition: Stream.php:79
assertStreamAttached()
Checks if the stream is attached to the wrapper.
Definition: Stream.php:366
write($string)
@inheritDoc
Definition: Stream.php:233
read($length)
@inheritDoc
Definition: Stream.php:263
seek($offset, $whence=SEEK_SET)
@inheritDoc
Definition: Stream.php:201
The purpose of this class is to wrap all stream handling php functions.
static stream_get_contents($handle, $length=-1)
static fseek($stream, int $offset, int $whence)
static fwrite($handle, string $string, ?int $length=null)
The base interface for all filesystem streams.
Definition: FileStream.php:32
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: FileStream.php:19