427 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			427 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace Aws;
 | |
| 
 | |
| /**
 | |
|  * Builds a single handler function from zero or more middleware functions and
 | |
|  * a handler. The handler function is then used to send command objects and
 | |
|  * return a promise that is resolved with an AWS result object.
 | |
|  *
 | |
|  * The "front" of the list is invoked before the "end" of the list. You can add
 | |
|  * middleware to the front of the list using one of the "prepend" method, and
 | |
|  * the end of the list using one of the "append" method. The last function
 | |
|  * invoked in a handler list is the handler (a function that does not accept a
 | |
|  * next handler but rather is responsible for returning a promise that is
 | |
|  * fulfilled with an Aws\ResultInterface object).
 | |
|  *
 | |
|  * Handlers are ordered using a "step" that describes the step at which the
 | |
|  * SDK is when sending a command. The available steps are:
 | |
|  *
 | |
|  * - init: The command is being initialized, allowing you to do things like add
 | |
|  *   default options.
 | |
|  * - validate: The command is being validated before it is serialized
 | |
|  * - build: The command is being serialized into an HTTP request. A middleware
 | |
|  *   in this step MUST serialize an HTTP request and populate the "@request"
 | |
|  *   parameter of a command with the request such that it is available to
 | |
|  *   subsequent middleware.
 | |
|  * - sign: The request is being signed and prepared to be sent over the wire.
 | |
|  *
 | |
|  * Middleware can be registered with a name to allow you to easily add a
 | |
|  * middleware before or after another middleware by name. This also allows you
 | |
|  * to remove a middleware by name (in addition to removing by instance).
 | |
|  */
 | |
| class HandlerList implements \Countable
 | |
| {
 | |
|     const INIT = 'init';
 | |
|     const VALIDATE = 'validate';
 | |
|     const BUILD = 'build';
 | |
|     const SIGN = 'sign';
 | |
| 
 | |
|     /** @var callable */
 | |
|     private $handler;
 | |
| 
 | |
|     /** @var array */
 | |
|     private $named = [];
 | |
| 
 | |
|     /** @var array */
 | |
|     private $sorted;
 | |
| 
 | |
|     /** @var callable|null */
 | |
|     private $interposeFn;
 | |
| 
 | |
|     /** @var array Steps (in reverse order) */
 | |
|     private $steps = [
 | |
|         self::SIGN     => [],
 | |
|         self::BUILD    => [],
 | |
|         self::VALIDATE => [],
 | |
|         self::INIT     => [],
 | |
|     ];
 | |
| 
 | |
|     /**
 | |
|      * @param callable $handler HTTP handler.
 | |
|      */
 | |
|     public function __construct(callable $handler = null)
 | |
|     {
 | |
|         $this->handler = $handler;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Dumps a string representation of the list.
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     public function __toString()
 | |
|     {
 | |
|         $str = '';
 | |
|         $i = 0;
 | |
| 
 | |
|         foreach (array_reverse($this->steps) as $k => $step) {
 | |
|             foreach (array_reverse($step) as $j => $tuple) {
 | |
|                 $str .= "{$i}) Step: {$k}, ";
 | |
|                 if ($tuple[1]) {
 | |
|                     $str .= "Name: {$tuple[1]}, ";
 | |
|                 }
 | |
|                 $str .= "Function: " . $this->debugCallable($tuple[0]) . "\n";
 | |
|                 $i++;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if ($this->handler) {
 | |
|             $str .= "{$i}) Handler: " . $this->debugCallable($this->handler) . "\n";
 | |
|         }
 | |
| 
 | |
|         return $str;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Set the HTTP handler that actually returns a response.
 | |
|      *
 | |
|      * @param callable $handler Function that accepts a request and array of
 | |
|      *                          options and returns a Promise.
 | |
|      */
 | |
|     public function setHandler(callable $handler)
 | |
|     {
 | |
|         $this->handler = $handler;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns true if the builder has a handler.
 | |
|      *
 | |
|      * @return bool
 | |
|      */
 | |
|     public function hasHandler()
 | |
|     {
 | |
|         return (bool) $this->handler;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Append a middleware to the init step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function appendInit(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::INIT, $name, $middleware);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepend a middleware to the init step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function prependInit(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::INIT, $name, $middleware, true);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Append a middleware to the validate step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function appendValidate(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::VALIDATE, $name, $middleware);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepend a middleware to the validate step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function prependValidate(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::VALIDATE, $name, $middleware, true);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Append a middleware to the build step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function appendBuild(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::BUILD, $name, $middleware);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepend a middleware to the build step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function prependBuild(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::BUILD, $name, $middleware, true);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Append a middleware to the sign step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function appendSign(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::SIGN, $name, $middleware);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Prepend a middleware to the sign step.
 | |
|      *
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param string   $name       Name of the middleware.
 | |
|      */
 | |
|     public function prependSign(callable $middleware, $name = null)
 | |
|     {
 | |
|         $this->add(self::SIGN, $name, $middleware, true);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add a middleware before the given middleware by name.
 | |
|      *
 | |
|      * @param string|callable $findName   Add before this
 | |
|      * @param string          $withName   Optional name to give the middleware
 | |
|      * @param callable        $middleware Middleware to add.
 | |
|      */
 | |
|     public function before($findName, $withName, callable $middleware)
 | |
|     {
 | |
|         $this->splice($findName, $withName, $middleware, true);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add a middleware after the given middleware by name.
 | |
|      *
 | |
|      * @param string|callable $findName   Add after this
 | |
|      * @param string          $withName   Optional name to give the middleware
 | |
|      * @param callable        $middleware Middleware to add.
 | |
|      */
 | |
|     public function after($findName, $withName, callable $middleware)
 | |
|     {
 | |
|         $this->splice($findName, $withName, $middleware, false);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Remove a middleware by name or by instance from the list.
 | |
|      *
 | |
|      * @param string|callable $nameOrInstance Middleware to remove.
 | |
|      */
 | |
|     public function remove($nameOrInstance)
 | |
|     {
 | |
|         if (is_callable($nameOrInstance)) {
 | |
|             $this->removeByInstance($nameOrInstance);
 | |
|         } elseif (is_string($nameOrInstance)) {
 | |
|             $this->removeByName($nameOrInstance);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Interpose a function between each middleware (e.g., allowing for a trace
 | |
|      * through the middleware layers).
 | |
|      *
 | |
|      * The interpose function is a function that accepts a "step" argument as a
 | |
|      * string and a "name" argument string. This function must then return a
 | |
|      * function that accepts the next handler in the list. This function must
 | |
|      * then return a function that accepts a CommandInterface and optional
 | |
|      * RequestInterface and returns a promise that is fulfilled with an
 | |
|      * Aws\ResultInterface or rejected with an Aws\Exception\AwsException
 | |
|      * object.
 | |
|      *
 | |
|      * @param callable|null $fn Pass null to remove any previously set function
 | |
|      */
 | |
|     public function interpose(callable $fn = null)
 | |
|     {
 | |
|         $this->sorted = null;
 | |
|         $this->interposeFn = $fn;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Compose the middleware and handler into a single callable function.
 | |
|      *
 | |
|      * @return callable
 | |
|      */
 | |
|     public function resolve()
 | |
|     {
 | |
|         if (!($prev = $this->handler)) {
 | |
|             throw new \LogicException('No handler has been specified');
 | |
|         }
 | |
| 
 | |
|         if ($this->sorted === null) {
 | |
|             $this->sortMiddleware();
 | |
|         }
 | |
| 
 | |
|         foreach ($this->sorted as $fn) {
 | |
|             $prev = $fn($prev);
 | |
|         }
 | |
| 
 | |
|         return $prev;
 | |
|     }
 | |
| 
 | |
|     public function count()
 | |
|     {
 | |
|         return count($this->steps[self::INIT])
 | |
|             + count($this->steps[self::VALIDATE])
 | |
|             + count($this->steps[self::BUILD])
 | |
|             + count($this->steps[self::SIGN]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Splices a function into the middleware list at a specific position.
 | |
|      *
 | |
|      * @param          $findName
 | |
|      * @param          $withName
 | |
|      * @param callable $middleware
 | |
|      * @param          $before
 | |
|      */
 | |
|     private function splice($findName, $withName, callable $middleware, $before)
 | |
|     {
 | |
|         if (!isset($this->named[$findName])) {
 | |
|             throw new \InvalidArgumentException("$findName not found");
 | |
|         }
 | |
| 
 | |
|         $idx = $this->sorted = null;
 | |
|         $step = $this->named[$findName];
 | |
| 
 | |
|         if ($withName) {
 | |
|             $this->named[$withName] = $step;
 | |
|         }
 | |
| 
 | |
|         foreach ($this->steps[$step] as $i => $tuple) {
 | |
|             if ($tuple[1] === $findName) {
 | |
|                 $idx = $i;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         $replacement = $before
 | |
|             ? [$this->steps[$step][$idx], [$middleware, $withName]]
 | |
|             : [[$middleware, $withName], $this->steps[$step][$idx]];
 | |
|         array_splice($this->steps[$step], $idx, 1, $replacement);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Provides a debug string for a given callable.
 | |
|      *
 | |
|      * @param array|callable $fn Function to write as a string.
 | |
|      *
 | |
|      * @return string
 | |
|      */
 | |
|     private function debugCallable($fn)
 | |
|     {
 | |
|         if (is_string($fn)) {
 | |
|             return "callable({$fn})";
 | |
|         }
 | |
| 
 | |
|         if (is_array($fn)) {
 | |
|             $ele = is_string($fn[0]) ? $fn[0] : get_class($fn[0]);
 | |
|             return "callable(['{$ele}', '{$fn[1]}'])";
 | |
|         }
 | |
| 
 | |
|         return 'callable(' . spl_object_hash($fn) . ')';
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sort the middleware, and interpose if needed in the sorted list.
 | |
|      */
 | |
|     private function sortMiddleware()
 | |
|     {
 | |
|         $this->sorted = [];
 | |
| 
 | |
|         if (!$this->interposeFn) {
 | |
|             foreach ($this->steps as $step) {
 | |
|                 foreach ($step as $fn) {
 | |
|                     $this->sorted[] = $fn[0];
 | |
|                 }
 | |
|             }
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $ifn = $this->interposeFn;
 | |
|         // Interpose the interposeFn into the handler stack.
 | |
|         foreach ($this->steps as $stepName => $step) {
 | |
|             foreach ($step as $fn) {
 | |
|                 $this->sorted[] = $ifn($stepName, $fn[1]);
 | |
|                 $this->sorted[] = $fn[0];
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private function removeByName($name)
 | |
|     {
 | |
|         if (!isset($this->named[$name])) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $this->sorted = null;
 | |
|         $step = $this->named[$name];
 | |
|         $this->steps[$step] = array_values(
 | |
|             array_filter(
 | |
|                 $this->steps[$step],
 | |
|                 function ($tuple) use ($name) {
 | |
|                     return $tuple[1] !== $name;
 | |
|                 }
 | |
|             )
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     private function removeByInstance(callable $fn)
 | |
|     {
 | |
|         foreach ($this->steps as $k => $step) {
 | |
|             foreach ($step as $j => $tuple) {
 | |
|                 if ($tuple[0] === $fn) {
 | |
|                     $this->sorted = null;
 | |
|                     unset($this->named[$this->steps[$k][$j][1]]);
 | |
|                     unset($this->steps[$k][$j]);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Add a middleware to a step.
 | |
|      *
 | |
|      * @param string   $step       Middleware step.
 | |
|      * @param string   $name       Middleware name.
 | |
|      * @param callable $middleware Middleware function to add.
 | |
|      * @param bool     $prepend    Prepend instead of append.
 | |
|      */
 | |
|     private function add($step, $name, callable $middleware, $prepend = false)
 | |
|     {
 | |
|         $this->sorted = null;
 | |
| 
 | |
|         if ($prepend) {
 | |
|             $this->steps[$step][] = [$middleware, $name];
 | |
|         } else {
 | |
|             array_unshift($this->steps[$step], [$middleware, $name]);
 | |
|         }
 | |
| 
 | |
|         if ($name) {
 | |
|             $this->named[$name] = $step;
 | |
|         }
 | |
|     }
 | |
| }
 | 
