| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 | <?phpnamespace 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';    const ATTEMPT = 'attempt';    /** @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::ATTEMPT  => [],        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);    }    /**     * Append a middleware to the attempt step.     *     * @param callable $middleware Middleware function to add.     * @param string   $name       Name of the middleware.     */    public function appendAttempt(callable $middleware, $name = null)    {        $this->add(self::ATTEMPT, $name, $middleware);    }    /**     * Prepend a middleware to the attempt step.     *     * @param callable $middleware Middleware function to add.     * @param string   $name       Name of the middleware.     */    public function prependAttempt(callable $middleware, $name = null)    {        $this->add(self::ATTEMPT, $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;    }    /**     * @return int     */    #[\ReturnTypeWillChange]    public function count()    {        return count($this->steps[self::INIT])            + count($this->steps[self::VALIDATE])            + count($this->steps[self::BUILD])            + count($this->steps[self::SIGN])            + count($this->steps[self::ATTEMPT]);    }    /**     * 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;        }    }}
 |