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  namespace GuzzleHttp;
   4  
   5  use GuzzleHttp\Cookie\CookieJarInterface;
   6  use GuzzleHttp\Exception\RequestException;
   7  use GuzzleHttp\Promise as P;
   8  use GuzzleHttp\Promise\PromiseInterface;
   9  use Psr\Http\Message\RequestInterface;
  10  use Psr\Http\Message\ResponseInterface;
  11  use Psr\Log\LoggerInterface;
  12  
  13  /**
  14   * Functions used to create and wrap handlers with handler middleware.
  15   */
  16  final class Middleware
  17  {
  18      /**
  19       * Middleware that adds cookies to requests.
  20       *
  21       * The options array must be set to a CookieJarInterface in order to use
  22       * cookies. This is typically handled for you by a client.
  23       *
  24       * @return callable Returns a function that accepts the next handler.
  25       */
  26      public static function cookies(): callable
  27      {
  28          return static function (callable $handler): callable {
  29              return static function ($request, array $options) use ($handler) {
  30                  if (empty($options['cookies'])) {
  31                      return $handler($request, $options);
  32                  } elseif (!($options['cookies'] instanceof CookieJarInterface)) {
  33                      throw new \InvalidArgumentException('cookies must be an instance of GuzzleHttp\Cookie\CookieJarInterface');
  34                  }
  35                  $cookieJar = $options['cookies'];
  36                  $request = $cookieJar->withCookieHeader($request);
  37                  return $handler($request, $options)
  38                      ->then(
  39                          static function (ResponseInterface $response) use ($cookieJar, $request): ResponseInterface {
  40                              $cookieJar->extractCookies($request, $response);
  41                              return $response;
  42                          }
  43                      );
  44              };
  45          };
  46      }
  47  
  48      /**
  49       * Middleware that throws exceptions for 4xx or 5xx responses when the
  50       * "http_errors" request option is set to true.
  51       *
  52       * @param BodySummarizerInterface|null $bodySummarizer The body summarizer to use in exception messages.
  53       *
  54       * @return callable(callable): callable Returns a function that accepts the next handler.
  55       */
  56      public static function httpErrors(BodySummarizerInterface $bodySummarizer = null): callable
  57      {
  58          return static function (callable $handler) use ($bodySummarizer): callable {
  59              return static function ($request, array $options) use ($handler, $bodySummarizer) {
  60                  if (empty($options['http_errors'])) {
  61                      return $handler($request, $options);
  62                  }
  63                  return $handler($request, $options)->then(
  64                      static function (ResponseInterface $response) use ($request, $bodySummarizer) {
  65                          $code = $response->getStatusCode();
  66                          if ($code < 400) {
  67                              return $response;
  68                          }
  69                          throw RequestException::create($request, $response, null, [], $bodySummarizer);
  70                      }
  71                  );
  72              };
  73          };
  74      }
  75  
  76      /**
  77       * Middleware that pushes history data to an ArrayAccess container.
  78       *
  79       * @param array|\ArrayAccess<int, array> $container Container to hold the history (by reference).
  80       *
  81       * @return callable(callable): callable Returns a function that accepts the next handler.
  82       *
  83       * @throws \InvalidArgumentException if container is not an array or ArrayAccess.
  84       */
  85      public static function history(&$container): callable
  86      {
  87          if (!\is_array($container) && !$container instanceof \ArrayAccess) {
  88              throw new \InvalidArgumentException('history container must be an array or object implementing ArrayAccess');
  89          }
  90  
  91          return static function (callable $handler) use (&$container): callable {
  92              return static function (RequestInterface $request, array $options) use ($handler, &$container) {
  93                  return $handler($request, $options)->then(
  94                      static function ($value) use ($request, &$container, $options) {
  95                          $container[] = [
  96                              'request'  => $request,
  97                              'response' => $value,
  98                              'error'    => null,
  99                              'options'  => $options
 100                          ];
 101                          return $value;
 102                      },
 103                      static function ($reason) use ($request, &$container, $options) {
 104                          $container[] = [
 105                              'request'  => $request,
 106                              'response' => null,
 107                              'error'    => $reason,
 108                              'options'  => $options
 109                          ];
 110                          return P\Create::rejectionFor($reason);
 111                      }
 112                  );
 113              };
 114          };
 115      }
 116  
 117      /**
 118       * Middleware that invokes a callback before and after sending a request.
 119       *
 120       * The provided listener cannot modify or alter the response. It simply
 121       * "taps" into the chain to be notified before returning the promise. The
 122       * before listener accepts a request and options array, and the after
 123       * listener accepts a request, options array, and response promise.
 124       *
 125       * @param callable $before Function to invoke before forwarding the request.
 126       * @param callable $after  Function invoked after forwarding.
 127       *
 128       * @return callable Returns a function that accepts the next handler.
 129       */
 130      public static function tap(callable $before = null, callable $after = null): callable
 131      {
 132          return static function (callable $handler) use ($before, $after): callable {
 133              return static function (RequestInterface $request, array $options) use ($handler, $before, $after) {
 134                  if ($before) {
 135                      $before($request, $options);
 136                  }
 137                  $response = $handler($request, $options);
 138                  if ($after) {
 139                      $after($request, $options, $response);
 140                  }
 141                  return $response;
 142              };
 143          };
 144      }
 145  
 146      /**
 147       * Middleware that handles request redirects.
 148       *
 149       * @return callable Returns a function that accepts the next handler.
 150       */
 151      public static function redirect(): callable
 152      {
 153          return static function (callable $handler): RedirectMiddleware {
 154              return new RedirectMiddleware($handler);
 155          };
 156      }
 157  
 158      /**
 159       * Middleware that retries requests based on the boolean result of
 160       * invoking the provided "decider" function.
 161       *
 162       * If no delay function is provided, a simple implementation of exponential
 163       * backoff will be utilized.
 164       *
 165       * @param callable $decider Function that accepts the number of retries,
 166       *                          a request, [response], and [exception] and
 167       *                          returns true if the request is to be retried.
 168       * @param callable $delay   Function that accepts the number of retries and
 169       *                          returns the number of milliseconds to delay.
 170       *
 171       * @return callable Returns a function that accepts the next handler.
 172       */
 173      public static function retry(callable $decider, callable $delay = null): callable
 174      {
 175          return static function (callable $handler) use ($decider, $delay): RetryMiddleware {
 176              return new RetryMiddleware($decider, $handler, $delay);
 177          };
 178      }
 179  
 180      /**
 181       * Middleware that logs requests, responses, and errors using a message
 182       * formatter.
 183       *
 184       * @phpstan-param \Psr\Log\LogLevel::* $logLevel  Level at which to log requests.
 185       *
 186       * @param LoggerInterface                            $logger    Logs messages.
 187       * @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
 188       * @param string                                     $logLevel  Level at which to log requests.
 189       *
 190       * @return callable Returns a function that accepts the next handler.
 191       */
 192      public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable
 193      {
 194          // To be compatible with Guzzle 7.1.x we need to allow users to pass a MessageFormatter
 195          if (!$formatter instanceof MessageFormatter && !$formatter instanceof MessageFormatterInterface) {
 196              throw new \LogicException(sprintf('Argument 2 to %s::log() must be of type %s', self::class, MessageFormatterInterface::class));
 197          }
 198  
 199          return static function (callable $handler) use ($logger, $formatter, $logLevel): callable {
 200              return static function (RequestInterface $request, array $options = []) use ($handler, $logger, $formatter, $logLevel) {
 201                  return $handler($request, $options)->then(
 202                      static function ($response) use ($logger, $request, $formatter, $logLevel): ResponseInterface {
 203                          $message = $formatter->format($request, $response);
 204                          $logger->log($logLevel, $message);
 205                          return $response;
 206                      },
 207                      static function ($reason) use ($logger, $request, $formatter): PromiseInterface {
 208                          $response = $reason instanceof RequestException ? $reason->getResponse() : null;
 209                          $message = $formatter->format($request, $response, P\Create::exceptionFor($reason));
 210                          $logger->error($message);
 211                          return P\Create::rejectionFor($reason);
 212                      }
 213                  );
 214              };
 215          };
 216      }
 217  
 218      /**
 219       * This middleware adds a default content-type if possible, a default
 220       * content-length or transfer-encoding header, and the expect header.
 221       */
 222      public static function prepareBody(): callable
 223      {
 224          return static function (callable $handler): PrepareBodyMiddleware {
 225              return new PrepareBodyMiddleware($handler);
 226          };
 227      }
 228  
 229      /**
 230       * Middleware that applies a map function to the request before passing to
 231       * the next handler.
 232       *
 233       * @param callable $fn Function that accepts a RequestInterface and returns
 234       *                     a RequestInterface.
 235       */
 236      public static function mapRequest(callable $fn): callable
 237      {
 238          return static function (callable $handler) use ($fn): callable {
 239              return static function (RequestInterface $request, array $options) use ($handler, $fn) {
 240                  return $handler($fn($request), $options);
 241              };
 242          };
 243      }
 244  
 245      /**
 246       * Middleware that applies a map function to the resolved promise's
 247       * response.
 248       *
 249       * @param callable $fn Function that accepts a ResponseInterface and
 250       *                     returns a ResponseInterface.
 251       */
 252      public static function mapResponse(callable $fn): callable
 253      {
 254          return static function (callable $handler) use ($fn): callable {
 255              return static function (RequestInterface $request, array $options) use ($handler, $fn) {
 256                  return $handler($request, $options)->then($fn);
 257              };
 258          };
 259      }
 260  }