diff --git a/src/Tempest/Router/src/GenericRouter.php b/src/Tempest/Router/src/GenericRouter.php index 8134d87d7..87cbe43e0 100644 --- a/src/Tempest/Router/src/GenericRouter.php +++ b/src/Tempest/Router/src/GenericRouter.php @@ -25,6 +25,7 @@ use Tempest\View\View; use function Tempest\map; +use function Tempest\Support\Regex\replace; use function Tempest\Support\str; /** @@ -173,6 +174,19 @@ public function toUri(array|string $action, ...$params): string return $uri->toString(); } + public function isCurrentUri(array|string $action, ...$params): bool + { + $matchedRoute = $this->container->get(MatchedRoute::class); + $candidateUri = $this->toUri($action, ...[...$matchedRoute->params, ...$params]); + $currentUri = $this->toUri([$matchedRoute->route->handler->getDeclaringClass(), $matchedRoute->route->handler->getName()]); + + foreach ($matchedRoute->params as $key => $value) { + $currentUri = replace($currentUri, '/({' . preg_quote($key, '/') . '(?::.*?)?})/', $value); + } + + return $currentUri === $candidateUri; + } + private function createResponse(Response|View $input): Response { if ($input instanceof View) { diff --git a/src/Tempest/Router/src/Router.php b/src/Tempest/Router/src/Router.php index dd0f5e1fd..6ce44229b 100644 --- a/src/Tempest/Router/src/Router.php +++ b/src/Tempest/Router/src/Router.php @@ -10,8 +10,16 @@ interface Router { public function dispatch(Request|PsrRequest $request): Response; + /** + * Creates a valid URI to the given `$action`. + */ public function toUri(array|string $action, ...$params): string; + /** + * Checks if the URI to the given `$action` would match the current route. + */ + public function isCurrentUri(array|string $action, ...$params): bool; + /** * @template T of \Tempest\Router\HttpMiddleware * @param class-string $middlewareClass diff --git a/src/Tempest/Router/src/functions.php b/src/Tempest/Router/src/functions.php index 8a7e704fc..704af0612 100644 --- a/src/Tempest/Router/src/functions.php +++ b/src/Tempest/Router/src/functions.php @@ -25,4 +25,24 @@ function uri(array|string|MethodReflector $action, mixed ...$params): string ...$params, ); } + + /** + * Checks whether the given controller action matches the current URI. + */ + function is_current_uri(array|string|MethodReflector $action, mixed ...$params): bool + { + if ($action instanceof MethodReflector) { + $action = [ + $action->getDeclaringClass()->getName(), + $action->getName(), + ]; + } + + $router = get(Router::class); + + return $router->isCurrentUri( + $action, + ...$params, + ); + } } diff --git a/tests/Integration/Route/RouterTest.php b/tests/Integration/Route/RouterTest.php index c192fbf1b..a241cd295 100644 --- a/tests/Integration/Route/RouterTest.php +++ b/tests/Integration/Route/RouterTest.php @@ -11,6 +11,7 @@ use Tempest\Database\Migrations\CreateMigrationsTable; use Tempest\Http\Status; use Tempest\Router\GenericRouter; +use Tempest\Router\MatchedRoute; use Tempest\Router\Responses\Ok; use Tempest\Router\Router; use Tests\Tempest\Fixtures\Controllers\ControllerWithEnumBinding; @@ -209,4 +210,41 @@ public function test_json_request(): void $this->assertSame(Status::OK, $response->status); $this->assertSame('foo', $response->body); } + + public function test_is_current_uri(): void + { + $router = $this->container->get(GenericRouter::class); + + $this->http->get('/test')->assertOk(); + + $this->assertTrue($router->isCurrentUri([TestController::class, '__invoke'])); + $this->assertFalse($router->isCurrentUri([TestController::class, 'withParams'])); + $this->assertFalse($router->isCurrentUri([TestController::class, 'withParams'], id: 1)); + $this->assertFalse($router->isCurrentUri([TestController::class, 'withParams'], id: 1, name: 'a')); + } + + public function test_is_current_uri_with_constrained_parameters(): void + { + $router = $this->container->get(GenericRouter::class); + + $this->http->get('/test/1/a')->assertOk(); + + $this->assertTrue($router->isCurrentUri([TestController::class, 'withCustomRegexParams'])); + $this->assertTrue($router->isCurrentUri([TestController::class, 'withCustomRegexParams'], id: 1)); + $this->assertTrue($router->isCurrentUri([TestController::class, 'withCustomRegexParams'], id: 1, name: 'a')); + $this->assertFalse($router->isCurrentUri([TestController::class, 'withCustomRegexParams'], id: 1, name: 'b')); + $this->assertFalse($router->isCurrentUri([TestController::class, 'withCustomRegexParams'], id: 0, name: 'a')); + $this->assertFalse($router->isCurrentUri([TestController::class, 'withCustomRegexParams'], id: 0, name: 'b')); + } + + public function test_is_current_uri_with_enum(): void + { + $router = $this->container->get(GenericRouter::class); + + $this->http->get('/with-enum/foo')->assertOk(); + + $this->assertTrue($router->isCurrentUri(ControllerWithEnumBinding::class)); + $this->assertTrue($router->isCurrentUri(ControllerWithEnumBinding::class, input: EnumForController::FOO)); + $this->assertFalse($router->isCurrentUri(ControllerWithEnumBinding::class, input: EnumForController::BAR)); + } }