ILIAS  release_8 Revision v8.23
Stream.php
Go to the documentation of this file.
1 <?php
2 
3 declare(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  *****************************************************************************/
32 class 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 = [
39  'r' => self::MASK_ACCESS_READ,
40  'w+' => self::MASK_ACCESS_READ_WRITE,
41  'r+' => self::MASK_ACCESS_READ_WRITE,
42  'x+' => self::MASK_ACCESS_READ_WRITE,
43  'c+' => self::MASK_ACCESS_READ_WRITE,
44  'rb' => self::MASK_ACCESS_READ,
45  'w+b' => self::MASK_ACCESS_READ_WRITE,
46  'r+b' => self::MASK_ACCESS_READ_WRITE,
47  'x+b' => self::MASK_ACCESS_READ_WRITE,
48  'c+b' => self::MASK_ACCESS_READ_WRITE,
49  'rt' => self::MASK_ACCESS_READ,
50  'w+t' => self::MASK_ACCESS_READ_WRITE,
51  'r+t' => self::MASK_ACCESS_READ_WRITE,
52  'x+t' => self::MASK_ACCESS_READ_WRITE,
53  'c+t' => self::MASK_ACCESS_READ_WRITE,
54  'a+' => self::MASK_ACCESS_READ_WRITE,
55  'w' => self::MASK_ACCESS_WRITE,
56  'rw' => self::MASK_ACCESS_WRITE,
57  'wb' => self::MASK_ACCESS_WRITE,
58  'a' => self::MASK_ACCESS_WRITE
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 }
static fseek($stream, int $offset, int $whence)
seek($offset, $whence=SEEK_SET)
Definition: Stream.php:203
assertStreamAttached()
Checks if the stream is attached to the wrapper.
Definition: Stream.php:379
static array bool $readable
Definition: Stream.php:40
__construct($stream, StreamOptions $options=null)
Stream constructor.
Definition: Stream.php:82
string $key
Consumer key/client ID value.
Definition: System.php:193
static fwrite($handle, string $string, ?int $length=null)
static stream_get_contents($handle, $length=-1)
Interface FileStream.
Definition: FileStream.php:33