Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,19 @@ public function __construct(...$middleware)
// only log for built-in webserver and PHP development webserver by default, others have their own access log
$needsAccessLog = (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') ? $container : null;

// remember if RouteHandler is added explicitly
$router = null;

if ($middleware) {
$needsErrorHandlerNext = false;
foreach ($middleware as $handler) {
// if explicit RouteHandler is given, it must be last in the chain
if ($router !== null) {
throw new \TypeError('RouteHandler must not be followed by other handlers');
}

// load required internal classes from last Container
if (\in_array($handler, [AccessLogHandler::class, ErrorHandler::class, Container::class], true)) {
if (\in_array($handler, [AccessLogHandler::class, ErrorHandler::class, Container::class, RouteHandler::class], true)) {
$handler = $container->getObject($handler);
}

Expand Down Expand Up @@ -88,6 +96,9 @@ public function __construct(...$middleware)
$needsAccessLog = null;
$needsErrorHandlerNext = true;
}
if ($handler instanceof RouteHandler) {
$router = $handler;
}
}
}
if ($needsErrorHandlerNext) {
Expand All @@ -108,8 +119,12 @@ public function __construct(...$middleware)
}
}

$this->router = new RouteHandler($container);
$handlers[] = $this->router;
// add default RouteHandler as last handler in middleware chain
if ($router === null) {
$handlers[] = $router = $container->getObject(RouteHandler::class);
}

$this->router = $router;
$this->handler = new MiddlewareHandler($handlers);
$this->sapi = \PHP_SAPI === 'cli' ? new ReactiveHandler($container->getEnv('X_LISTEN')) : new SapiHandler();
}
Expand Down
3 changes: 3 additions & 0 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace FrameworkX;

use FrameworkX\Io\RouteHandler;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;

Expand Down Expand Up @@ -163,6 +164,8 @@ public function getObject(string $class) /*: object (PHP 7.2+) */
// fallback for missing required internal classes from PSR-11 adapter
if ($class === Container::class) {
return $this; // @phpstan-ignore-line returns instanceof `T`
} elseif ($class === RouteHandler::class) {
return new RouteHandler($this); // @phpstan-ignore-line returns instanceof `T`
}
return new $class();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Io/RouteHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ class RouteHandler
/** @var Container */
private $container;

public function __construct(?Container $container = null)
public function __construct(Container $container)
{
$this->routeCollector = new RouteCollector(new RouteParser(), new RouteGenerator());
$this->errorHandler = new ErrorHandler();
$this->container = $container ?? new Container();
$this->container = $container;
}

/**
Expand Down
81 changes: 18 additions & 63 deletions tests/AppMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,171 +18,126 @@ class AppMiddlewareTest extends TestCase
{
public function testGetMethodWithMiddlewareAddsGetRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['GET'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->get('/', $middleware, $controller);
}

public function testHeadMethodWithMiddlewareAddsHeadRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['HEAD'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->head('/', $middleware, $controller);
}

public function testPostMethodWithMiddlewareAddsPostRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['POST'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->post('/', $middleware, $controller);
}

public function testPutMethodWithMiddlewareAddsPutRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['PUT'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->put('/', $middleware, $controller);
}

public function testPatchMethodWithMiddlewareAddsPatchRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['PATCH'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->patch('/', $middleware, $controller);
}

public function testDeleteMethodWithMiddlewareAddsDeleteRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['DELETE'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->delete('/', $middleware, $controller);
}

public function testOptionsMethodWithMiddlewareAddsOptionsRouteOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['OPTIONS'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->options('/', $middleware, $controller);
}

public function testAnyMethodWithMiddlewareAddsAllHttpMethodsOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->any('/', $middleware, $controller);
}

public function testMapMethodWithMiddlewareAddsGivenMethodsOnRouter(): void
{
$app = $this->createAppWithoutLogger();

$middleware = function () {};
$controller = function () { };

$router = $this->createMock(RouteHandler::class);
$router->expects($this->once())->method('map')->with(['GET', 'POST'], '/', $middleware, $controller);
assert($router instanceof RouteHandler);

$ref = new \ReflectionProperty($app, 'router');
if (PHP_VERSION_ID < 80100) {
$ref->setAccessible(true);
}
$ref->setValue($app, $router);
$app = new App($router);

$app->map(['GET', 'POST'], '/', $middleware, $controller);
}
Expand Down
Loading