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.
   1  <?php
   2  
   3  namespace GuzzleHttp\Handler;
   4  
   5  use GuzzleHttp\Exception\RequestException;
   6  use GuzzleHttp\HandlerStack;
   7  use GuzzleHttp\Promise as P;
   8  use GuzzleHttp\Promise\PromiseInterface;
   9  use GuzzleHttp\TransferStats;
  10  use GuzzleHttp\Utils;
  11  use Psr\Http\Message\RequestInterface;
  12  use Psr\Http\Message\ResponseInterface;
  13  use Psr\Http\Message\StreamInterface;
  14  
  15  /**
  16   * Handler that returns responses or throw exceptions from a queue.
  17   *
  18   * @final
  19   */
  20  class MockHandler implements \Countable
  21  {
  22      /**
  23       * @var array
  24       */
  25      private $queue = [];
  26  
  27      /**
  28       * @var RequestInterface|null
  29       */
  30      private $lastRequest;
  31  
  32      /**
  33       * @var array
  34       */
  35      private $lastOptions = [];
  36  
  37      /**
  38       * @var callable|null
  39       */
  40      private $onFulfilled;
  41  
  42      /**
  43       * @var callable|null
  44       */
  45      private $onRejected;
  46  
  47      /**
  48       * Creates a new MockHandler that uses the default handler stack list of
  49       * middlewares.
  50       *
  51       * @param array|null    $queue       Array of responses, callables, or exceptions.
  52       * @param callable|null $onFulfilled Callback to invoke when the return value is fulfilled.
  53       * @param callable|null $onRejected  Callback to invoke when the return value is rejected.
  54       */
  55      public static function createWithMiddleware(array $queue = null, callable $onFulfilled = null, callable $onRejected = null): HandlerStack
  56      {
  57          return HandlerStack::create(new self($queue, $onFulfilled, $onRejected));
  58      }
  59  
  60      /**
  61       * The passed in value must be an array of
  62       * {@see \Psr\Http\Message\ResponseInterface} objects, Exceptions,
  63       * callables, or Promises.
  64       *
  65       * @param array<int, mixed>|null $queue       The parameters to be passed to the append function, as an indexed array.
  66       * @param callable|null          $onFulfilled Callback to invoke when the return value is fulfilled.
  67       * @param callable|null          $onRejected  Callback to invoke when the return value is rejected.
  68       */
  69      public function __construct(array $queue = null, callable $onFulfilled = null, callable $onRejected = null)
  70      {
  71          $this->onFulfilled = $onFulfilled;
  72          $this->onRejected = $onRejected;
  73  
  74          if ($queue) {
  75              // array_values included for BC
  76              $this->append(...array_values($queue));
  77          }
  78      }
  79  
  80      public function __invoke(RequestInterface $request, array $options): PromiseInterface
  81      {
  82          if (!$this->queue) {
  83              throw new \OutOfBoundsException('Mock queue is empty');
  84          }
  85  
  86          if (isset($options['delay']) && \is_numeric($options['delay'])) {
  87              \usleep((int) $options['delay'] * 1000);
  88          }
  89  
  90          $this->lastRequest = $request;
  91          $this->lastOptions = $options;
  92          $response = \array_shift($this->queue);
  93  
  94          if (isset($options['on_headers'])) {
  95              if (!\is_callable($options['on_headers'])) {
  96                  throw new \InvalidArgumentException('on_headers must be callable');
  97              }
  98              try {
  99                  $options['on_headers']($response);
 100              } catch (\Exception $e) {
 101                  $msg = 'An error was encountered during the on_headers event';
 102                  $response = new RequestException($msg, $request, $response, $e);
 103              }
 104          }
 105  
 106          if (\is_callable($response)) {
 107              $response = $response($request, $options);
 108          }
 109  
 110          $response = $response instanceof \Throwable
 111              ? P\Create::rejectionFor($response)
 112              : P\Create::promiseFor($response);
 113  
 114          return $response->then(
 115              function (?ResponseInterface $value) use ($request, $options) {
 116                  $this->invokeStats($request, $options, $value);
 117                  if ($this->onFulfilled) {
 118                      ($this->onFulfilled)($value);
 119                  }
 120  
 121                  if ($value !== null && isset($options['sink'])) {
 122                      $contents = (string) $value->getBody();
 123                      $sink = $options['sink'];
 124  
 125                      if (\is_resource($sink)) {
 126                          \fwrite($sink, $contents);
 127                      } elseif (\is_string($sink)) {
 128                          \file_put_contents($sink, $contents);
 129                      } elseif ($sink instanceof StreamInterface) {
 130                          $sink->write($contents);
 131                      }
 132                  }
 133  
 134                  return $value;
 135              },
 136              function ($reason) use ($request, $options) {
 137                  $this->invokeStats($request, $options, null, $reason);
 138                  if ($this->onRejected) {
 139                      ($this->onRejected)($reason);
 140                  }
 141                  return P\Create::rejectionFor($reason);
 142              }
 143          );
 144      }
 145  
 146      /**
 147       * Adds one or more variadic requests, exceptions, callables, or promises
 148       * to the queue.
 149       *
 150       * @param mixed ...$values
 151       */
 152      public function append(...$values): void
 153      {
 154          foreach ($values as $value) {
 155              if ($value instanceof ResponseInterface
 156                  || $value instanceof \Throwable
 157                  || $value instanceof PromiseInterface
 158                  || \is_callable($value)
 159              ) {
 160                  $this->queue[] = $value;
 161              } else {
 162                  throw new \TypeError('Expected a Response, Promise, Throwable or callable. Found ' . Utils::describeType($value));
 163              }
 164          }
 165      }
 166  
 167      /**
 168       * Get the last received request.
 169       */
 170      public function getLastRequest(): ?RequestInterface
 171      {
 172          return $this->lastRequest;
 173      }
 174  
 175      /**
 176       * Get the last received request options.
 177       */
 178      public function getLastOptions(): array
 179      {
 180          return $this->lastOptions;
 181      }
 182  
 183      /**
 184       * Returns the number of remaining items in the queue.
 185       */
 186      public function count(): int
 187      {
 188          return \count($this->queue);
 189      }
 190  
 191      public function reset(): void
 192      {
 193          $this->queue = [];
 194      }
 195  
 196      /**
 197       * @param mixed $reason Promise or reason.
 198       */
 199      private function invokeStats(
 200          RequestInterface $request,
 201          array $options,
 202          ResponseInterface $response = null,
 203          $reason = null
 204      ): void {
 205          if (isset($options['on_stats'])) {
 206              $transferTime = $options['transfer_time'] ?? 0;
 207              $stats = new TransferStats($request, $response, $transferTime, $reason);
 208              ($options['on_stats'])($stats);
 209          }
 210      }
 211  }