Added nikic/fast-route as routing dispatcher

main
Igor Scheller 6 years ago
parent 73c9d923e7
commit a1bc763a16

@ -22,6 +22,7 @@
"illuminate/container": "5.5.*",
"illuminate/database": "5.5.*",
"illuminate/support": "^5.5",
"nikic/fast-route": "^1.3",
"psr/container": "^1.0",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.0",

@ -15,13 +15,15 @@ return [
\Engelsystem\Http\SessionServiceProvider::class,
\Engelsystem\Http\ResponseServiceProvider::class,
\Engelsystem\Http\Psr7ServiceProvider::class,
\Engelsystem\Middleware\RouteDispatcherServiceProvider::class,
\Engelsystem\Middleware\RequestHandlerServiceProvider::class,
],
// Application middleware
'middleware' => [
\Engelsystem\Middleware\SendResponseHandler::class,
\Engelsystem\Middleware\ExceptionHandler::class,
\Engelsystem\Middleware\LegacyMiddleware::class,
\Engelsystem\Middleware\NotFoundResponse::class,
\Engelsystem\Middleware\RouteDispatcher::class,
\Engelsystem\Middleware\RequestHandler::class,
],
];

@ -0,0 +1,14 @@
<?php
use FastRoute\RouteCollector;
use Psr\Http\Message\ServerRequestInterface;
/** @var RouteCollector $route */
/** Demo route endpoint, TODO: Remove */
$route->addRoute('GET', '/hello/{name}', function ($request) {
/** @var ServerRequestInterface $request */
$name = $request->getAttribute('name');
return response(sprintf('Hello %s!', htmlspecialchars($name)));
});

@ -0,0 +1,77 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Container\Container;
use Engelsystem\Http\Response;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CallableHandler implements MiddlewareInterface, RequestHandlerInterface
{
/** @var callable */
protected $callable;
/** @var Container */
protected $container;
/**
* @param callable $callable The callable that should be wrapped
* @param Container $container
*/
public function __construct(callable $callable, Container $container = null)
{
$this->callable = $callable;
$this->container = $container;
}
/**
* 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
{
return $this->execute([$request, $handler]);
}
/**
* Handle the request and return a response.
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->execute([$request]);
}
/**
* Execute the callable and return a response
*
* @param array $arguments
* @return ResponseInterface
*/
protected function execute(array $arguments = []): ResponseInterface
{
$return = call_user_func_array($this->callable, $arguments);
if ($return instanceof ResponseInterface) {
return $return;
}
if (!$this->container instanceof Container) {
throw new InvalidArgumentException('Unable to resolve response');
}
/** @var Response $response */
$response = $this->container->get('response');
return $response->withContent($return);
}
}

@ -12,6 +12,8 @@ use Psr\Http\Server\RequestHandlerInterface;
class Dispatcher implements MiddlewareInterface, RequestHandlerInterface
{
use ResolvesMiddlewareTrait;
/** @var MiddlewareInterface[]|string[] */
protected $stack;
@ -70,10 +72,7 @@ class Dispatcher implements MiddlewareInterface, RequestHandlerInterface
throw new LogicException('Middleware queue is empty');
}
if (is_string($middleware)) {
$middleware = $this->resolveMiddleware($middleware);
}
$middleware = $this->resolveMiddleware($middleware);
if (!$middleware instanceof MiddlewareInterface) {
throw new InvalidArgumentException('Middleware is no instance of ' . MiddlewareInterface::class);
}
@ -81,25 +80,6 @@ class Dispatcher implements MiddlewareInterface, RequestHandlerInterface
return $middleware->process($request, $this);
}
/**
* Resolve the middleware with the container
*
* @param string $middleware
* @return MiddlewareInterface
*/
protected function resolveMiddleware($middleware)
{
if (!$this->container instanceof Application) {
throw new InvalidArgumentException('Unable to resolve middleware ' . $middleware);
}
if ($this->container->has($middleware)) {
return $this->container->get($middleware);
}
return $this->container->make($middleware);
}
/**
* @param Application $container
*/

@ -83,7 +83,9 @@ class LegacyMiddleware implements MiddlewareInterface
}
if (empty($title) and empty($content)) {
return $handler->handle($request);
$page = '404';
$title = _('Page not found');
$content = _('This page could not be found or you don\'t have permission to view it. You probably have to sign in or register in order to gain access!');
}
return $this->renderPage($page, $title, $content);
@ -270,10 +272,17 @@ class LegacyMiddleware implements MiddlewareInterface
$parameters = [
'key' => (isset($user) ? $user['api_key'] : ''),
];
if ($page == 'user_meetings') {
$parameters['meetings'] = 1;
}
$status = 200;
if ($page == '404') {
$status = 404;
$content = info($content, true);
}
return response(view(__DIR__ . '/../../templates/layout.html', [
'theme' => isset($user) ? $user['color'] : config('theme'),
'title' => $title,
@ -291,6 +300,6 @@ class LegacyMiddleware implements MiddlewareInterface
'contact_email' => config('contact_email'),
'locale' => locale(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]));
]), $status);
}
}

@ -1,56 +0,0 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Http\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class NotFoundResponse implements MiddlewareInterface
{
/**
* Returns a 404: Page not found response
*
* Should be the last middleware
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface {
$info = _('This page could not be found or you don\'t have permission to view it. You probably have to sign in or register in order to gain access!');
return $this->renderPage($info);
}
/**
* @param string $content
* @return Response
* @codeCoverageIgnore
*/
protected function renderPage($content)
{
global $user;
$event_config = EventConfig();
return response(view(__DIR__ . '/../../templates/layout.html', [
'theme' => isset($user) ? $user['color'] : config('theme'),
'title' => _('Page not found'),
'atom_link' => '',
'start_page_url' => page_link_to('/'),
'credits_url' => page_link_to('credits'),
'menu' => make_menu(),
'content' => msg() . info($content),
'header_toolbar' => header_toolbar(),
'faq_url' => config('faq_url'),
'contact_email' => config('contact_email'),
'locale' => locale(),
'event_info' => EventConfig_info($event_config) . ' <br />'
]), 404);
}
}

@ -0,0 +1,50 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Application;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RequestHandler implements MiddlewareInterface
{
use ResolvesMiddlewareTrait;
/** @var Application */
protected $container;
/**
* @param Application $container
*/
public function __construct(Application $container)
{
$this->container = $container;
}
/**
* 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
{
$requestHandler = $request->getAttribute('route-request-handler');
$requestHandler = $this->resolveMiddleware($requestHandler);
if ($requestHandler instanceof MiddlewareInterface) {
return $requestHandler->process($request, $handler);
}
if ($requestHandler instanceof RequestHandlerInterface) {
return $requestHandler->handle($request);
}
throw new InvalidArgumentException('Unable to process request handler of type ' . gettype($requestHandler));
}
}

@ -0,0 +1,17 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Container\ServiceProvider;
class RequestHandlerServiceProvider extends ServiceProvider
{
public function register()
{
/** @var RequestHandler $requestHandler */
$requestHandler = $this->app->make(RequestHandler::class);
$this->app->instance('request.handler', $requestHandler);
$this->app->bind(RequestHandler::class, 'request.handler');
}
}

@ -0,0 +1,56 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Application;
use InvalidArgumentException;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
trait ResolvesMiddlewareTrait
{
/**
* Resolve the middleware with the container
*
* @param string|callable|MiddlewareInterface|RequestHandlerInterface $middleware
* @return MiddlewareInterface|RequestHandlerInterface
*/
protected function resolveMiddleware($middleware)
{
if ($this->isMiddleware($middleware)) {
return $middleware;
}
if (!property_exists($this, 'container') || !$this->container instanceof Application) {
throw new InvalidArgumentException('Unable to resolve middleware');
}
/** @var Application $container */
$container = $this->container;
if (is_string($middleware)) {
$middleware = $container->make($middleware);
}
if (is_callable($middleware)) {
$middleware = $container->make(CallableHandler::class, ['callable' => $middleware]);
}
if ($this->isMiddleware($middleware)) {
return $middleware;
}
throw new InvalidArgumentException('Unable to resolve middleware');
}
/**
* Checks if the given object is a middleware or middleware or request handler
*
* @param mixed $middleware
* @return bool
*/
protected function isMiddleware($middleware)
{
return ($middleware instanceof MiddlewareInterface || $middleware instanceof RequestHandlerInterface);
}
}

@ -0,0 +1,75 @@
<?php
namespace Engelsystem\Middleware;
use FastRoute\Dispatcher as FastRouteDispatcher;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RouteDispatcher implements MiddlewareInterface
{
/** @var FastRouteDispatcher */
protected $dispatcher;
/** @var ResponseInterface */
protected $response;
/** @var MiddlewareInterface|null */
protected $notFound;
/**
* @param FastRouteDispatcher $dispatcher
* @param ResponseInterface $response Default response
* @param MiddlewareInterface|null $notFound Handles any requests if the route can't be found
*/
public function __construct(
FastRouteDispatcher $dispatcher,
ResponseInterface $response,
MiddlewareInterface $notFound = null
) {
$this->dispatcher = $dispatcher;
$this->response = $response;
$this->notFound = $notFound;
}
/**
* 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
{
$route = $this->dispatcher->dispatch($request->getMethod(), urldecode($request->getUri()->getPath()));
$status = $route[0];
if ($status == FastRouteDispatcher::NOT_FOUND) {
if ($this->notFound instanceof MiddlewareInterface) {
return $this->notFound->process($request, $handler);
}
return $this->response->withStatus(404);
}
if ($status == FastRouteDispatcher::METHOD_NOT_ALLOWED) {
$methods = $route[1];
return $this->response
->withStatus(405)
->withHeader('Allow', implode(', ', $methods));
}
$routeHandler = $route[1];
$request = $request->withAttribute('route-request-handler', $routeHandler);
$vars = $route[2];
foreach ($vars as $name => $value) {
$request = $request->withAttribute($name, $value);
}
return $handler->handle($request);
}
}

@ -0,0 +1,41 @@
<?php
namespace Engelsystem\Middleware;
use Engelsystem\Container\ServiceProvider;
use FastRoute\Dispatcher as FastRouteDispatcher;
use FastRoute\RouteCollector;
use Psr\Http\Server\MiddlewareInterface;
class RouteDispatcherServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->alias(RouteDispatcher::class, 'route.dispatcher');
$this->app
->when(RouteDispatcher::class)
->needs(FastRouteDispatcher::class)
->give(function () {
return $this->generateRouting();
});
$this->app
->when(RouteDispatcher::class)
->needs(MiddlewareInterface::class)
->give(LegacyMiddleware::class);
}
/**
* Includes the routes.php file
*
* @return FastRouteDispatcher
* @codeCoverageIgnore
*/
function generateRouting()
{
return \FastRoute\simpleDispatcher(function (RouteCollector $route) {
require config_path('routes.php');
});
}
}

@ -0,0 +1,141 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Container\Container;
use Engelsystem\Http\Response;
use Engelsystem\Middleware\CallableHandler;
use Engelsystem\Test\Unit\Middleware\Stub\HasStaticMethod;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use ReflectionClass as Reflection;
use stdClass;
class CallableHandlerTest extends TestCase
{
public function provideCallable()
{
return [
[function () { }],
[[$this, 'provideCallable']],
[[HasStaticMethod::class, 'foo']],
];
}
/**
* @dataProvider provideCallable
* @covers \Engelsystem\Middleware\CallableHandler::__construct
* @param callable $callable
*/
public function testInit($callable)
{
$handler = new CallableHandler($callable);
$reflection = new Reflection(get_class($handler));
$property = $reflection->getProperty('callable');
$property->setAccessible(true);
$this->assertEquals($callable, $property->getValue($handler));
}
/**
* @covers \Engelsystem\Middleware\CallableHandler::process
*/
public function testProcess()
{
/** @var ServerRequestInterface|MockObject $request */
/** @var ResponseInterface|MockObject $response */
/** @var callable|MockObject $callable */
/** @var RequestHandlerInterface|MockObject $handler */
list($request, $response, $callable, $handler) = $this->getMocks();
$callable->expects($this->once())
->method('__invoke')
->with($request, $handler)
->willReturn($response);
$middleware = new CallableHandler($callable);
$middleware->process($request, $handler);
}
/**
* @covers \Engelsystem\Middleware\CallableHandler::handle
*/
public function testHandler()
{
/** @var ServerRequestInterface|MockObject $request */
/** @var ResponseInterface|MockObject $response */
/** @var callable|MockObject $callable */
list($request, $response, $callable) = $this->getMocks();
$callable->expects($this->once())
->method('__invoke')
->with($request)
->willReturn($response);
$middleware = new CallableHandler($callable);
$middleware->handle($request);
}
/**
* @covers \Engelsystem\Middleware\CallableHandler::execute
*/
public function testExecute()
{
/** @var ServerRequestInterface|MockObject $request */
/** @var Response|MockObject $response */
/** @var callable|MockObject $callable */
list($request, $response, $callable) = $this->getMocks();
/** @var Container|MockObject $container */
$container = $this->createMock(Container::class);
$callable->expects($this->exactly(3))
->method('__invoke')
->with($request)
->willReturnOnConsecutiveCalls($response, 'Lorem ipsum?', 'I\'m not an exception!');
$container->expects($this->once())
->method('get')
->with('response')
->willReturn($response);
$response->expects($this->once())
->method('withContent')
->with('Lorem ipsum?')
->willReturn($response);
$middleware = new CallableHandler($callable, $container);
$return = $middleware->handle($request);
$this->assertInstanceOf(ResponseInterface::class, $return);
$this->assertEquals($response, $return);
$return = $middleware->handle($request);
$this->assertInstanceOf(ResponseInterface::class, $return);
$this->assertEquals($response, $return);
$middleware = new CallableHandler($callable);
$this->expectException(\InvalidArgumentException::class);
$middleware->handle($request);
}
/**
* @return array
*/
protected function getMocks(): array
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var Response|MockObject $response */
$response = $this->createMock(Response::class);
/** @var callable|MockObject $callable */
$callable = $this->getMockBuilder(stdClass::class)
->setMethods(['__invoke'])
->getMock();
return array($request, $response, $callable, $handler);
}
}

@ -5,7 +5,6 @@ namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Middleware\Dispatcher;
use Engelsystem\Test\Unit\Middleware\Stub\NotARealMiddleware;
use Engelsystem\Test\Unit\Middleware\Stub\ReturnResponseMiddleware;
use InvalidArgumentException;
use LogicException;
use PHPUnit\Framework\TestCase;
@ -158,14 +157,14 @@ class DispatcherTest extends TestCase
/** @var Dispatcher|MockObject $dispatcher */
$dispatcher = $this->getMockBuilder(Dispatcher::class)
->setConstructorArgs([[MiddlewareInterface::class]])
->setConstructorArgs([[MiddlewareInterface::class, MiddlewareInterface::class]])
->setMethods(['resolveMiddleware'])
->getMock();
$dispatcher->expects($this->once())
$dispatcher->expects($this->exactly(2))
->method('resolveMiddleware')
->with(MiddlewareInterface::class)
->willReturn($middleware);
->willReturnOnConsecutiveCalls($middleware, null);
$middleware->expects($this->once())
->method('process')
@ -174,57 +173,26 @@ class DispatcherTest extends TestCase
$return = $dispatcher->handle($request);
$this->assertEquals($response, $return);
$this->expectException(InvalidArgumentException::class);
$dispatcher->handle($request);
}
/**
* @covers \Engelsystem\Middleware\Dispatcher::resolveMiddleware
* @covers \Engelsystem\Middleware\Dispatcher::setContainer
*/
public function testResolveMiddleware()
public function testSetContainer()
{
/** @var Application|MockObject $container */
$container = $this->createMock(Application::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->createMock(ResponseInterface::class);
$returnResponseMiddleware = new ReturnResponseMiddleware($response);
$container->expects($this->exactly(2))
->method('has')
->withConsecutive([ReturnResponseMiddleware::class], ['middleware'])
->willReturnOnConsecutiveCalls(false, true);
$container->expects($this->once())
->method('make')
->with(ReturnResponseMiddleware::class)
->willReturn($returnResponseMiddleware);
$container->expects($this->once())
->method('get')
->with('middleware')
->willReturn($returnResponseMiddleware);
$dispatcher = new Dispatcher([ReturnResponseMiddleware::class]);
$dispatcher->setContainer($container);
$dispatcher->handle($request);
$dispatcher = new Dispatcher(['middleware'], $container);
$dispatcher->handle($request);
}
$middleware = new Dispatcher();
$middleware->setContainer($container);
/**
* @covers \Engelsystem\Middleware\Dispatcher::resolveMiddleware
*/
public function testResolveMiddlewareNoContainer()
{
/** @var ServerRequestInterface|MockObject $request */
$request = $this->createMock(ServerRequestInterface::class);
$this->expectException(InvalidArgumentException::class);
$reflection = new Reflection(get_class($middleware));
$property = $reflection->getProperty('container');
$property->setAccessible(true);
$dispatcher = new Dispatcher([ReturnResponseMiddleware::class]);
$dispatcher->handle($request);
$this->assertEquals($container, $property->getValue($middleware));
}
}

@ -46,10 +46,11 @@ class LegacyMiddlewareTest extends TestCase
['title2', 'content2']
);
$middleware->expects($this->exactly(2))
$middleware->expects($this->exactly(3))
->method('renderPage')
->withConsecutive(
['user_worklog', 'title', 'content'],
['404', 'Page not found'],
['login', 'title2', 'content2']
)
->willReturn($response);
@ -73,11 +74,6 @@ class LegacyMiddlewareTest extends TestCase
'/'
);
$handler->expects($this->once())
->method('handle')
->with($request)
->willReturn($response);
$middleware->process($request, $handler);
$middleware->process($request, $handler);
$middleware->process($request, $handler);

@ -1,39 +0,0 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\NotFoundResponse;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class NotFoundResponseTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\NotFoundResponse::process
*/
public function testRegister()
{
/** @var NotFoundResponse|MockObject $middleware */
$middleware = $this->getMockBuilder(NotFoundResponse::class)
->setMethods(['renderPage'])
->getMock();
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
$middleware->expects($this->once())
->method('renderPage')
->willReturn($response);
$handler->expects($this->never())
->method('handle');
$middleware->process($request, $handler);
}
}

@ -0,0 +1,36 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\RequestHandler;
use Engelsystem\Middleware\RequestHandlerServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
use PHPUnit\Framework\MockObject\MockObject;
class RequestHandlerServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Middleware\RequestHandlerServiceProvider::register()
*/
public function testRegister()
{
/** @var RequestHandler|MockObject $requestHandler */
$requestHandler = $this->createMock(RequestHandler::class);
$app = $this->getApp(['make', 'instance', 'bind']);
$app->expects($this->once())
->method('make')
->with(RequestHandler::class)
->willReturn($requestHandler);
$app->expects($this->once())
->method('instance')
->with('request.handler', $requestHandler);
$app->expects($this->once())
->method('bind')
->with(RequestHandler::class, 'request.handler');
$serviceProvider = new RequestHandlerServiceProvider($app);
$serviceProvider->register();
}
}

@ -0,0 +1,89 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Middleware\RequestHandler;
use InvalidArgumentException;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use ReflectionClass as Reflection;
class RequestHandlerTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\RequestHandler::__construct
*/
public function testInit()
{
/** @var Application|MockObject $container */
$container = $this->createMock(Application::class);
$handler = new RequestHandler($container);
$reflection = new Reflection(get_class($handler));
$property = $reflection->getProperty('container');
$property->setAccessible(true);
$this->assertEquals($container, $property->getValue($handler));
}
/**
* @covers \Engelsystem\Middleware\RequestHandler::process
*/
public function testProcess()
{
/** @var Application|MockObject $container */
$container = $this->createMock(Application::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
$middlewareInterface = $this->getMockForAbstractClass(MiddlewareInterface::class);
$requestHandlerInterface = $this->getMockForAbstractClass(RequestHandlerInterface::class);
$request->expects($this->exactly(3))
->method('getAttribute')
->with('route-request-handler')
->willReturn('FooBarClass');
/** @var RequestHandler|MockObject $middleware */
$middleware = $this->getMockBuilder(RequestHandler::class)
->setConstructorArgs([$container])
->setMethods(['resolveMiddleware'])
->getMock();
$middleware->expects($this->exactly(3))
->method('resolveMiddleware')
->with('FooBarClass')
->willReturnOnConsecutiveCalls(
$middlewareInterface,
$requestHandlerInterface,
null
);
$middlewareInterface->expects($this->once())
->method('process')
->with($request, $handler)
->willReturn($response);
$requestHandlerInterface->expects($this->once())
->method('handle')
->with($request)
->willReturn($response);
$return = $middleware->process($request, $handler);
$this->assertEquals($return, $response);
$middleware->process($request, $handler);
$this->assertEquals($return, $response);
$this->expectException(InvalidArgumentException::class);
$middleware->process($request, $handler);
}
}

@ -0,0 +1,67 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Application;
use Engelsystem\Middleware\CallableHandler;
use Engelsystem\Test\Unit\Middleware\Stub\HasStaticMethod;
use Engelsystem\Test\Unit\Middleware\Stub\ResolvesMiddlewareTraitImplementation;
use InvalidArgumentException;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Server\MiddlewareInterface;
class ResolvesMiddlewareTraitTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\ResolvesMiddlewareTrait::resolveMiddleware
* @covers \Engelsystem\Middleware\ResolvesMiddlewareTrait::isMiddleware
*/
public function testResolveMiddleware()
{
/** @var Application|MockObject $container */
$container = $this->createMock(Application::class);
$middlewareInterface = $this->getMockForAbstractClass(MiddlewareInterface::class);
$callable = [HasStaticMethod::class, 'foo'];
$container->expects($this->exactly(3))
->method('make')
->withConsecutive(
['FooBarClass'],
[CallableHandler::class, ['callable' => $callable]],
['UnresolvableClass']
)
->willReturnOnConsecutiveCalls(
$middlewareInterface,
$middlewareInterface,
null
);
$middleware = new ResolvesMiddlewareTraitImplementation($container);
$return = $middleware->callResolveMiddleware('FooBarClass');
$this->assertEquals($middlewareInterface, $return);
$return = $middleware->callResolveMiddleware($callable);
$this->assertEquals($middlewareInterface, $return);
$this->expectException(InvalidArgumentException::class);
$middleware->callResolveMiddleware('UnresolvableClass');
}
/**
* @covers \Engelsystem\Middleware\ResolvesMiddlewareTrait::resolveMiddleware
*/
public function testResolveMiddlewareNoContainer()
{
$middlewareInterface = $this->getMockForAbstractClass(MiddlewareInterface::class);
$middleware = new ResolvesMiddlewareTraitImplementation();
$return = $middleware->callResolveMiddleware($middlewareInterface);
$this->assertEquals($middlewareInterface, $return);
$this->expectException(InvalidArgumentException::class);
$middleware->callResolveMiddleware('FooBarClass');
}
}

@ -0,0 +1,65 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\LegacyMiddleware;
use Engelsystem\Middleware\RouteDispatcher;
use Engelsystem\Middleware\RouteDispatcherServiceProvider;
use Engelsystem\Test\Unit\ServiceProviderTest;
use FastRoute\Dispatcher as FastRouteDispatcher;
use Illuminate\Contracts\Container\ContextualBindingBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Http\Server\MiddlewareInterface;
class RouteDispatcherServiceProviderTest extends ServiceProviderTest
{
/**
* @covers \Engelsystem\Middleware\RouteDispatcherServiceProvider::register()
*/
public function testRegister()
{
$bindingBuilder = $this->createMock(ContextualBindingBuilder::class);
$routeDispatcher = $this->getMockForAbstractClass(FastRouteDispatcher::class);
$app = $this->getApp(['alias', 'when']);
$app->expects($this->once())
->method('alias')
->with(RouteDispatcher::class, 'route.dispatcher');
$app->expects($this->exactly(2))
->method('when')
->with(RouteDispatcher::class)
->willReturn($bindingBuilder);
$bindingBuilder->expects($this->exactly(2))
->method('needs')
->withConsecutive(
[FastRouteDispatcher::class],
[MiddlewareInterface::class]
)
->willReturn($bindingBuilder);
$bindingBuilder->expects($this->exactly(2))
->method('give')
->with($this->callback(function ($subject) {
if (is_callable($subject)) {
$subject();
}
return is_callable($subject) || $subject == LegacyMiddleware::class;
}));
/** @var RouteDispatcherServiceProvider|MockObject $serviceProvider */
$serviceProvider = $this->getMockBuilder(RouteDispatcherServiceProvider::class)
->setConstructorArgs([$app])
->setMethods(['generateRouting'])
->getMock();
$serviceProvider->expects($this->once())
->method('generateRouting')
->willReturn($routeDispatcher);
$serviceProvider->register();
}
}

@ -0,0 +1,148 @@
<?php
namespace Engelsystem\Test\Unit\Middleware;
use Engelsystem\Middleware\RouteDispatcher;
use FastRoute\Dispatcher as FastRouteDispatcher;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RouteDispatcherTest extends TestCase
{
/**
* @covers \Engelsystem\Middleware\RouteDispatcher::process
* @covers \Engelsystem\Middleware\RouteDispatcher::__construct
*/
public function testProcess()
{
/** @var FastRouteDispatcher|MockObject $dispatcher */
/** @var ResponseInterface|MockObject $response */
/** @var ServerRequestInterface|MockObject $request */
/** @var RequestHandlerInterface|MockObject $handler */
list($dispatcher, $response, $request, $handler) = $this->getMocks();
$dispatcher->expects($this->once())
->method('dispatch')
->with('HEAD', '/foo!bar')
->willReturn([FastRouteDispatcher::FOUND, $handler, ['foo' => 'bar', 'lorem' => 'ipsum']]);
$request->expects($this->exactly(3))
->method('withAttribute')
->withConsecutive(
['route-request-handler', $handler],
['foo', 'bar'],
['lorem', 'ipsum']
)
->willReturn($request);
$handler->expects($this->once())
->method('handle')
->with($request)
->willReturn($response);
$middleware = new RouteDispatcher($dispatcher, $response);
$return = $middleware->process($request, $handler);
$this->assertEquals($response, $return);
}
/**
* @covers \Engelsystem\Middleware\RouteDispatcher::process
*/
public function testProcessNotFound()
{
/** @var FastRouteDispatcher|MockObject $dispatcher */
/** @var ResponseInterface|MockObject $response */
/** @var ServerRequestInterface|MockObject $request */
/** @var RequestHandlerInterface|MockObject $handler */
list($dispatcher, $response, $request, $handler) = $this->getMocks();
/** @var MiddlewareInterface|MockObject $notFound */
$notFound = $this->createMock(MiddlewareInterface::class);
$dispatcher->expects($this->exactly(2))
->method('dispatch')
->with('HEAD', '/foo!bar')
->willReturn([FastRouteDispatcher::NOT_FOUND]);
$response->expects($this->once())
->method('withStatus')
->with(404)
->willReturn($response);
$notFound->expects($this->once())
->method('process')
->with($request, $handler)
->willReturn($response);
$middleware = new RouteDispatcher($dispatcher, $response, $notFound);
$return = $middleware->process($request, $handler);
$this->assertEquals($response, $return);
$middleware = new RouteDispatcher($dispatcher, $response);
$return = $middleware->process($request, $handler);
$this->assertEquals($response, $return);
}
/**
* @covers \Engelsystem\Middleware\RouteDispatcher::process
*/
public function testProcessNotAllowed()
{
/** @var FastRouteDispatcher|MockObject $dispatcher */
/** @var ResponseInterface|MockObject $response */
/** @var ServerRequestInterface|MockObject $request */
/** @var RequestHandlerInterface|MockObject $handler */
list($dispatcher, $response, $request, $handler) = $this->getMocks();
$dispatcher->expects($this->once())
->method('dispatch')
->with('HEAD', '/foo!bar')
->willReturn([FastRouteDispatcher::METHOD_NOT_ALLOWED, ['POST', 'TEST']]);
$response->expects($this->once())
->method('withStatus')
->with(405)
->willReturn($response);
$response->expects($this->once())
->method('withHeader')
->with('Allow', 'POST, TEST')
->willReturn($response);
$middleware = new RouteDispatcher($dispatcher, $response);
$return = $middleware->process($request, $handler);
$this->assertEquals($response, $return);
}
/**
* @return array
*/
protected function getMocks(): array
{
/** @var FastRouteDispatcher|MockObject $dispatcher */
$dispatcher = $this->getMockForAbstractClass(FastRouteDispatcher::class);
/** @var ResponseInterface|MockObject $response */
$response = $this->getMockForAbstractClass(ResponseInterface::class);
/** @var ServerRequestInterface|MockObject $request */
$request = $this->getMockForAbstractClass(ServerRequestInterface::class);
/** @var RequestHandlerInterface|MockObject $handler */
$handler = $this->getMockForAbstractClass(RequestHandlerInterface::class);
/** @var UriInterface|MockObject $uriInterface */
$uriInterface = $this->getMockForAbstractClass(UriInterface::class);
$request->expects($this->atLeastOnce())
->method('getMethod')
->willReturn('HEAD');
$request->expects($this->atLeastOnce())
->method('getUri')
->willReturn($uriInterface);
$uriInterface->expects($this->atLeastOnce())
->method('getPath')
->willReturn('/foo%21bar');
return array($dispatcher, $response, $request, $handler);
}
}

@ -0,0 +1,8 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
class HasStaticMethod
{
public static function foo() { }
}

@ -0,0 +1,35 @@
<?php
namespace Engelsystem\Test\Unit\Middleware\Stub;
use Engelsystem\Application;
use Engelsystem\Middleware\ResolvesMiddlewareTrait;
use InvalidArgumentException;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ResolvesMiddlewareTraitImplementation
{
use ResolvesMiddlewareTrait;
/** @var Application */
protected $container;
/**
* @param Application $container
*/
public function __construct(Application $container = null)
{
$this->container = $container;
}
/**
* @param string|callable|MiddlewareInterface|RequestHandlerInterface $middleware
* @return MiddlewareInterface|RequestHandlerInterface
* @throws InvalidArgumentException
*/
public function callResolveMiddleware($middleware)
{
return $this->resolveMiddleware($middleware);
}
}
Loading…
Cancel
Save