HandlerStack.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. namespace GuzzleHttp;
  3. use GuzzleHttp\Promise\PromiseInterface;
  4. use Psr\Http\Message\RequestInterface;
  5. use Psr\Http\Message\ResponseInterface;
  6. /**
  7. * Creates a composed Guzzle handler function by stacking middlewares on top of
  8. * an HTTP handler function.
  9. */
  10. class HandlerStack
  11. {
  12. /** @var callable|null */
  13. private $handler;
  14. /** @var array */
  15. private $stack = [];
  16. /** @var callable|null */
  17. private $cached;
  18. /**
  19. * Creates a default handler stack that can be used by clients.
  20. *
  21. * The returned handler will wrap the provided handler or use the most
  22. * appropriate default handler for your system. The returned HandlerStack has
  23. * support for cookies, redirects, HTTP error exceptions, and preparing a body
  24. * before sending.
  25. *
  26. * The returned handler stack can be passed to a client in the "handler"
  27. * option.
  28. *
  29. * @param callable $handler HTTP handler function to use with the stack. If no
  30. * handler is provided, the best handler for your
  31. * system will be utilized.
  32. *
  33. * @return HandlerStack
  34. */
  35. public static function create(callable $handler = null)
  36. {
  37. $stack = new self($handler ?: choose_handler());
  38. $stack->push(Middleware::httpErrors(), 'http_errors');
  39. $stack->push(Middleware::redirect(), 'allow_redirects');
  40. $stack->push(Middleware::cookies(), 'cookies');
  41. $stack->push(Middleware::prepareBody(), 'prepare_body');
  42. return $stack;
  43. }
  44. /**
  45. * @param callable $handler Underlying HTTP handler.
  46. */
  47. public function __construct(callable $handler = null)
  48. {
  49. $this->handler = $handler;
  50. }
  51. /**
  52. * Invokes the handler stack as a composed handler
  53. *
  54. * @param RequestInterface $request
  55. * @param array $options
  56. *
  57. * @return ResponseInterface|PromiseInterface
  58. */
  59. public function __invoke(RequestInterface $request, array $options)
  60. {
  61. $handler = $this->resolve();
  62. return $handler($request, $options);
  63. }
  64. /**
  65. * Dumps a string representation of the stack.
  66. *
  67. * @return string
  68. */
  69. public function __toString()
  70. {
  71. $depth = 0;
  72. $stack = [];
  73. if ($this->handler) {
  74. $stack[] = "0) Handler: " . $this->debugCallable($this->handler);
  75. }
  76. $result = '';
  77. foreach (array_reverse($this->stack) as $tuple) {
  78. $depth++;
  79. $str = "{$depth}) Name: '{$tuple[1]}', ";
  80. $str .= "Function: " . $this->debugCallable($tuple[0]);
  81. $result = "> {$str}\n{$result}";
  82. $stack[] = $str;
  83. }
  84. foreach (array_keys($stack) as $k) {
  85. $result .= "< {$stack[$k]}\n";
  86. }
  87. return $result;
  88. }
  89. /**
  90. * Set the HTTP handler that actually returns a promise.
  91. *
  92. * @param callable $handler Accepts a request and array of options and
  93. * returns a Promise.
  94. */
  95. public function setHandler(callable $handler)
  96. {
  97. $this->handler = $handler;
  98. $this->cached = null;
  99. }
  100. /**
  101. * Returns true if the builder has a handler.
  102. *
  103. * @return bool
  104. */
  105. public function hasHandler()
  106. {
  107. return (bool) $this->handler;
  108. }
  109. /**
  110. * Unshift a middleware to the bottom of the stack.
  111. *
  112. * @param callable $middleware Middleware function
  113. * @param string $name Name to register for this middleware.
  114. */
  115. public function unshift(callable $middleware, $name = null)
  116. {
  117. array_unshift($this->stack, [$middleware, $name]);
  118. $this->cached = null;
  119. }
  120. /**
  121. * Push a middleware to the top of the stack.
  122. *
  123. * @param callable $middleware Middleware function
  124. * @param string $name Name to register for this middleware.
  125. */
  126. public function push(callable $middleware, $name = '')
  127. {
  128. $this->stack[] = [$middleware, $name];
  129. $this->cached = null;
  130. }
  131. /**
  132. * Add a middleware before another middleware by name.
  133. *
  134. * @param string $findName Middleware to find
  135. * @param callable $middleware Middleware function
  136. * @param string $withName Name to register for this middleware.
  137. */
  138. public function before($findName, callable $middleware, $withName = '')
  139. {
  140. $this->splice($findName, $withName, $middleware, true);
  141. }
  142. /**
  143. * Add a middleware after another middleware by name.
  144. *
  145. * @param string $findName Middleware to find
  146. * @param callable $middleware Middleware function
  147. * @param string $withName Name to register for this middleware.
  148. */
  149. public function after($findName, callable $middleware, $withName = '')
  150. {
  151. $this->splice($findName, $withName, $middleware, false);
  152. }
  153. /**
  154. * Remove a middleware by instance or name from the stack.
  155. *
  156. * @param callable|string $remove Middleware to remove by instance or name.
  157. */
  158. public function remove($remove)
  159. {
  160. $this->cached = null;
  161. $idx = is_callable($remove) ? 0 : 1;
  162. $this->stack = array_values(array_filter(
  163. $this->stack,
  164. function ($tuple) use ($idx, $remove) {
  165. return $tuple[$idx] !== $remove;
  166. }
  167. ));
  168. }
  169. /**
  170. * Compose the middleware and handler into a single callable function.
  171. *
  172. * @return callable
  173. */
  174. public function resolve()
  175. {
  176. if (!$this->cached) {
  177. if (!($prev = $this->handler)) {
  178. throw new \LogicException('No handler has been specified');
  179. }
  180. foreach (array_reverse($this->stack) as $fn) {
  181. $prev = $fn[0]($prev);
  182. }
  183. $this->cached = $prev;
  184. }
  185. return $this->cached;
  186. }
  187. /**
  188. * @param string $name
  189. * @return int
  190. */
  191. private function findByName($name)
  192. {
  193. foreach ($this->stack as $k => $v) {
  194. if ($v[1] === $name) {
  195. return $k;
  196. }
  197. }
  198. throw new \InvalidArgumentException("Middleware not found: $name");
  199. }
  200. /**
  201. * Splices a function into the middleware list at a specific position.
  202. *
  203. * @param string $findName
  204. * @param string $withName
  205. * @param callable $middleware
  206. * @param bool $before
  207. */
  208. private function splice($findName, $withName, callable $middleware, $before)
  209. {
  210. $this->cached = null;
  211. $idx = $this->findByName($findName);
  212. $tuple = [$middleware, $withName];
  213. if ($before) {
  214. if ($idx === 0) {
  215. array_unshift($this->stack, $tuple);
  216. } else {
  217. $replacement = [$tuple, $this->stack[$idx]];
  218. array_splice($this->stack, $idx, 1, $replacement);
  219. }
  220. } elseif ($idx === count($this->stack) - 1) {
  221. $this->stack[] = $tuple;
  222. } else {
  223. $replacement = [$this->stack[$idx], $tuple];
  224. array_splice($this->stack, $idx, 1, $replacement);
  225. }
  226. }
  227. /**
  228. * Provides a debug string for a given callable.
  229. *
  230. * @param array|callable $fn Function to write as a string.
  231. *
  232. * @return string
  233. */
  234. private function debugCallable($fn)
  235. {
  236. if (is_string($fn)) {
  237. return "callable({$fn})";
  238. }
  239. if (is_array($fn)) {
  240. return is_string($fn[0])
  241. ? "callable({$fn[0]}::{$fn[1]})"
  242. : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])";
  243. }
  244. return 'callable(' . spl_object_hash($fn) . ')';
  245. }
  246. }