diff --git a/composer.json b/composer.json
index 283bc30a..f38bb972 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
diff --git a/config/app.php b/config/app.php
index 6278f193..9af35eb4 100644
--- a/config/app.php
+++ b/config/app.php
@@ -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,
],
];
diff --git a/config/routes.php b/config/routes.php
new file mode 100644
index 00000000..5296dbc7
--- /dev/null
+++ b/config/routes.php
@@ -0,0 +1,14 @@
+addRoute('GET', '/hello/{name}', function ($request) {
+ /** @var ServerRequestInterface $request */
+ $name = $request->getAttribute('name');
+
+ return response(sprintf('Hello %s!', htmlspecialchars($name)));
+});
diff --git a/src/Middleware/CallableHandler.php b/src/Middleware/CallableHandler.php
new file mode 100644
index 00000000..eb493bf1
--- /dev/null
+++ b/src/Middleware/CallableHandler.php
@@ -0,0 +1,77 @@
+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);
+ }
+}
diff --git a/src/Middleware/Dispatcher.php b/src/Middleware/Dispatcher.php
index f2a5b5d5..48eb0948 100644
--- a/src/Middleware/Dispatcher.php
+++ b/src/Middleware/Dispatcher.php
@@ -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
*/
diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php
index 714141de..276fb3ee 100644
--- a/src/Middleware/LegacyMiddleware.php
+++ b/src/Middleware/LegacyMiddleware.php
@@ -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) . '
'
- ]));
+ ]), $status);
}
}
diff --git a/src/Middleware/NotFoundResponse.php b/src/Middleware/NotFoundResponse.php
deleted file mode 100644
index f9431c1d..00000000
--- a/src/Middleware/NotFoundResponse.php
+++ /dev/null
@@ -1,56 +0,0 @@
-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) . '
'
- ]), 404);
- }
-}
diff --git a/src/Middleware/RequestHandler.php b/src/Middleware/RequestHandler.php
new file mode 100644
index 00000000..e1381abf
--- /dev/null
+++ b/src/Middleware/RequestHandler.php
@@ -0,0 +1,50 @@
+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));
+ }
+}
diff --git a/src/Middleware/RequestHandlerServiceProvider.php b/src/Middleware/RequestHandlerServiceProvider.php
new file mode 100644
index 00000000..c6488118
--- /dev/null
+++ b/src/Middleware/RequestHandlerServiceProvider.php
@@ -0,0 +1,17 @@
+app->make(RequestHandler::class);
+
+ $this->app->instance('request.handler', $requestHandler);
+ $this->app->bind(RequestHandler::class, 'request.handler');
+ }
+}
diff --git a/src/Middleware/ResolvesMiddlewareTrait.php b/src/Middleware/ResolvesMiddlewareTrait.php
new file mode 100644
index 00000000..76557ce6
--- /dev/null
+++ b/src/Middleware/ResolvesMiddlewareTrait.php
@@ -0,0 +1,56 @@
+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);
+ }
+}
diff --git a/src/Middleware/RouteDispatcher.php b/src/Middleware/RouteDispatcher.php
new file mode 100644
index 00000000..f14faea8
--- /dev/null
+++ b/src/Middleware/RouteDispatcher.php
@@ -0,0 +1,75 @@
+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);
+ }
+}
diff --git a/src/Middleware/RouteDispatcherServiceProvider.php b/src/Middleware/RouteDispatcherServiceProvider.php
new file mode 100644
index 00000000..3b4fa183
--- /dev/null
+++ b/src/Middleware/RouteDispatcherServiceProvider.php
@@ -0,0 +1,41 @@
+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');
+ });
+ }
+}
diff --git a/tests/Unit/Middleware/CallableHandlerTest.php b/tests/Unit/Middleware/CallableHandlerTest.php
new file mode 100644
index 00000000..6e6dab58
--- /dev/null
+++ b/tests/Unit/Middleware/CallableHandlerTest.php
@@ -0,0 +1,141 @@
+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);
+ }
+}
diff --git a/tests/Unit/Middleware/DispatcherTest.php b/tests/Unit/Middleware/DispatcherTest.php
index c01c5029..4e1c51a7 100644
--- a/tests/Unit/Middleware/DispatcherTest.php
+++ b/tests/Unit/Middleware/DispatcherTest.php
@@ -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));
}
}
diff --git a/tests/Unit/Middleware/LegacyMiddlewareTest.php b/tests/Unit/Middleware/LegacyMiddlewareTest.php
index 34e60b60..ed9a5a74 100644
--- a/tests/Unit/Middleware/LegacyMiddlewareTest.php
+++ b/tests/Unit/Middleware/LegacyMiddlewareTest.php
@@ -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);
diff --git a/tests/Unit/Middleware/NotFoundResponseTest.php b/tests/Unit/Middleware/NotFoundResponseTest.php
deleted file mode 100644
index 9279e81d..00000000
--- a/tests/Unit/Middleware/NotFoundResponseTest.php
+++ /dev/null
@@ -1,39 +0,0 @@
-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);
- }
-}
diff --git a/tests/Unit/Middleware/RequestHandlerServiceProviderTest.php b/tests/Unit/Middleware/RequestHandlerServiceProviderTest.php
new file mode 100644
index 00000000..281016b5
--- /dev/null
+++ b/tests/Unit/Middleware/RequestHandlerServiceProviderTest.php
@@ -0,0 +1,36 @@
+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();
+ }
+}
diff --git a/tests/Unit/Middleware/RequestHandlerTest.php b/tests/Unit/Middleware/RequestHandlerTest.php
new file mode 100644
index 00000000..896b55c3
--- /dev/null
+++ b/tests/Unit/Middleware/RequestHandlerTest.php
@@ -0,0 +1,89 @@
+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);
+ }
+}
diff --git a/tests/Unit/Middleware/ResolvesMiddlewareTraitTest.php b/tests/Unit/Middleware/ResolvesMiddlewareTraitTest.php
new file mode 100644
index 00000000..320a6d6b
--- /dev/null
+++ b/tests/Unit/Middleware/ResolvesMiddlewareTraitTest.php
@@ -0,0 +1,67 @@
+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');
+ }
+}
diff --git a/tests/Unit/Middleware/RouteDispatcherServiceProviderTest.php b/tests/Unit/Middleware/RouteDispatcherServiceProviderTest.php
new file mode 100644
index 00000000..ca784c73
--- /dev/null
+++ b/tests/Unit/Middleware/RouteDispatcherServiceProviderTest.php
@@ -0,0 +1,65 @@
+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();
+ }
+}
diff --git a/tests/Unit/Middleware/RouteDispatcherTest.php b/tests/Unit/Middleware/RouteDispatcherTest.php
new file mode 100644
index 00000000..edb2f158
--- /dev/null
+++ b/tests/Unit/Middleware/RouteDispatcherTest.php
@@ -0,0 +1,148 @@
+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);
+ }
+}
diff --git a/tests/Unit/Middleware/Stub/HasStaticMethod.php b/tests/Unit/Middleware/Stub/HasStaticMethod.php
new file mode 100644
index 00000000..5ca2670e
--- /dev/null
+++ b/tests/Unit/Middleware/Stub/HasStaticMethod.php
@@ -0,0 +1,8 @@
+container = $container;
+ }
+
+ /**
+ * @param string|callable|MiddlewareInterface|RequestHandlerInterface $middleware
+ * @return MiddlewareInterface|RequestHandlerInterface
+ * @throws InvalidArgumentException
+ */
+ public function callResolveMiddleware($middleware)
+ {
+ return $this->resolveMiddleware($middleware);
+ }
+}