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;
   4  
   5  use GuzzleHttp\Promise as P;
   6  use GuzzleHttp\Promise\PromiseInterface;
   7  use Psr\Http\Message\RequestInterface;
   8  use Psr\Http\Message\ResponseInterface;
   9  
  10  /**
  11   * Middleware that retries requests based on the boolean result of
  12   * invoking the provided "decider" function.
  13   *
  14   * @final
  15   */
  16  class RetryMiddleware
  17  {
  18      /**
  19       * @var callable(RequestInterface, array): PromiseInterface
  20       */
  21      private $nextHandler;
  22  
  23      /**
  24       * @var callable
  25       */
  26      private $decider;
  27  
  28      /**
  29       * @var callable(int)
  30       */
  31      private $delay;
  32  
  33      /**
  34       * @param callable                                            $decider     Function that accepts the number of retries,
  35       *                                                                         a request, [response], and [exception] and
  36       *                                                                         returns true if the request is to be
  37       *                                                                         retried.
  38       * @param callable(RequestInterface, array): PromiseInterface $nextHandler Next handler to invoke.
  39       * @param (callable(int): int)|null                           $delay       Function that accepts the number of retries
  40       *                                                                         and returns the number of
  41       *                                                                         milliseconds to delay.
  42       */
  43      public function __construct(callable $decider, callable $nextHandler, callable $delay = null)
  44      {
  45          $this->decider = $decider;
  46          $this->nextHandler = $nextHandler;
  47          $this->delay = $delay ?: __CLASS__ . '::exponentialDelay';
  48      }
  49  
  50      /**
  51       * Default exponential backoff delay function.
  52       *
  53       * @return int milliseconds.
  54       */
  55      public static function exponentialDelay(int $retries): int
  56      {
  57          return (int) \pow(2, $retries - 1) * 1000;
  58      }
  59  
  60      public function __invoke(RequestInterface $request, array $options): PromiseInterface
  61      {
  62          if (!isset($options['retries'])) {
  63              $options['retries'] = 0;
  64          }
  65  
  66          $fn = $this->nextHandler;
  67          return $fn($request, $options)
  68              ->then(
  69                  $this->onFulfilled($request, $options),
  70                  $this->onRejected($request, $options)
  71              );
  72      }
  73  
  74      /**
  75       * Execute fulfilled closure
  76       */
  77      private function onFulfilled(RequestInterface $request, array $options): callable
  78      {
  79          return function ($value) use ($request, $options) {
  80              if (!($this->decider)(
  81                  $options['retries'],
  82                  $request,
  83                  $value,
  84                  null
  85              )) {
  86                  return $value;
  87              }
  88              return $this->doRetry($request, $options, $value);
  89          };
  90      }
  91  
  92      /**
  93       * Execute rejected closure
  94       */
  95      private function onRejected(RequestInterface $req, array $options): callable
  96      {
  97          return function ($reason) use ($req, $options) {
  98              if (!($this->decider)(
  99                  $options['retries'],
 100                  $req,
 101                  null,
 102                  $reason
 103              )) {
 104                  return P\Create::rejectionFor($reason);
 105              }
 106              return $this->doRetry($req, $options);
 107          };
 108      }
 109  
 110      private function doRetry(RequestInterface $request, array $options, ResponseInterface $response = null): PromiseInterface
 111      {
 112          $options['delay'] = ($this->delay)(++$options['retries'], $response, $request);
 113  
 114          return $this($request, $options);
 115      }
 116  }