From c29889ca52850630810683d4c05cb64e3c6a6595 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Fri, 29 Dec 2023 05:02:47 +0700 Subject: [PATCH 1/5] Remove redundant dependency --- composer.json | 1 - src/FileRouter.php | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 48aa138..cfeb96f 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,6 @@ "roave/infection-static-analysis-plugin": "^1.34", "spatie/phpunit-watcher": "^1.23", "vimeo/psalm": "^5.16", - "yiisoft/strings": "^2.4", "yiisoft/test-support": "^3.0" }, "autoload": { diff --git a/src/FileRouter.php b/src/FileRouter.php index ec9a3c9..e71df78 100644 --- a/src/FileRouter.php +++ b/src/FileRouter.php @@ -9,7 +9,6 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; use Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher; -use Yiisoft\Strings\StringHelper; final class FileRouter implements MiddlewareInterface { @@ -89,15 +88,15 @@ private function parseController(ServerRequestInterface $request): mixed fn(array $matches) => strtoupper($matches[1]), $path, ); - $directoryPath = StringHelper::directoryName($controllerName); - - $controllerName = StringHelper::basename($controllerName); + $directoryPath = dirname($controllerName); + $controllerName = basename($controllerName); } $controller = $controllerName . $this->classPostfix; + $className = str_replace( - ['/', '\\\\'], - ['\\', '\\'], + ['\\/\\', '\\/', '\\\\'], + '\\', $this->namespace . '\\' . $this->baseControllerDirectory . '\\' . $directoryPath . '\\' . $controller ); From b327afec5bae875f29aabb0996e94f3ba4ba99ff Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Fri, 29 Dec 2023 05:03:02 +0700 Subject: [PATCH 2/5] Fix tools errors --- composer.json | 1 + src/FileRouter.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/composer.json b/composer.json index cfeb96f..bc3e363 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ "require": { "php": "^8.1", "psr/http-message": "^2.0", + "psr/http-server-handler": "^1.0", "psr/http-server-middleware": "^1.0", "yiisoft/middleware-dispatcher": "^5.2" }, diff --git a/src/FileRouter.php b/src/FileRouter.php index e71df78..62fab97 100644 --- a/src/FileRouter.php +++ b/src/FileRouter.php @@ -52,6 +52,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } $action = $this->parseAction($request); + if ($action === null) { + return $handler->handle($request); + } if (!method_exists($controllerClass, $action)) { return $handler->handle($request); From e2c55b9c8a7862aa86f9905de0e5e51d0b990086 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Fri, 29 Dec 2023 05:17:17 +0700 Subject: [PATCH 3/5] Cover trailing slash case --- src/FileRouter.php | 10 +++++++--- tests/FileRouterTest.php | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/FileRouter.php b/src/FileRouter.php index 62fab97..e15a79d 100644 --- a/src/FileRouter.php +++ b/src/FileRouter.php @@ -79,7 +79,7 @@ private function parseAction(ServerRequestInterface $request): ?string }; } - private function parseController(ServerRequestInterface $request): mixed + private function parseController(ServerRequestInterface $request): ?string { $path = $request->getUri()->getPath(); if ($path === '/') { @@ -91,8 +91,12 @@ private function parseController(ServerRequestInterface $request): mixed fn(array $matches) => strtoupper($matches[1]), $path, ); - $directoryPath = dirname($controllerName); - $controllerName = basename($controllerName); + + if (!preg_match('#^(.*?)/([^/]*)/?$#', $controllerName, $matches)) { + return null; + } + $directoryPath = $matches[1]; + $controllerName = $matches[2]; } $controller = $controllerName . $this->classPostfix; diff --git a/tests/FileRouterTest.php b/tests/FileRouterTest.php index 9769798..04abadf 100644 --- a/tests/FileRouterTest.php +++ b/tests/FileRouterTest.php @@ -42,6 +42,27 @@ public function testMiddleware(): void $this->assertEquals('x-header-value', $response->getHeaderLine('X-Header-Name')); } + public function testTrailingSlash(): void + { + /** + * @var FileRouter $router + */ + $router = $this->createRouter(); + $router = $router + ->withNamespace('Yiisoft\FileRouter\Tests\Support\App1'); + + $handler = $this->createExceptionHandler(); + $request = new ServerRequest( + method: 'GET', + uri: '/user/', + ); + + $response = $router->process($request, $handler); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('Hello, index!', (string) $response->getBody()); + } + #[DataProvider('dataRouter')] public function testRouter(string $method, string $uri, string $expectedResponse): void { From 6ec3bc0c9bc8a9cdb221f0cee7948e4003f613ab Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Fri, 29 Dec 2023 05:27:57 +0700 Subject: [PATCH 4/5] Increase coverage --- src/FileRouter.php | 24 ++++----- tests/FileRouterTest.php | 51 +++++++++++++++++++ .../App1/Controller/IndexController.php | 4 ++ 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/FileRouter.php b/src/FileRouter.php index e15a79d..5481c39 100644 --- a/src/FileRouter.php +++ b/src/FileRouter.php @@ -51,7 +51,16 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($controllerClass === null) { return $handler->handle($request); } - $action = $this->parseAction($request); + $actions = $controllerClass::$actions ?? [ + 'HEAD' => 'head', + 'OPTIONS' => 'options', + 'GET' => 'index', + 'POST' => 'create', + 'PUT' => 'update', + 'DELETE' => 'delete', + ]; + $action = $actions[$request->getMethod()] ?? null; + if ($action === null) { return $handler->handle($request); } @@ -68,17 +77,6 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $middlewareDispatcher->dispatch($request, $handler); } - private function parseAction(ServerRequestInterface $request): ?string - { - return match ($request->getMethod()) { - 'HEAD', 'GET' => 'index', - 'POST' => 'create', - 'PUT' => 'update', - 'DELETE' => 'delete', - default => throw new \Exception('Not implemented.'), - }; - } - private function parseController(ServerRequestInterface $request): ?string { $path = $request->getUri()->getPath(); @@ -92,7 +90,7 @@ private function parseController(ServerRequestInterface $request): ?string $path, ); - if (!preg_match('#^(.*?)/([^/]*)/?$#', $controllerName, $matches)) { + if (!preg_match('#^(.*?)/([^/]+)/?$#', $controllerName, $matches)) { return null; } $directoryPath = $matches[1]; diff --git a/tests/FileRouterTest.php b/tests/FileRouterTest.php index 04abadf..ba156a6 100644 --- a/tests/FileRouterTest.php +++ b/tests/FileRouterTest.php @@ -110,6 +110,23 @@ public static function dataRouter(): iterable } public function testUnsupportedMethod(): void + { + $router = $this->createRouter(); + $router = $router + ->withNamespace('Yiisoft\FileRouter\Tests\Support\App1'); + + $handler = $this->createExceptionHandler(); + $request = new ServerRequest( + method: 'HEAD', + uri: '/', + ); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Not implemented from tests.'); + $router->process($request, $handler); + } + + public function testNotImplementedAction(): void { $router = $this->createRouter(); $router = $router @@ -126,6 +143,40 @@ public function testUnsupportedMethod(): void $router->process($request, $handler); } + public function testUnknownController(): void + { + $router = $this->createRouter(); + $router = $router + ->withNamespace('Yiisoft\FileRouter\Tests\Support\App1'); + + $handler = $this->createExceptionHandler(); + $request = new ServerRequest( + method: 'DELETE', + uri: '/test/123', + ); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Not implemented from tests.'); + $router->process($request, $handler); + } + + public function testIncorrectUrl(): void + { + $router = $this->createRouter(); + $router = $router + ->withNamespace('Yiisoft\FileRouter\Tests\Support\App1'); + + $handler = $this->createExceptionHandler(); + $request = new ServerRequest( + method: 'DELETE', + uri: '/test//123///', + ); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Not implemented from tests.'); + $router->process($request, $handler); + } + public function testBaseController(): void { $router = $this->createRouter(); diff --git a/tests/Support/App1/Controller/IndexController.php b/tests/Support/App1/Controller/IndexController.php index 88abb86..a2bfeed 100644 --- a/tests/Support/App1/Controller/IndexController.php +++ b/tests/Support/App1/Controller/IndexController.php @@ -9,6 +9,10 @@ class IndexController { + public static array $actions = [ + 'GET' => 'index', + 'DELETE' => 'delete', + ]; public function index(): ResponseInterface { return new TextResponse('Hello, index!', 200, ['X-Header-Name' => 'X-Header-Value']); From c0a17c23d43c17a097543169d9727baf55ec6562 Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Fri, 29 Dec 2023 05:32:59 +0700 Subject: [PATCH 5/5] Fix psalm --- src/FileRouter.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/FileRouter.php b/src/FileRouter.php index 5481c39..1073135 100644 --- a/src/FileRouter.php +++ b/src/FileRouter.php @@ -47,10 +47,14 @@ public function withNamespace(string $namespace): self public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + /** + * @psalm-var class-string|null $controllerClass + */ $controllerClass = $this->parseController($request); if ($controllerClass === null) { return $handler->handle($request); } + /** @psalm-suppress InvalidPropertyFetch */ $actions = $controllerClass::$actions ?? [ 'HEAD' => 'head', 'OPTIONS' => 'options', @@ -69,6 +73,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } + /** @psalm-suppress InvalidPropertyFetch */ $middlewares = $controllerClass::$middlewares[$action] ?? []; $middlewares[] = [$controllerClass, $action];