HandlerList.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <?php
  2. namespace Aws;
  3. /**
  4. * Builds a single handler function from zero or more middleware functions and
  5. * a handler. The handler function is then used to send command objects and
  6. * return a promise that is resolved with an AWS result object.
  7. *
  8. * The "front" of the list is invoked before the "end" of the list. You can add
  9. * middleware to the front of the list using one of the "prepend" method, and
  10. * the end of the list using one of the "append" method. The last function
  11. * invoked in a handler list is the handler (a function that does not accept a
  12. * next handler but rather is responsible for returning a promise that is
  13. * fulfilled with an Aws\ResultInterface object).
  14. *
  15. * Handlers are ordered using a "step" that describes the step at which the
  16. * SDK is when sending a command. The available steps are:
  17. *
  18. * - init: The command is being initialized, allowing you to do things like add
  19. * default options.
  20. * - validate: The command is being validated before it is serialized
  21. * - build: The command is being serialized into an HTTP request. A middleware
  22. * in this step MUST serialize an HTTP request and populate the "@request"
  23. * parameter of a command with the request such that it is available to
  24. * subsequent middleware.
  25. * - sign: The request is being signed and prepared to be sent over the wire.
  26. *
  27. * Middleware can be registered with a name to allow you to easily add a
  28. * middleware before or after another middleware by name. This also allows you
  29. * to remove a middleware by name (in addition to removing by instance).
  30. */
  31. class HandlerList implements \Countable
  32. {
  33. const INIT = 'init';
  34. const VALIDATE = 'validate';
  35. const BUILD = 'build';
  36. const SIGN = 'sign';
  37. const ATTEMPT = 'attempt';
  38. /** @var callable */
  39. private $handler;
  40. /** @var array */
  41. private $named = [];
  42. /** @var array */
  43. private $sorted;
  44. /** @var callable|null */
  45. private $interposeFn;
  46. /** @var array Steps (in reverse order) */
  47. private $steps = [
  48. self::ATTEMPT => [],
  49. self::SIGN => [],
  50. self::BUILD => [],
  51. self::VALIDATE => [],
  52. self::INIT => [],
  53. ];
  54. /**
  55. * @param callable $handler HTTP handler.
  56. */
  57. public function __construct(callable $handler = null)
  58. {
  59. $this->handler = $handler;
  60. }
  61. /**
  62. * Dumps a string representation of the list.
  63. *
  64. * @return string
  65. */
  66. public function __toString()
  67. {
  68. $str = '';
  69. $i = 0;
  70. foreach (array_reverse($this->steps) as $k => $step) {
  71. foreach (array_reverse($step) as $j => $tuple) {
  72. $str .= "{$i}) Step: {$k}, ";
  73. if ($tuple[1]) {
  74. $str .= "Name: {$tuple[1]}, ";
  75. }
  76. $str .= "Function: " . $this->debugCallable($tuple[0]) . "\n";
  77. $i++;
  78. }
  79. }
  80. if ($this->handler) {
  81. $str .= "{$i}) Handler: " . $this->debugCallable($this->handler) . "\n";
  82. }
  83. return $str;
  84. }
  85. /**
  86. * Set the HTTP handler that actually returns a response.
  87. *
  88. * @param callable $handler Function that accepts a request and array of
  89. * options and returns a Promise.
  90. */
  91. public function setHandler(callable $handler)
  92. {
  93. $this->handler = $handler;
  94. }
  95. /**
  96. * Returns true if the builder has a handler.
  97. *
  98. * @return bool
  99. */
  100. public function hasHandler()
  101. {
  102. return (bool) $this->handler;
  103. }
  104. /**
  105. * Append a middleware to the init step.
  106. *
  107. * @param callable $middleware Middleware function to add.
  108. * @param string $name Name of the middleware.
  109. */
  110. public function appendInit(callable $middleware, $name = null)
  111. {
  112. $this->add(self::INIT, $name, $middleware);
  113. }
  114. /**
  115. * Prepend a middleware to the init step.
  116. *
  117. * @param callable $middleware Middleware function to add.
  118. * @param string $name Name of the middleware.
  119. */
  120. public function prependInit(callable $middleware, $name = null)
  121. {
  122. $this->add(self::INIT, $name, $middleware, true);
  123. }
  124. /**
  125. * Append a middleware to the validate step.
  126. *
  127. * @param callable $middleware Middleware function to add.
  128. * @param string $name Name of the middleware.
  129. */
  130. public function appendValidate(callable $middleware, $name = null)
  131. {
  132. $this->add(self::VALIDATE, $name, $middleware);
  133. }
  134. /**
  135. * Prepend a middleware to the validate step.
  136. *
  137. * @param callable $middleware Middleware function to add.
  138. * @param string $name Name of the middleware.
  139. */
  140. public function prependValidate(callable $middleware, $name = null)
  141. {
  142. $this->add(self::VALIDATE, $name, $middleware, true);
  143. }
  144. /**
  145. * Append a middleware to the build step.
  146. *
  147. * @param callable $middleware Middleware function to add.
  148. * @param string $name Name of the middleware.
  149. */
  150. public function appendBuild(callable $middleware, $name = null)
  151. {
  152. $this->add(self::BUILD, $name, $middleware);
  153. }
  154. /**
  155. * Prepend a middleware to the build step.
  156. *
  157. * @param callable $middleware Middleware function to add.
  158. * @param string $name Name of the middleware.
  159. */
  160. public function prependBuild(callable $middleware, $name = null)
  161. {
  162. $this->add(self::BUILD, $name, $middleware, true);
  163. }
  164. /**
  165. * Append a middleware to the sign step.
  166. *
  167. * @param callable $middleware Middleware function to add.
  168. * @param string $name Name of the middleware.
  169. */
  170. public function appendSign(callable $middleware, $name = null)
  171. {
  172. $this->add(self::SIGN, $name, $middleware);
  173. }
  174. /**
  175. * Prepend a middleware to the sign step.
  176. *
  177. * @param callable $middleware Middleware function to add.
  178. * @param string $name Name of the middleware.
  179. */
  180. public function prependSign(callable $middleware, $name = null)
  181. {
  182. $this->add(self::SIGN, $name, $middleware, true);
  183. }
  184. /**
  185. * Append a middleware to the attempt step.
  186. *
  187. * @param callable $middleware Middleware function to add.
  188. * @param string $name Name of the middleware.
  189. */
  190. public function appendAttempt(callable $middleware, $name = null)
  191. {
  192. $this->add(self::ATTEMPT, $name, $middleware);
  193. }
  194. /**
  195. * Prepend a middleware to the attempt step.
  196. *
  197. * @param callable $middleware Middleware function to add.
  198. * @param string $name Name of the middleware.
  199. */
  200. public function prependAttempt(callable $middleware, $name = null)
  201. {
  202. $this->add(self::ATTEMPT, $name, $middleware, true);
  203. }
  204. /**
  205. * Add a middleware before the given middleware by name.
  206. *
  207. * @param string|callable $findName Add before this
  208. * @param string $withName Optional name to give the middleware
  209. * @param callable $middleware Middleware to add.
  210. */
  211. public function before($findName, $withName, callable $middleware)
  212. {
  213. $this->splice($findName, $withName, $middleware, true);
  214. }
  215. /**
  216. * Add a middleware after the given middleware by name.
  217. *
  218. * @param string|callable $findName Add after this
  219. * @param string $withName Optional name to give the middleware
  220. * @param callable $middleware Middleware to add.
  221. */
  222. public function after($findName, $withName, callable $middleware)
  223. {
  224. $this->splice($findName, $withName, $middleware, false);
  225. }
  226. /**
  227. * Remove a middleware by name or by instance from the list.
  228. *
  229. * @param string|callable $nameOrInstance Middleware to remove.
  230. */
  231. public function remove($nameOrInstance)
  232. {
  233. if (is_callable($nameOrInstance)) {
  234. $this->removeByInstance($nameOrInstance);
  235. } elseif (is_string($nameOrInstance)) {
  236. $this->removeByName($nameOrInstance);
  237. }
  238. }
  239. /**
  240. * Interpose a function between each middleware (e.g., allowing for a trace
  241. * through the middleware layers).
  242. *
  243. * The interpose function is a function that accepts a "step" argument as a
  244. * string and a "name" argument string. This function must then return a
  245. * function that accepts the next handler in the list. This function must
  246. * then return a function that accepts a CommandInterface and optional
  247. * RequestInterface and returns a promise that is fulfilled with an
  248. * Aws\ResultInterface or rejected with an Aws\Exception\AwsException
  249. * object.
  250. *
  251. * @param callable|null $fn Pass null to remove any previously set function
  252. */
  253. public function interpose(callable $fn = null)
  254. {
  255. $this->sorted = null;
  256. $this->interposeFn = $fn;
  257. }
  258. /**
  259. * Compose the middleware and handler into a single callable function.
  260. *
  261. * @return callable
  262. */
  263. public function resolve()
  264. {
  265. if (!($prev = $this->handler)) {
  266. throw new \LogicException('No handler has been specified');
  267. }
  268. if ($this->sorted === null) {
  269. $this->sortMiddleware();
  270. }
  271. foreach ($this->sorted as $fn) {
  272. $prev = $fn($prev);
  273. }
  274. return $prev;
  275. }
  276. public function count()
  277. {
  278. return count($this->steps[self::INIT])
  279. + count($this->steps[self::VALIDATE])
  280. + count($this->steps[self::BUILD])
  281. + count($this->steps[self::SIGN])
  282. + count($this->steps[self::ATTEMPT]);
  283. }
  284. /**
  285. * Splices a function into the middleware list at a specific position.
  286. *
  287. * @param $findName
  288. * @param $withName
  289. * @param callable $middleware
  290. * @param $before
  291. */
  292. private function splice($findName, $withName, callable $middleware, $before)
  293. {
  294. if (!isset($this->named[$findName])) {
  295. throw new \InvalidArgumentException("$findName not found");
  296. }
  297. $idx = $this->sorted = null;
  298. $step = $this->named[$findName];
  299. if ($withName) {
  300. $this->named[$withName] = $step;
  301. }
  302. foreach ($this->steps[$step] as $i => $tuple) {
  303. if ($tuple[1] === $findName) {
  304. $idx = $i;
  305. break;
  306. }
  307. }
  308. $replacement = $before
  309. ? [$this->steps[$step][$idx], [$middleware, $withName]]
  310. : [[$middleware, $withName], $this->steps[$step][$idx]];
  311. array_splice($this->steps[$step], $idx, 1, $replacement);
  312. }
  313. /**
  314. * Provides a debug string for a given callable.
  315. *
  316. * @param array|callable $fn Function to write as a string.
  317. *
  318. * @return string
  319. */
  320. private function debugCallable($fn)
  321. {
  322. if (is_string($fn)) {
  323. return "callable({$fn})";
  324. }
  325. if (is_array($fn)) {
  326. $ele = is_string($fn[0]) ? $fn[0] : get_class($fn[0]);
  327. return "callable(['{$ele}', '{$fn[1]}'])";
  328. }
  329. return 'callable(' . spl_object_hash($fn) . ')';
  330. }
  331. /**
  332. * Sort the middleware, and interpose if needed in the sorted list.
  333. */
  334. private function sortMiddleware()
  335. {
  336. $this->sorted = [];
  337. if (!$this->interposeFn) {
  338. foreach ($this->steps as $step) {
  339. foreach ($step as $fn) {
  340. $this->sorted[] = $fn[0];
  341. }
  342. }
  343. return;
  344. }
  345. $ifn = $this->interposeFn;
  346. // Interpose the interposeFn into the handler stack.
  347. foreach ($this->steps as $stepName => $step) {
  348. foreach ($step as $fn) {
  349. $this->sorted[] = $ifn($stepName, $fn[1]);
  350. $this->sorted[] = $fn[0];
  351. }
  352. }
  353. }
  354. private function removeByName($name)
  355. {
  356. if (!isset($this->named[$name])) {
  357. return;
  358. }
  359. $this->sorted = null;
  360. $step = $this->named[$name];
  361. $this->steps[$step] = array_values(
  362. array_filter(
  363. $this->steps[$step],
  364. function ($tuple) use ($name) {
  365. return $tuple[1] !== $name;
  366. }
  367. )
  368. );
  369. }
  370. private function removeByInstance(callable $fn)
  371. {
  372. foreach ($this->steps as $k => $step) {
  373. foreach ($step as $j => $tuple) {
  374. if ($tuple[0] === $fn) {
  375. $this->sorted = null;
  376. unset($this->named[$this->steps[$k][$j][1]]);
  377. unset($this->steps[$k][$j]);
  378. }
  379. }
  380. }
  381. }
  382. /**
  383. * Add a middleware to a step.
  384. *
  385. * @param string $step Middleware step.
  386. * @param string $name Middleware name.
  387. * @param callable $middleware Middleware function to add.
  388. * @param bool $prepend Prepend instead of append.
  389. */
  390. private function add($step, $name, callable $middleware, $prepend = false)
  391. {
  392. $this->sorted = null;
  393. if ($prepend) {
  394. $this->steps[$step][] = [$middleware, $name];
  395. } else {
  396. array_unshift($this->steps[$step], [$middleware, $name]);
  397. }
  398. if ($name) {
  399. $this->named[$name] = $step;
  400. }
  401. }
  402. }