HandlerStack.php 7.4 KB

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