ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
Stream.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types=1);
3 
5 
7 
15 class Stream implements FileStream
16 {
17  const MASK_ACCESS_READ = 01;
18  const MASK_ACCESS_WRITE = 02;
20 
21  private static $accessMap = [
22  'r' => self::MASK_ACCESS_READ,
23  'w+' => self::MASK_ACCESS_READ_WRITE,
24  'r+' => self::MASK_ACCESS_READ_WRITE,
25  'x+' => self::MASK_ACCESS_READ_WRITE,
26  'c+' => self::MASK_ACCESS_READ_WRITE,
27  'rb' => self::MASK_ACCESS_READ,
28  'w+b' => self::MASK_ACCESS_READ_WRITE,
29  'r+b' => self::MASK_ACCESS_READ_WRITE,
30  'x+b' => self::MASK_ACCESS_READ_WRITE,
31  'c+b' => self::MASK_ACCESS_READ_WRITE,
32  'rt' => self::MASK_ACCESS_READ,
33  'w+t' => self::MASK_ACCESS_READ_WRITE,
34  'r+t' => self::MASK_ACCESS_READ_WRITE,
35  'x+t' => self::MASK_ACCESS_READ_WRITE,
36  'c+t' => self::MASK_ACCESS_READ_WRITE,
37  'a+' => self::MASK_ACCESS_READ_WRITE,
38  'w' => self::MASK_ACCESS_WRITE,
39  'rw' => self::MASK_ACCESS_WRITE,
40  'wb' => self::MASK_ACCESS_WRITE,
41  'a' => self::MASK_ACCESS_WRITE
42  ];
43 
47  private $readable;
51  private $writeable;
55  private $seekable;
59  private $stream;
63  private $size;
67  private $uri;
71  private $customMetadata;
72 
78  public function __construct($stream, StreamOptions $options = null)
79  {
80  if (!is_resource($stream)) {
81  throw new \InvalidArgumentException('Stream must be a valid resource but "' . gettype($stream) . '" was given.');
82  }
83 
84  if ($options !== null) {
85  $this->customMetadata = $options->getMetadata();
86  $this->size = ($options->getSize() !== -1) ? $options->getSize() : null;
87  } else {
88  $this->customMetadata = [];
89  }
90 
91  $this->stream = $stream;
92 
93  $meta = stream_get_meta_data($this->stream);
94  $mode = $meta['mode'];
95 
96  $this->readable = array_key_exists($mode,
97  self::$accessMap) && boolval(self::$accessMap[$mode]&self::MASK_ACCESS_READ);
98  $this->writeable = array_key_exists($mode,
99  self::$accessMap) && boolval(self::$accessMap[$mode]&self::MASK_ACCESS_WRITE);
100  $this->seekable = boolval($meta['seekable']);
101  $this->uri = $this->getMetadata('uri');
102  }
103 
107  public function close()
108  {
109  if ($this->stream !== null && is_resource($this->stream)) {
110  PHPStreamFunctions::fclose($this->stream);
111  }
112 
113  $this->detach();
114  }
115 
119  public function detach()
120  {
122  $this->stream = $this->size = $this->uri = null;
123 
124  return $stream;
125  }
126 
130  public function getSize()
131  {
132 
133  //check if we know the size
134  if ($this->size !== null) {
135  return $this->size;
136  }
137 
138  //check if stream is detached
139  if ($this->stream === null) {
140  return null;
141  }
142 
143  //clear stat cache if we got a uri (indicates that we have a file resource)
144  if ($this->uri !== null) {
145  clearstatcache(true, $this->uri);
146  }
147 
148  $stats = fstat($this->stream);
149  if (array_key_exists('size', $stats)) {
150  $this->size = $stats['size'];
151  return $this->size;
152  }
153 
154  //unable to determine stream size
155  return null;
156  }
157 
161  public function tell()
162  {
163  $this->assertStreamAttached();
164 
165  $result = PHPStreamFunctions::ftell($this->stream);
166 
167  if ($result === false) {
168  throw new \RuntimeException('Unable to determine stream position');
169  }
170 
171  return $result;
172  }
173 
177  public function eof()
178  {
179  $this->assertStreamAttached();
180 
181  return feof($this->stream);
182  }
183 
187  public function isSeekable()
188  {
189  return $this->seekable;
190  }
191 
195  public function seek($offset, $whence = SEEK_SET)
196  {
197  $this->assertStreamAttached();
198 
199  if (!$this->isSeekable()) {
200  throw new \RuntimeException('Stream is not seekable');
201  }
202 
203  if (PHPStreamFunctions::fseek($this->stream, $offset, $whence) === -1) {
204  throw new \RuntimeException("Unable to seek to stream position \"$offset\" with whence \"$whence\"");
205  }
206  }
207 
211  public function rewind()
212  {
213  $this->seek(0);
214  }
215 
219  public function isWritable()
220  {
221  return $this->writeable;
222  }
223 
227  public function write($string)
228  {
229  $this->assertStreamAttached();
230 
231  if (!$this->isWritable()) {
232  throw new \RuntimeException('Can not write to a non-writable stream');
233  }
234 
235  //we can't know the new size
236  $this->size = null;
237  $result = PHPStreamFunctions::fwrite($this->stream, $string);
238 
239  if ($result === false) {
240  throw new \RuntimeException('Unable to write to stream');
241  }
242 
243  return $result;
244  }
245 
249  public function isReadable()
250  {
251  return $this->readable;
252  }
253 
257  public function read($length)
258  {
259  $this->assertStreamAttached();
260 
261  if (!$this->isReadable()) {
262  throw new \RuntimeException('Can not read from non-readable stream');
263  }
264 
265  if ($length < 0) {
266  throw new \RuntimeException('Length parameter must not be negative');
267  }
268 
269  if ($length === 0) {
270  return '';
271  }
272 
273  $junk = PHPStreamFunctions::fread($this->stream, $length);
274  if ($junk === false) {
275  throw new \RuntimeException('Unable to read from stream');
276  }
277 
278  return $junk;
279  }
280 
284  public function getContents()
285  {
286  $this->assertStreamAttached();
287 
288  $content = PHPStreamFunctions::stream_get_contents($this->stream);
289 
290  if ($content === false) {
291  throw new \RuntimeException('Unable to read stream contents');
292  }
293 
294  return $content;
295  }
296 
300  public function getMetadata($key = null)
301  {
302 
303  //return empty array if stream is detached
304  if ($this->stream === null) {
305  return [];
306  }
307 
308  //return merged metadata if key is missing
309  if ($key === null) {
310  return array_merge(stream_get_meta_data($this->stream), $this->customMetadata);
311  }
312 
313  //return value if key was provided
314 
315  //try fetch data from custom metadata
316  if (array_key_exists($key, $this->customMetadata)) {
317  return $this->customMetadata[$key];
318  }
319 
320  //try to fetch data from php resource metadata
321  $meta = stream_get_meta_data($this->stream);
322  if (array_key_exists($key, $meta)) {
323  return $meta[$key];
324  }
325 
326  //the key was not found in standard and custom metadata.
327  return null;
328  }
329 
333  public function __toString()
334  {
335  try {
336  $this->rewind();
337  return strval($this->getContents());
338  } catch (\Exception $ex) {
339  //to string must not throw an error.
340  return '';
341  }
342  }
343 
347  public function __destruct()
348  {
349 
350  //cleanup the resource on object destruction if the stream is not detached.
351  if (!is_null($this->stream)) {
352  $this->close();
353  }
354  }
355 
361  private function assertStreamAttached()
362  {
363  if ($this->stream === null) {
364  throw new \RuntimeException('Stream is detached');
365  }
366  }
367 }
$result
seek($offset, $whence=SEEK_SET)
Definition: Stream.php:195
assertStreamAttached()
Checks if the stream is attached to the wrapper.
Definition: Stream.php:361
static fseek($stream, $offset, $whence)
fseek wrapper.
static fread($handle, $length)
fread wrapper
static fclose($handle)
fclose wrapper
__construct($stream, StreamOptions $options=null)
Stream constructor.
Definition: Stream.php:78
static fwrite($handle, $string, $length=null)
fwrite wrapper
static stream_get_contents($handle, $length=-1)
stream_get_contents wrapper
Interface FileStream The base interface for all filesystem streams.
Definition: FileStream.php:17
Class StreamOptions The streaming options are used by the stream implementation.