avalanche-development/talus

View on GitHub
src/Talus.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/**
 * What what
 */

namespace AvalancheDevelopment\Talus;

use DomainException;
use Exception;
use InvalidArgumentException;

use AvalancheDevelopment\CrashPad\ErrorHandler;
use AvalancheDevelopment\SwaggerCasterMiddleware\Caster;
use AvalancheDevelopment\SwaggerHeaderMiddleware\Header;
use AvalancheDevelopment\SwaggerRouterMiddleware\Router;
use AvalancheDevelopment\SwaggerValidationMiddleware\Validation;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequestFactory;

class Talus implements LoggerAwareInterface
{

    use LoggerAwareTrait;

    use MiddlewareAwareTrait;

    /** @var array $swagger */
    protected $swagger;

    /** @var array $controllerList */
    protected $controllerList;

    /** @var callable $errorHandler */
    protected $errorHandler;

    /**
     * @param array $swagger
     */
    public function __construct(array $swagger)
    {
        $this->swagger = $swagger;

        $this->logger = new NullLogger;
        $this->errorHandler = new ErrorHandler;
    }

    /**
     * @param string $operationId
     * @param callable $controller
     */
    public function addController($operationId, $controller)
    {
        if (!is_callable($controller)) {
            throw new InvalidArgumentException('Controller must be callable');
        }

        $this->controllerList[$operationId] = $controller;
    }

    /**
     * @param callable $errorHandler
     */
    public function setErrorHandler($errorHandler)
    {
        if (!is_callable($errorHandler)) {
            throw new InvalidArgumentException('Error handler must be callable');
        }

        $this->errorHandler = $errorHandler;
    }

    public function run()
    {
        $request = $this->getRequest();
        $response = $this->getResponse();

        $this->logger->debug('Talus: walking through swagger doc looking for dispatch');

        $this->buildMiddlewareStack();

        try {
            $result = $this->callStack($request, $response);
        } catch (Exception $exception) {
            if ($this->errorHandler instanceof LoggerAwareInterface) {
                $this->errorHandler->setLogger($this->logger);
            }

            $result = $this->errorHandler->__invoke($request, $response, $exception);
        }

        $this->outputResponse($result);
    }

    public function buildMiddlewareStack()
    {
        $header = new Header;
        $header->setLogger($this->logger);
        $this->addMiddleware($header);

        $caster = new Caster;
        $caster->setLogger($this->logger);
        $this->addMiddleware($caster);

        $validation = new Validation;
        $validation->setLogger($this->logger);
        $this->addMiddleware($validation);

        $router = new Router($this->swagger);
        $router->setLogger($this->logger);
        $this->addMiddleware($router);
    }

    /**
     * @param ResponseInterface $response
     */
    protected function outputResponse(ResponseInterface $response)
    {
        header(
            sprintf('HTTP/1.1 %d %s', $response->getStatusCode(), $response->getReasonPhrase()),
            true,
            $response->getStatusCode()
        );

        if ($response->getHeaders() !== null) {
            foreach ($response->getHeaders() as $header => $values) {
                header(
                    sprintf('%s: %s', $header, implode(', ', $values)),
                    true
                );
            }
        }

        // todo do we care about chunking?
        echo (string) $response->getBody();
    }

    /**
     * @param RequestInterface $request
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    public function __invoke(RequestInterface $request, ResponseInterface $response)
    {
        $operation = $request->getAttribute('swagger')->getOperation()['operationId'];

        if (!array_key_exists($operation, $this->controllerList)) {
            throw new DomainException('Operation is not defined with a controller');
        }

        return call_user_func($this->controllerList[$operation], $request, $response);
    }

    /**
     * @return RequestInterface
     */
    protected function getRequest()
    {
        return ServerRequestFactory::fromGlobals();
    }

    /**
     * @return ResponseInterface
     */
    protected function getResponse()
    {
        return new Response();
    }
}