Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace ZipStream;
   6  
   7  use function mb_strlen;
   8  
   9  use Psr\Http\Message\StreamInterface;
  10  use RuntimeException;
  11  
  12  /**
  13   * Describes a data stream.
  14   *
  15   * Typically, an instance will wrap a PHP stream; this interface provides
  16   * a wrapper around the most common operations, including serialization of
  17   * the entire stream to a string.
  18   */
  19  class Stream implements StreamInterface
  20  {
  21      protected $stream;
  22  
  23      public function __construct($stream)
  24      {
  25          $this->stream = $stream;
  26      }
  27  
  28      /**
  29       * Reads all data from the stream into a string, from the beginning to end.
  30       *
  31       * This method MUST attempt to seek to the beginning of the stream before
  32       * reading data and read the stream until the end is reached.
  33       *
  34       * Warning: This could attempt to load a large amount of data into memory.
  35       *
  36       * This method MUST NOT raise an exception in order to conform with PHP's
  37       * string casting operations.
  38       *
  39       * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
  40       * @return string
  41       */
  42      public function __toString(): string
  43      {
  44          try {
  45              $this->seek(0);
  46          } catch (RuntimeException $e) {
  47          }
  48          return (string) stream_get_contents($this->stream);
  49      }
  50  
  51      /**
  52       * Closes the stream and any underlying resources.
  53       *
  54       * @return void
  55       */
  56      public function close(): void
  57      {
  58          if (is_resource($this->stream)) {
  59              fclose($this->stream);
  60          }
  61          $this->detach();
  62      }
  63  
  64      /**
  65       * Separates any underlying resources from the stream.
  66       *
  67       * After the stream has been detached, the stream is in an unusable state.
  68       *
  69       * @return resource|null Underlying PHP stream, if any
  70       */
  71      public function detach()
  72      {
  73          $result = $this->stream;
  74          $this->stream = null;
  75          return $result;
  76      }
  77  
  78      /**
  79       * Seek to a position in the stream.
  80       *
  81       * @link http://www.php.net/manual/en/function.fseek.php
  82       * @param int $offset Stream offset
  83       * @param int $whence Specifies how the cursor position will be calculated
  84       *     based on the seek offset. Valid values are identical to the built-in
  85       *     PHP $whence values for `fseek()`.  SEEK_SET: Set position equal to
  86       *     offset bytes SEEK_CUR: Set position to current location plus offset
  87       *     SEEK_END: Set position to end-of-stream plus offset.
  88       * @throws RuntimeException on failure.
  89       */
  90      public function seek($offset, $whence = SEEK_SET): void
  91      {
  92          if (!$this->isSeekable()) {
  93              throw new RuntimeException();
  94          }
  95          if (fseek($this->stream, $offset, $whence) !== 0) {
  96              throw new RuntimeException();
  97          }
  98      }
  99  
 100      /**
 101       * Returns whether or not the stream is seekable.
 102       *
 103       * @return bool
 104       */
 105      public function isSeekable(): bool
 106      {
 107          return (bool)$this->getMetadata('seekable');
 108      }
 109  
 110      /**
 111       * Get stream metadata as an associative array or retrieve a specific key.
 112       *
 113       * The keys returned are identical to the keys returned from PHP's
 114       * stream_get_meta_data() function.
 115       *
 116       * @link http://php.net/manual/en/function.stream-get-meta-data.php
 117       * @param string $key Specific metadata to retrieve.
 118       * @return array|mixed|null Returns an associative array if no key is
 119       *     provided. Returns a specific key value if a key is provided and the
 120       *     value is found, or null if the key is not found.
 121       */
 122      public function getMetadata($key = null)
 123      {
 124          $metadata = stream_get_meta_data($this->stream);
 125          return $key !== null ? @$metadata[$key] : $metadata;
 126      }
 127  
 128      /**
 129       * Get the size of the stream if known.
 130       *
 131       * @return int|null Returns the size in bytes if known, or null if unknown.
 132       */
 133      public function getSize(): ?int
 134      {
 135          $stats = fstat($this->stream);
 136          return $stats['size'];
 137      }
 138  
 139      /**
 140       * Returns the current position of the file read/write pointer
 141       *
 142       * @return int Position of the file pointer
 143       * @throws RuntimeException on error.
 144       */
 145      public function tell(): int
 146      {
 147          $position = ftell($this->stream);
 148          if ($position === false) {
 149              throw new RuntimeException();
 150          }
 151          return $position;
 152      }
 153  
 154      /**
 155       * Returns true if the stream is at the end of the stream.
 156       *
 157       * @return bool
 158       */
 159      public function eof(): bool
 160      {
 161          return feof($this->stream);
 162      }
 163  
 164      /**
 165       * Seek to the beginning of the stream.
 166       *
 167       * If the stream is not seekable, this method will raise an exception;
 168       * otherwise, it will perform a seek(0).
 169       *
 170       * @see seek()
 171       * @link http://www.php.net/manual/en/function.fseek.php
 172       * @throws RuntimeException on failure.
 173       */
 174      public function rewind(): void
 175      {
 176          $this->seek(0);
 177      }
 178  
 179      /**
 180       * Write data to the stream.
 181       *
 182       * @param string $string The string that is to be written.
 183       * @return int Returns the number of bytes written to the stream.
 184       * @throws RuntimeException on failure.
 185       */
 186      public function write($string): int
 187      {
 188          if (!$this->isWritable()) {
 189              throw new RuntimeException();
 190          }
 191          if (fwrite($this->stream, $string) === false) {
 192              throw new RuntimeException();
 193          }
 194          return mb_strlen($string);
 195      }
 196  
 197      /**
 198       * Returns whether or not the stream is writable.
 199       *
 200       * @return bool
 201       */
 202      public function isWritable(): bool
 203      {
 204          $mode = $this->getMetadata('mode');
 205          if (!is_string($mode)) {
 206              throw new RuntimeException('Could not get stream mode from metadata!');
 207          }
 208          return preg_match('/[waxc+]/', $mode) === 1;
 209      }
 210  
 211      /**
 212       * Read data from the stream.
 213       *
 214       * @param int $length Read up to $length bytes from the object and return
 215       *     them. Fewer than $length bytes may be returned if underlying stream
 216       *     call returns fewer bytes.
 217       * @return string Returns the data read from the stream, or an empty string
 218       *     if no bytes are available.
 219       * @throws RuntimeException if an error occurs.
 220       */
 221      public function read($length): string
 222      {
 223          if (!$this->isReadable()) {
 224              throw new RuntimeException();
 225          }
 226          $result = fread($this->stream, $length);
 227          if ($result === false) {
 228              throw new RuntimeException();
 229          }
 230          return $result;
 231      }
 232  
 233      /**
 234       * Returns whether or not the stream is readable.
 235       *
 236       * @return bool
 237       */
 238      public function isReadable(): bool
 239      {
 240          $mode = $this->getMetadata('mode');
 241          if (!is_string($mode)) {
 242              throw new RuntimeException('Could not get stream mode from metadata!');
 243          }
 244          return preg_match('/[r+]/', $mode) === 1;
 245      }
 246  
 247      /**
 248       * Returns the remaining contents in a string
 249       *
 250       * @return string
 251       * @throws RuntimeException if unable to read or an error occurs while
 252       *     reading.
 253       */
 254      public function getContents(): string
 255      {
 256          if (!$this->isReadable()) {
 257              throw new RuntimeException();
 258          }
 259          $result = stream_get_contents($this->stream);
 260          if ($result === false) {
 261              throw new RuntimeException();
 262          }
 263          return $result;
 264      }
 265  }