Chris@0: push(Middleware::httpErrors(), 'http_errors'); Chris@0: $stack->push(Middleware::redirect(), 'allow_redirects'); Chris@0: $stack->push(Middleware::cookies(), 'cookies'); Chris@0: $stack->push(Middleware::prepareBody(), 'prepare_body'); Chris@0: Chris@0: return $stack; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param callable $handler Underlying HTTP handler. Chris@0: */ Chris@0: public function __construct(callable $handler = null) Chris@0: { Chris@0: $this->handler = $handler; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Invokes the handler stack as a composed handler Chris@0: * Chris@0: * @param RequestInterface $request Chris@0: * @param array $options Chris@0: */ Chris@0: public function __invoke(RequestInterface $request, array $options) Chris@0: { Chris@0: $handler = $this->resolve(); Chris@0: Chris@0: return $handler($request, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Dumps a string representation of the stack. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function __toString() Chris@0: { Chris@0: $depth = 0; Chris@0: $stack = []; Chris@0: if ($this->handler) { Chris@0: $stack[] = "0) Handler: " . $this->debugCallable($this->handler); Chris@0: } Chris@0: Chris@0: $result = ''; Chris@0: foreach (array_reverse($this->stack) as $tuple) { Chris@0: $depth++; Chris@0: $str = "{$depth}) Name: '{$tuple[1]}', "; Chris@0: $str .= "Function: " . $this->debugCallable($tuple[0]); Chris@0: $result = "> {$str}\n{$result}"; Chris@0: $stack[] = $str; Chris@0: } Chris@0: Chris@0: foreach (array_keys($stack) as $k) { Chris@0: $result .= "< {$stack[$k]}\n"; Chris@0: } Chris@0: Chris@0: return $result; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the HTTP handler that actually returns a promise. Chris@0: * Chris@0: * @param callable $handler Accepts a request and array of options and Chris@0: * returns a Promise. Chris@0: */ Chris@0: public function setHandler(callable $handler) Chris@0: { Chris@0: $this->handler = $handler; Chris@0: $this->cached = null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns true if the builder has a handler. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function hasHandler() Chris@0: { Chris@0: return (bool) $this->handler; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Unshift a middleware to the bottom of the stack. Chris@0: * Chris@0: * @param callable $middleware Middleware function Chris@0: * @param string $name Name to register for this middleware. Chris@0: */ Chris@0: public function unshift(callable $middleware, $name = null) Chris@0: { Chris@0: array_unshift($this->stack, [$middleware, $name]); Chris@0: $this->cached = null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Push a middleware to the top of the stack. Chris@0: * Chris@0: * @param callable $middleware Middleware function Chris@0: * @param string $name Name to register for this middleware. Chris@0: */ Chris@0: public function push(callable $middleware, $name = '') Chris@0: { Chris@0: $this->stack[] = [$middleware, $name]; Chris@0: $this->cached = null; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a middleware before another middleware by name. Chris@0: * Chris@0: * @param string $findName Middleware to find Chris@0: * @param callable $middleware Middleware function Chris@0: * @param string $withName Name to register for this middleware. Chris@0: */ Chris@0: public function before($findName, callable $middleware, $withName = '') Chris@0: { Chris@0: $this->splice($findName, $withName, $middleware, true); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Add a middleware after another middleware by name. Chris@0: * Chris@0: * @param string $findName Middleware to find Chris@0: * @param callable $middleware Middleware function Chris@0: * @param string $withName Name to register for this middleware. Chris@0: */ Chris@0: public function after($findName, callable $middleware, $withName = '') Chris@0: { Chris@0: $this->splice($findName, $withName, $middleware, false); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Remove a middleware by instance or name from the stack. Chris@0: * Chris@0: * @param callable|string $remove Middleware to remove by instance or name. Chris@0: */ Chris@0: public function remove($remove) Chris@0: { Chris@0: $this->cached = null; Chris@0: $idx = is_callable($remove) ? 0 : 1; Chris@0: $this->stack = array_values(array_filter( Chris@0: $this->stack, Chris@0: function ($tuple) use ($idx, $remove) { Chris@0: return $tuple[$idx] !== $remove; Chris@0: } Chris@0: )); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Compose the middleware and handler into a single callable function. Chris@0: * Chris@0: * @return callable Chris@0: */ Chris@0: public function resolve() Chris@0: { Chris@0: if (!$this->cached) { Chris@0: if (!($prev = $this->handler)) { Chris@0: throw new \LogicException('No handler has been specified'); Chris@0: } Chris@0: Chris@0: foreach (array_reverse($this->stack) as $fn) { Chris@0: $prev = $fn[0]($prev); Chris@0: } Chris@0: Chris@0: $this->cached = $prev; Chris@0: } Chris@0: Chris@0: return $this->cached; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param $name Chris@0: * @return int Chris@0: */ Chris@0: private function findByName($name) Chris@0: { Chris@0: foreach ($this->stack as $k => $v) { Chris@0: if ($v[1] === $name) { Chris@0: return $k; Chris@0: } Chris@0: } Chris@0: Chris@0: throw new \InvalidArgumentException("Middleware not found: $name"); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Splices a function into the middleware list at a specific position. Chris@0: * Chris@0: * @param $findName Chris@0: * @param $withName Chris@0: * @param callable $middleware Chris@0: * @param $before Chris@0: */ Chris@0: private function splice($findName, $withName, callable $middleware, $before) Chris@0: { Chris@0: $this->cached = null; Chris@0: $idx = $this->findByName($findName); Chris@0: $tuple = [$middleware, $withName]; Chris@0: Chris@0: if ($before) { Chris@0: if ($idx === 0) { Chris@0: array_unshift($this->stack, $tuple); Chris@0: } else { Chris@0: $replacement = [$tuple, $this->stack[$idx]]; Chris@0: array_splice($this->stack, $idx, 1, $replacement); Chris@0: } Chris@0: } elseif ($idx === count($this->stack) - 1) { Chris@0: $this->stack[] = $tuple; Chris@0: } else { Chris@0: $replacement = [$this->stack[$idx], $tuple]; Chris@0: array_splice($this->stack, $idx, 1, $replacement); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides a debug string for a given callable. Chris@0: * Chris@0: * @param array|callable $fn Function to write as a string. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function debugCallable($fn) Chris@0: { Chris@0: if (is_string($fn)) { Chris@0: return "callable({$fn})"; Chris@0: } Chris@0: Chris@0: if (is_array($fn)) { Chris@0: return is_string($fn[0]) Chris@0: ? "callable({$fn[0]}::{$fn[1]})" Chris@0: : "callable(['" . get_class($fn[0]) . "', '{$fn[1]}'])"; Chris@0: } Chris@0: Chris@0: return 'callable(' . spl_object_hash($fn) . ')'; Chris@0: } Chris@0: }