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