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