HandlerList.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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. /**
  277. * @return int
  278. */
  279. #[\ReturnTypeWillChange]
  280. public function count()
  281. {
  282. return count($this->steps[self::INIT])
  283. + count($this->steps[self::VALIDATE])
  284. + count($this->steps[self::BUILD])
  285. + count($this->steps[self::SIGN])
  286. + count($this->steps[self::ATTEMPT]);
  287. }
  288. /**
  289. * Splices a function into the middleware list at a specific position.
  290. *
  291. * @param $findName
  292. * @param $withName
  293. * @param callable $middleware
  294. * @param $before
  295. */
  296. private function splice($findName, $withName, callable $middleware, $before)
  297. {
  298. if (!isset($this->named[$findName])) {
  299. throw new \InvalidArgumentException("$findName not found");
  300. }
  301. $idx = $this->sorted = null;
  302. $step = $this->named[$findName];
  303. if ($withName) {
  304. $this->named[$withName] = $step;
  305. }
  306. foreach ($this->steps[$step] as $i => $tuple) {
  307. if ($tuple[1] === $findName) {
  308. $idx = $i;
  309. break;
  310. }
  311. }
  312. $replacement = $before
  313. ? [$this->steps[$step][$idx], [$middleware, $withName]]
  314. : [[$middleware, $withName], $this->steps[$step][$idx]];
  315. array_splice($this->steps[$step], $idx, 1, $replacement);
  316. }
  317. /**
  318. * Provides a debug string for a given callable.
  319. *
  320. * @param array|callable $fn Function to write as a string.
  321. *
  322. * @return string
  323. */
  324. private function debugCallable($fn)
  325. {
  326. if (is_string($fn)) {
  327. return "callable({$fn})";
  328. }
  329. if (is_array($fn)) {
  330. $ele = is_string($fn[0]) ? $fn[0] : get_class($fn[0]);
  331. return "callable(['{$ele}', '{$fn[1]}'])";
  332. }
  333. return 'callable(' . spl_object_hash($fn) . ')';
  334. }
  335. /**
  336. * Sort the middleware, and interpose if needed in the sorted list.
  337. */
  338. private function sortMiddleware()
  339. {
  340. $this->sorted = [];
  341. if (!$this->interposeFn) {
  342. foreach ($this->steps as $step) {
  343. foreach ($step as $fn) {
  344. $this->sorted[] = $fn[0];
  345. }
  346. }
  347. return;
  348. }
  349. $ifn = $this->interposeFn;
  350. // Interpose the interposeFn into the handler stack.
  351. foreach ($this->steps as $stepName => $step) {
  352. foreach ($step as $fn) {
  353. $this->sorted[] = $ifn($stepName, $fn[1]);
  354. $this->sorted[] = $fn[0];
  355. }
  356. }
  357. }
  358. private function removeByName($name)
  359. {
  360. if (!isset($this->named[$name])) {
  361. return;
  362. }
  363. $this->sorted = null;
  364. $step = $this->named[$name];
  365. $this->steps[$step] = array_values(
  366. array_filter(
  367. $this->steps[$step],
  368. function ($tuple) use ($name) {
  369. return $tuple[1] !== $name;
  370. }
  371. )
  372. );
  373. }
  374. private function removeByInstance(callable $fn)
  375. {
  376. foreach ($this->steps as $k => $step) {
  377. foreach ($step as $j => $tuple) {
  378. if ($tuple[0] === $fn) {
  379. $this->sorted = null;
  380. unset($this->named[$this->steps[$k][$j][1]]);
  381. unset($this->steps[$k][$j]);
  382. }
  383. }
  384. }
  385. }
  386. /**
  387. * Add a middleware to a step.
  388. *
  389. * @param string $step Middleware step.
  390. * @param string $name Middleware name.
  391. * @param callable $middleware Middleware function to add.
  392. * @param bool $prepend Prepend instead of append.
  393. */
  394. private function add($step, $name, callable $middleware, $prepend = false)
  395. {
  396. $this->sorted = null;
  397. if ($prepend) {
  398. $this->steps[$step][] = [$middleware, $name];
  399. } else {
  400. array_unshift($this->steps[$step], [$middleware, $name]);
  401. }
  402. if ($name) {
  403. $this->named[$name] = $step;
  404. }
  405. }
  406. }