peakphp/framework

View on GitHub
src/Http/Stack.php

Summary

Maintainability
A
45 mins
Test Coverage
<?php

declare(strict_types=1);

namespace Peak\Http;

use Peak\Http\Exception\EmptyStackException;
use Peak\Http\Exception\StackEndedWithoutResponseException;
use Peak\Http\Request\Exception\InvalidHandlerException;
use Peak\Blueprint\Common\ResourceResolver;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

use function current;
use function is_callable;
use function is_object;
use function next;
use function reset;

class Stack implements \Peak\Blueprint\Http\Stack
{
    /**
     * @var array
     */
    private $handlers;

    /**
     * @var mixed
     */
    private $nextHandler;

    /**
     * @var ResourceResolver
     */
    private $handlerResolver;

    /**
     * @var \Peak\Blueprint\Http\Stack
     */
    private $parentStack = null;

    /**
     * Stack constructor.
     *
     * @param array $handlers
     * @param ResourceResolver $handlerResolver
     */
    public function __construct(array $handlers, ResourceResolver $handlerResolver)
    {
        if (empty($handlers)) {
            throw new EmptyStackException($this);
        }

        $this->handlers = $handlers;
        $this->handlerResolver = $handlerResolver;
    }

    /**
     * @param \Peak\Blueprint\Http\Stack $parentStack
     */
    public function setParent(\Peak\Blueprint\Http\Stack $parentStack)
    {
        $this->parentStack = $parentStack;
    }

    /**
     * @return \Peak\Blueprint\Http\Stack
     */
    public function getParent(): \Peak\Blueprint\Http\Stack
    {
        return $this->parentStack;
    }

    /**
     * @return array
     */
    public function getHandlers(): array
    {
        return $this->handlers;
    }

    /**
     * Process an incoming server request and return a response, optionally delegating
     * response creation to a handler.
     *
     * @param ServerRequestInterface $request
     * @param RequestHandlerInterface $handler
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $this->nextHandler = (isset($this->nextHandler)) ? next($this->handlers) : current($this->handlers);

        // nextHandler is a stack
        if ($this->nextHandler instanceof \Peak\Blueprint\Http\Stack) {
            return $this->returnResponse($this->handleStack($this->nextHandler, $request));
        }

        // no more handlers, look for parent
        if ($this->nextHandler === false && isset($this->parentStack)) {
            return $this->returnResponse($this->parentStack->process($request, $this->parentStack));
        } elseif ($this->nextHandler === false) {
            throw new StackEndedWithoutResponseException($this);
        }

        // resolve nextHandler if not already a object
        $handlerInstance = $this->nextHandler;
        if (is_callable($handlerInstance) || !is_object($handlerInstance)) {
            $handlerInstance = $this->handlerResolver->resolve($this->nextHandler);
        }

        // how to call the handler (MiddlewareInterface or RequestHandlerInterface)
        if ($handlerInstance instanceof MiddlewareInterface) {
            return $this->returnResponse($handlerInstance->process($request, $this));
        } elseif($handlerInstance instanceof RequestHandlerInterface) {
            return $this->returnResponse($handlerInstance->handle($request));
        }

        // at this point, the handler is not good
        throw new InvalidHandlerException($handlerInstance);
    }

    /**
     * Handle the request and return a response.
     *
     * @param ServerRequestInterface $request
     * @return ResponseInterface
     */
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return $this->process($request, $this);
    }

    /**
     * Handle a child stack
     *
     * @param \Peak\Blueprint\Http\Stack $stack
     * @param ServerRequestInterface $request
     * @return ResponseInterface
     */
    protected function handleStack(\Peak\Blueprint\Http\Stack $stack, ServerRequestInterface $request): ResponseInterface
    {
        $stack->setParent($this);
        return $stack->handle($request);
    }

    /**
     * Reset the stack before returning the response,
     * This allow the stack to be re-handle without throwing exception
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    protected function returnResponse(ResponseInterface $response): ResponseInterface
    {
        $this->nextHandler = null;
        reset($this->handlers);
        return $response;
    }
}