Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace GuzzleHttp\Psr7;
   6  
   7  use Psr\Http\Message\StreamInterface;
   8  
   9  /**
  10   * Compose stream implementations based on a hash of functions.
  11   *
  12   * Allows for easy testing and extension of a provided stream without needing
  13   * to create a concrete class for a simple extension point.
  14   */
  15  #[\AllowDynamicProperties]
  16  final class FnStream implements StreamInterface
  17  {
  18      private const SLOTS = [
  19          '__toString', 'close', 'detach', 'rewind',
  20          'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
  21          'isReadable', 'read', 'getContents', 'getMetadata'
  22      ];
  23  
  24      /** @var array<string, callable> */
  25      private $methods;
  26  
  27      /**
  28       * @param array<string, callable> $methods Hash of method name to a callable.
  29       */
  30      public function __construct(array $methods)
  31      {
  32          $this->methods = $methods;
  33  
  34          // Create the functions on the class
  35          foreach ($methods as $name => $fn) {
  36              $this->{'_fn_' . $name} = $fn;
  37          }
  38      }
  39  
  40      /**
  41       * Lazily determine which methods are not implemented.
  42       *
  43       * @throws \BadMethodCallException
  44       */
  45      public function __get(string $name): void
  46      {
  47          throw new \BadMethodCallException(str_replace('_fn_', '', $name)
  48              . '() is not implemented in the FnStream');
  49      }
  50  
  51      /**
  52       * The close method is called on the underlying stream only if possible.
  53       */
  54      public function __destruct()
  55      {
  56          if (isset($this->_fn_close)) {
  57              call_user_func($this->_fn_close);
  58          }
  59      }
  60  
  61      /**
  62       * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
  63       *
  64       * @throws \LogicException
  65       */
  66      public function __wakeup(): void
  67      {
  68          throw new \LogicException('FnStream should never be unserialized');
  69      }
  70  
  71      /**
  72       * Adds custom functionality to an underlying stream by intercepting
  73       * specific method calls.
  74       *
  75       * @param StreamInterface         $stream  Stream to decorate
  76       * @param array<string, callable> $methods Hash of method name to a closure
  77       *
  78       * @return FnStream
  79       */
  80      public static function decorate(StreamInterface $stream, array $methods)
  81      {
  82          // If any of the required methods were not provided, then simply
  83          // proxy to the decorated stream.
  84          foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) {
  85              /** @var callable $callable */
  86              $callable = [$stream, $diff];
  87              $methods[$diff] = $callable;
  88          }
  89  
  90          return new self($methods);
  91      }
  92  
  93      public function __toString(): string
  94      {
  95          try {
  96              return call_user_func($this->_fn___toString);
  97          } catch (\Throwable $e) {
  98              if (\PHP_VERSION_ID >= 70400) {
  99                  throw $e;
 100              }
 101              trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
 102              return '';
 103          }
 104      }
 105  
 106      public function close(): void
 107      {
 108          call_user_func($this->_fn_close);
 109      }
 110  
 111      public function detach()
 112      {
 113          return call_user_func($this->_fn_detach);
 114      }
 115  
 116      public function getSize(): ?int
 117      {
 118          return call_user_func($this->_fn_getSize);
 119      }
 120  
 121      public function tell(): int
 122      {
 123          return call_user_func($this->_fn_tell);
 124      }
 125  
 126      public function eof(): bool
 127      {
 128          return call_user_func($this->_fn_eof);
 129      }
 130  
 131      public function isSeekable(): bool
 132      {
 133          return call_user_func($this->_fn_isSeekable);
 134      }
 135  
 136      public function rewind(): void
 137      {
 138          call_user_func($this->_fn_rewind);
 139      }
 140  
 141      public function seek($offset, $whence = SEEK_SET): void
 142      {
 143          call_user_func($this->_fn_seek, $offset, $whence);
 144      }
 145  
 146      public function isWritable(): bool
 147      {
 148          return call_user_func($this->_fn_isWritable);
 149      }
 150  
 151      public function write($string): int
 152      {
 153          return call_user_func($this->_fn_write, $string);
 154      }
 155  
 156      public function isReadable(): bool
 157      {
 158          return call_user_func($this->_fn_isReadable);
 159      }
 160  
 161      public function read($length): string
 162      {
 163          return call_user_func($this->_fn_read, $length);
 164      }
 165  
 166      public function getContents(): string
 167      {
 168          return call_user_func($this->_fn_getContents);
 169      }
 170  
 171      /**
 172       * {@inheritdoc}
 173       *
 174       * @return mixed
 175       */
 176      public function getMetadata($key = null)
 177      {
 178          return call_user_func($this->_fn_getMetadata, $key);
 179      }
 180  }