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