You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
http/src/Server.php

218 lines
6.5 KiB
PHP

<?php
namespace BitCommunism\Http;
use DI\Container;
use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\ServerRequest;
use Invoker\Invoker;
use Invoker\ParameterResolver\AssociativeArrayResolver;
use Invoker\ParameterResolver\Container\TypeHintContainerResolver;
use Invoker\ParameterResolver\DefaultValueResolver;
use Invoker\ParameterResolver\NumericArrayResolver;
use Invoker\ParameterResolver\ResolverChain;
use Invoker\ParameterResolver\TypeHintResolver;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use function FastRoute\simpleDispatcher;
use function GuzzleHttp\Psr7\copy_to_stream;
use function GuzzleHttp\Psr7\stream_for;
class Server
{
private $routes = [];
private $container;
/**
* @var Invoker
*/
private $invoker;
/**
* @var Dispatcher
*/
private $router;
public function __construct($routes, Container $container)
{
$this->routes = $routes;
$this->container = $container;
$this->invoker = $this->createDefaultInvoker();
}
private function iterateRoutes()
{
yield from $this->iterateRoutesFromArray($this->routes);
}
private function iterateRoutesFromArray($routeArr, $prefix = '', $known = [], $middleware = [])
{
$route = $routeArr['middleware'] ?? [];
$middleware = array_merge($middleware, array_map(function ($route) {
if (!$route instanceof Handle) {
return new Handle($route);
}
return $route;
}, (array)$route));
foreach ($routeArr as $key => $route) {
if ($key === 'middleware') {
continue;
}
if (is_array($route) && !(isset($route[0]) && isset($route[1]))) {
yield from $this->iterateRoutesFromArray($route, $prefix . $key, $known, $middleware);
continue;
}
if (!$route instanceof Handle) {
$route = new Handle($route);
}
$route = $route->withPrependedMiddleware($middleware);
if ($key[0] !== '/') {
yield [$key, $prefix, $route];
continue;
}
yield ['GET', $prefix . $key, $route];
}
}
public function buildRoutes()
{
$this->router = simpleDispatcher(function (RouteCollector $collector) {
foreach ($this->iterateRoutes() as $route) {
$collector->addRoute(...$route);
}
});
}
public function serve()
{
$request = ServerRequest::fromGlobals();
$response = $this->respond($request);
header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
foreach ($response->getHeaders() as $headerName => $headerValues) {
$first = true;
foreach ($headerValues as $headerValue) {
header($headerName . ': ' . $headerValue, $first === true);
$first = false;
}
}
$stdout = fopen('php://output', 'w+');
copy_to_stream($response->getBody(), stream_for($stdout));
}
public function respond(ServerRequestInterface $request, ResponseInterface $response = null): ResponseInterface
{
$path = $request->getUri()->getPath();
$method = $request->getMethod();
$routeInfo = $this->router->dispatch($method, $path);
switch ($routeInfo[0]) {
case Dispatcher::NOT_FOUND:
// todo: Error page
break;
case Dispatcher::METHOD_NOT_ALLOWED:
// todo: Error page
break;
case Dispatcher::FOUND:
[$_, $handler, $vars] = $routeInfo;
$response = $this->handleHttp($request, $handler, $vars, $response);
break;
}
if ($response === null) {
$response = (new Response())
->withStatus(200);
}
return $response;
}
private function createDefaultInvoker() {
return new Invoker(new ResolverChain(array(
new NumericArrayResolver(),
new AssociativeArrayResolver(),
new DefaultValueResolver(),
new TypeHintResolver(),
new TypeHintContainerResolver($this->container),
)), $this->container);
}
public function handleHttp(ServerRequestInterface $request, Handle $handler, array $vars, ResponseInterface $response = null): ResponseInterface
{
$middlewareArray = $handler->getMiddleware();
$middlewarePointer = 0;
$next = null;
if ($response === null) {
$response = new Response(200, [], stream_for('Empty response'));
}
$context = new Context($request, $response, $vars);
$next = function () use ($context, $handler, $middlewareArray, &$middlewarePointer, &$next) {
$output = null;
if (count($middlewareArray) > $middlewarePointer) {
$handler = $middlewareArray[$middlewarePointer++];
}
$call = $handler->getCall();
if (is_array($call)) {
$obj = $this->container->get($call[0]);
$call = [$obj, $call[1]];
}
$context->setSettings($handler->getSettings());
$output = $this->invoker->call($call, [
'request' => $context->getRequest(),
ServerRequestInterface::class => $context->getRequest(),
'response' => $context->getResponse(),
ResponseInterface::class => $context->getResponse(),
'context' => $context,
Context::class => $context,
'vars' => $context->getVars(),
'settings' => $context->getSettings(),
'next' => $next,
]);
$this->normalizeResponse($output, $context);
};
$next();
return $context->renderResponse();
}
private function normalizeResponse($response, Context $context)
{
if ($response === null) {
return;
}
if ($response instanceof ResponseInterface) {
$context->setResponse($response);
return;
}
$body = stream_for($response);
$context->withResponse(function (ResponseInterface $response) use ($body) {
return $response->withBody($body);
});
}
}