From dae902023722a67398a578302f8bc03969809c2a Mon Sep 17 00:00:00 2001 From: wpjscc Date: Wed, 18 Oct 2023 19:11:32 +0800 Subject: [PATCH] add route group --- src/App.php | 36 +++++++- tests/AppTest.php | 204 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 2 deletions(-) diff --git a/src/App.php b/src/App.php index c746ceb..0572933 100644 --- a/src/App.php +++ b/src/App.php @@ -25,6 +25,12 @@ class App /** @var ReactiveHandler|SapiHandler */ private $sapi; + /** @var string */ + protected $currentGroupPrefix; + + /** @var callable|class-string[] */ + protected $currentGroupHandlers = []; + /** * Instantiate new X application * @@ -190,7 +196,27 @@ public function options(string $route, $handler, ...$handlers): void */ public function any(string $route, $handler, ...$handlers): void { - $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $route, $handler, ...$handlers); + $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $this->currentGroupPrefix . $route, $handler, ...$handlers); + } + + /** + * Create a route group with a common prefix. + * + * All routes created in the passed callback will have the given group prefix prepended. + * + * @param string $prefix + * @param callable|class-string[] $handlers + * @param callable $callback + */ + public function addGroup(string $prefix, array $handlers, callable $callback): void + { + $previousGroupPrefix = $this->currentGroupPrefix; + $previousGroupHandlers = $this->currentGroupHandlers; + $this->currentGroupPrefix = $previousGroupPrefix . $prefix; + $this->currentGroupHandlers = array_merge($previousGroupHandlers, $handlers); + $callback($this); + $this->currentGroupPrefix = $previousGroupPrefix; + $this->currentGroupHandlers = $previousGroupHandlers; } /** @@ -202,7 +228,13 @@ public function any(string $route, $handler, ...$handlers): void */ public function map(array $methods, string $route, $handler, ...$handlers): void { - $this->router->map($methods, $route, $handler, ...$handlers); + if (!empty($this->currentGroupHandlers)) { + \array_unshift($handlers, $handler); + $currentGroupHandlers = $this->currentGroupHandlers; + $handler = array_pop($currentGroupHandlers); + \array_unshift($handlers, ...$currentGroupHandlers); + } + $this->router->map($methods, $this->currentGroupPrefix . $route, $handler, ...$handlers); } /** diff --git a/tests/AppTest.php b/tests/AppTest.php index d59955d..0d29bea 100644 --- a/tests/AppTest.php +++ b/tests/AppTest.php @@ -1649,6 +1649,210 @@ public function testInvokeWithMatchingRouteReturnsInternalServerErrorResponseWhe $this->assertStringContainsString("

Expected request handler to return Psr\Http\Message\ResponseInterface but got null.

\n", (string) $response->getBody()); } + public function testInvokeWithMatchingGroupRouteReturnsResponseFromMatchingRouteHandler(): void + { + $app = $this->createAppWithoutLogger(); + $app->addGroup('/users', [], function ($app) { + $app->get('', function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK\n" + ); + }); + }); + $request = new ServerRequest('GET', 'http://localhost/users'); + + $response = $app($request); + assert($response instanceof ResponseInterface); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/html', $response->getHeaderLine('Content-Type')); + $this->assertEquals("OK\n", (string) $response->getBody()); + } + + public function testInvokeWithMatchingChildGroupRouteReturnsResponseFromMatchingRouteHandler(): void + { + $app = $this->createAppWithoutLogger(); + $app->addGroup('/users', [], function ($app) { + $app->addGroup('/{name}', [], function ($app) { + $app->get('/posts/{post}', function (ServerRequestInterface $request) { + $name = $request->getAttribute('name'); + $post = $request->getAttribute('post'); + assert(is_string($name)); + assert(is_string($post)); + + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK $name $post\n" + ); + }); + }); + }); + $request = new ServerRequest('GET', 'http://localhost/users/alice/posts/first'); + + $response = $app($request); + assert($response instanceof ResponseInterface); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/html', $response->getHeaderLine('Content-Type')); + $this->assertEquals("OK alice first\n", (string) $response->getBody()); + } + + public function testConstractAndGroupRouteWithMiddlewareReturnContructMiddleware(): void + { + + $middleware1 = function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK1\n" + ); + }; + $middleware2 = function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK2\n" + ); + }; + + $app = $this->createAppWithoutLogger( + $middleware1 + ); + $app->addGroup('/users', [ + $middleware2 + ], function ($app) { + $app->get('', function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK3\n" + ); + }); + }); + $request = new ServerRequest('GET', 'http://localhost/users'); + + $response = $app($request); + assert($response instanceof ResponseInterface); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/html', $response->getHeaderLine('Content-Type')); + $this->assertEquals("OK1\n", (string) $response->getBody()); + } + + public function testGroupRouteWithMiddlewareReturnGroupMiddleware(): void + { + + $middleware2 = function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK2\n" + ); + }; + + $app = $this->createAppWithoutLogger(); + $app->addGroup('/users', [ + $middleware2 + ], function ($app) { + $app->get('', function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK3\n" + ); + }); + }); + $request = new ServerRequest('GET', 'http://localhost/users'); + + $response = $app($request); + assert($response instanceof ResponseInterface); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/html', $response->getHeaderLine('Content-Type')); + $this->assertEquals("OK2\n", (string) $response->getBody()); + } + + public function testGroupRouteWithMiddlewareReturnRouteMiddleware(): void + { + + $middleware2 = function ($request, $next) { + return $next($request); + }; + + $app = $this->createAppWithoutLogger(); + $app->addGroup('/users', [ + $middleware2 + ], function ($app) { + $app->get('', function () { + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK3\n" + ); + }); + }); + $request = new ServerRequest('GET', 'http://localhost/users'); + + $response = $app($request); + assert($response instanceof ResponseInterface); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/html', $response->getHeaderLine('Content-Type')); + $this->assertEquals("OK3\n", (string) $response->getBody()); + } + + public function testSetRequestGroupRouteWithMiddlewareAndReturnRouteMiddleware(): void + { + + $middleware2 = function ($request, $next) { + return $next($request->withAttribute('group', 'group')); + }; + + $app = $this->createAppWithoutLogger(); + $app->addGroup('/users', [ + $middleware2 + ], function ($app) { + $app->get('', function ($request) { + $group = $request->getAttribute('group'); + assert(is_string($group)); + return new Response( + 200, + [ + 'Content-Type' => 'text/html' + ], + "OK {$group}\n" + ); + }); + }); + $request = new ServerRequest('GET', 'http://localhost/users'); + + $response = $app($request); + assert($response instanceof ResponseInterface); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('text/html', $response->getHeaderLine('Content-Type')); + $this->assertEquals("OK group\n", (string) $response->getBody()); + } + private function createAppWithoutLogger(callable ...$middleware): App { $app = new App(...$middleware);