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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body