ILIAS  trunk Revision v11.0_alpha-1753-gb21ca8c4367
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
Stream.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
21 namespace ILIAS\Filesystem\Stream;
22 
24 
29 class 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 = [
36  'r' => self::MASK_ACCESS_READ,
37  'w+' => self::MASK_ACCESS_READ_WRITE,
38  'r+' => self::MASK_ACCESS_READ_WRITE,
39  'x+' => self::MASK_ACCESS_READ_WRITE,
40  'c+' => self::MASK_ACCESS_READ_WRITE,
41  'rb' => self::MASK_ACCESS_READ,
42  'w+b' => self::MASK_ACCESS_READ_WRITE,
43  'r+b' => self::MASK_ACCESS_READ_WRITE,
44  'x+b' => self::MASK_ACCESS_READ_WRITE,
45  'c+b' => self::MASK_ACCESS_READ_WRITE,
46  'rt' => self::MASK_ACCESS_READ,
47  'w+t' => self::MASK_ACCESS_READ_WRITE,
48  'r+t' => self::MASK_ACCESS_READ_WRITE,
49  'x+t' => self::MASK_ACCESS_READ_WRITE,
50  'c+t' => self::MASK_ACCESS_READ_WRITE,
51  'a+' => self::MASK_ACCESS_READ_WRITE,
52  'w' => self::MASK_ACCESS_WRITE,
53  'rw' => self::MASK_ACCESS_WRITE,
54  'wb' => self::MASK_ACCESS_WRITE,
55  'a' => self::MASK_ACCESS_WRITE
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 }
static fseek($stream, int $offset, int $whence)
seek($offset, $whence=SEEK_SET)
Definition: Stream.php:201
assertStreamAttached()
Checks if the stream is attached to the wrapper.
Definition: Stream.php:366
static array string $_mode
Definition: Stream.php:57
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: FileStream.php:19
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
static fwrite($handle, string $string, ?int $length=null)
static stream_get_contents($handle, $length=-1)
__construct($stream, ?StreamOptions $options=null)
Stream constructor.
Definition: Stream.php:79
The base interface for all filesystem streams.
Definition: FileStream.php:31
The streaming options are used by the stream implementation.