diff --git a/src/Tempest/Auth/src/AuthorizerMiddleware.php b/src/Tempest/Auth/src/AuthorizerMiddleware.php index 963fc9243..976eff167 100644 --- a/src/Tempest/Auth/src/AuthorizerMiddleware.php +++ b/src/Tempest/Auth/src/AuthorizerMiddleware.php @@ -5,26 +5,27 @@ namespace Tempest\Auth; use Tempest\Container\Container; +use Tempest\Container\Tag; use Tempest\Http\HttpMiddleware; use Tempest\Http\HttpMiddlewareCallable; -use Tempest\Http\MatchedRoute; use Tempest\Http\Request; use Tempest\Http\Response; use Tempest\Http\Responses\Forbidden; +use Tempest\Http\Route; final readonly class AuthorizerMiddleware implements HttpMiddleware { public function __construct( private Authenticator $authenticator, - private MatchedRoute $matchedRoute, + #[Tag('current')] + private Route $currentRoute, private Container $container, ) { } public function __invoke(Request $request, HttpMiddlewareCallable $next): Response { - $attribute = $this->matchedRoute - ->route + $attribute = $this->currentRoute ->handler ->getAttribute(Allow::class); diff --git a/src/Tempest/Http/src/GenericRouter.php b/src/Tempest/Http/src/GenericRouter.php index 53805527d..28b085a54 100644 --- a/src/Tempest/Http/src/GenericRouter.php +++ b/src/Tempest/Http/src/GenericRouter.php @@ -51,8 +51,9 @@ public function dispatch(Request|PsrRequest $request): Response } $this->container->singleton( - MatchedRoute::class, - fn () => $matchedRoute, + className: Route::class, + definition: fn () => $matchedRoute, + tag: 'current' ); $callable = $this->getCallable($matchedRoute); @@ -69,14 +70,12 @@ public function dispatch(Request|PsrRequest $request): Response return $response; } - private function getCallable(MatchedRoute $matchedRoute): HttpMiddlewareCallable + private function getCallable(Route $route): HttpMiddlewareCallable { - $route = $matchedRoute->route; - - $callControllerAction = function (Request $request) use ($route, $matchedRoute) { + $callControllerAction = function (Request $request) use ($route) { $response = $this->container->invoke( $route->handler, - ...$matchedRoute->params, + ...$route->params, ); if ($response === null) { @@ -164,13 +163,13 @@ private function createResponse(Response|View $input): Response return $input; } - private function resolveRequest(PsrRequest $psrRequest, MatchedRoute $matchedRoute): Request + private function resolveRequest(PsrRequest $psrRequest, Route $matchedRoute): Request { // Let's find out if our input request data matches what the route's action needs $requestClass = GenericRequest::class; // We'll loop over all the handler's parameters - foreach ($matchedRoute->route->handler->getParameters() as $parameter) { + foreach ($matchedRoute->handler->getParameters() as $parameter) { // If the parameter's type is an instance of Request… if ($parameter->getType()->matches(Request::class)) { diff --git a/src/Tempest/Http/src/Route.php b/src/Tempest/Http/src/Route.php index 3cc696c8b..827648fd9 100644 --- a/src/Tempest/Http/src/Route.php +++ b/src/Tempest/Http/src/Route.php @@ -16,7 +16,7 @@ class Route public readonly bool $isDynamic; /** @var string[] Route parameters */ - public readonly array $params; + public array $params; public const string DEFAULT_MATCHING_GROUP = '[^/]++'; @@ -48,7 +48,7 @@ public function setHandler(MethodReflector $handler): self /** @return string[] */ public static function getRouteParams(string $uriPart): array { - $regex = '#\{'. self::ROUTE_PARAM_NAME_REGEX . self::ROUTE_PARAM_CUSTOM_REGEX .'\}#'; + $regex = '#\{' . self::ROUTE_PARAM_NAME_REGEX . self::ROUTE_PARAM_CUSTOM_REGEX . '\}#'; preg_match_all($regex, $uriPart, $matches); @@ -58,15 +58,20 @@ public static function getRouteParams(string $uriPart): array /** * Splits the route URI into separate segments * - * @example '/test/{id}/edit' becomes ['test', '{id}', 'edit'] * @return string[] + * @example '/test/{id}/edit' becomes ['test', '{id}', 'edit'] */ public function split(): array { $parts = explode('/', $this->uri); return array_values( - array_filter($parts, static fn (string $part) => $part !== '') + array_filter($parts, static fn (string $part) => $part !== ''), ); } + + public function setParams(array $params): self + { + $this->params = $params; + } } diff --git a/src/Tempest/Http/src/RouteEnumBindingInitializer.php b/src/Tempest/Http/src/RouteEnumBindingInitializer.php index a6ff3229d..007f639a9 100644 --- a/src/Tempest/Http/src/RouteEnumBindingInitializer.php +++ b/src/Tempest/Http/src/RouteEnumBindingInitializer.php @@ -19,11 +19,11 @@ public function canInitialize(ClassReflector $class): bool public function initialize(ClassReflector $class, Container $container): object { - $matchedRoute = $container->get(MatchedRoute::class); + $matchedRoute = $container->get(Route::class, tag: 'current'); $parameter = null; - foreach ($matchedRoute->route->handler->getParameters() as $searchParameter) { + foreach ($matchedRoute->handler->getParameters() as $searchParameter) { if ($searchParameter->getType()->equals($class->getType())) { $parameter = $searchParameter; diff --git a/src/Tempest/Http/src/RouteModelBindingInitializer.php b/src/Tempest/Http/src/RouteModelBindingInitializer.php index 479f76e03..2ca4e91a0 100644 --- a/src/Tempest/Http/src/RouteModelBindingInitializer.php +++ b/src/Tempest/Http/src/RouteModelBindingInitializer.php @@ -20,11 +20,11 @@ public function canInitialize(ClassReflector $class): bool public function initialize(ClassReflector $class, Container $container): object { - $matchedRoute = $container->get(MatchedRoute::class); + $matchedRoute = $container->get(Route::class, tag: 'current'); $parameter = null; - foreach ($matchedRoute->route->handler->getParameters() as $searchParameter) { + foreach ($matchedRoute->handler->getParameters() as $searchParameter) { if ($searchParameter->getType()->equals($class->getType())) { $parameter = $searchParameter; diff --git a/src/Tempest/Http/src/Routing/Matching/GenericRouteMatcher.php b/src/Tempest/Http/src/Routing/Matching/GenericRouteMatcher.php index a10968dcc..95bc28701 100644 --- a/src/Tempest/Http/src/Routing/Matching/GenericRouteMatcher.php +++ b/src/Tempest/Http/src/Routing/Matching/GenericRouteMatcher.php @@ -5,7 +5,6 @@ namespace Tempest\Http\Routing\Matching; use Psr\Http\Message\ServerRequestInterface as PsrRequest; -use Tempest\Http\MatchedRoute; use Tempest\Http\Route; use Tempest\Http\RouteConfig; use Tempest\Http\Routing\Construction\MarkedRoute; @@ -16,7 +15,7 @@ public function __construct(private RouteConfig $routeConfig) { } - public function match(PsrRequest $request): ?MatchedRoute + public function match(PsrRequest $request): ?Route { // Try to match routes without any parameters if (($staticRoute = $this->matchStaticRoute($request)) !== null) { @@ -27,18 +26,12 @@ public function match(PsrRequest $request): ?MatchedRoute return $this->matchDynamicRoute($request); } - private function matchStaticRoute(PsrRequest $request): ?MatchedRoute + private function matchStaticRoute(PsrRequest $request): ?Route { - $staticRoute = $this->routeConfig->staticRoutes[$request->getMethod()][$request->getUri()->getPath()] ?? null; - - if ($staticRoute === null) { - return null; - } - - return new MatchedRoute($staticRoute, []); + return $this->routeConfig->staticRoutes[$request->getMethod()][$request->getUri()->getPath()] ?? null; } - private function matchDynamicRoute(PsrRequest $request): ?MatchedRoute + private function matchDynamicRoute(PsrRequest $request): ?Route { // If there are no routes for the given request method, we immediately stop $routesForMethod = $this->routeConfig->dynamicRoutes[$request->getMethod()] ?? null; @@ -62,7 +55,7 @@ private function matchDynamicRoute(PsrRequest $request): ?MatchedRoute // Extract the parameters based on the route and matches $routeParams = $this->extractParams($route, $routingMatches); - return new MatchedRoute($route, $routeParams); + return $route->setParams($routeParams); } /** @@ -74,6 +67,7 @@ private function matchDynamicRoute(PsrRequest $request): ?MatchedRoute private function extractParams(Route $route, array $routeMatches): array { $valueMap = []; + foreach ($route->params as $i => $param) { $valueMap[$param] = $routeMatches[$i + 1]; } diff --git a/src/Tempest/Http/src/Routing/Matching/RouteMatcher.php b/src/Tempest/Http/src/Routing/Matching/RouteMatcher.php index a7528a52b..aca5e52af 100644 --- a/src/Tempest/Http/src/Routing/Matching/RouteMatcher.php +++ b/src/Tempest/Http/src/Routing/Matching/RouteMatcher.php @@ -5,9 +5,9 @@ namespace Tempest\Http\Routing\Matching; use Psr\Http\Message\ServerRequestInterface as PsrRequest; -use Tempest\Http\MatchedRoute; +use Tempest\Http\Route; interface RouteMatcher { - public function match(PsrRequest $request): ?MatchedRoute; + public function match(PsrRequest $request): ?Route; } diff --git a/src/Tempest/Http/tests/Routing/Matching/GenericRouteMatcherTest.php b/src/Tempest/Http/tests/Routing/Matching/GenericRouteMatcherTest.php index a9fae55b2..9091ab851 100644 --- a/src/Tempest/Http/tests/Routing/Matching/GenericRouteMatcherTest.php +++ b/src/Tempest/Http/tests/Routing/Matching/GenericRouteMatcherTest.php @@ -56,8 +56,8 @@ public function test_match_on_static_route(): void $matchedRoute = $this->subject->match($request); $this->assertEquals([], $matchedRoute->params); - $this->assertFalse($matchedRoute->route->isDynamic); - $this->assertEquals('/static', $matchedRoute->route->uri); + $this->assertFalse($matchedRoute->isDynamic); + $this->assertEquals('/static', $matchedRoute->uri); } public function test_match_returns_null_on_unknown_route(): void @@ -85,8 +85,8 @@ public function test_match_on_dynamic_route(): void $matchedRoute = $this->subject->match($request); $this->assertEquals([ 'id' => '5' ], $matchedRoute->params); - $this->assertTrue($matchedRoute->route->isDynamic); - $this->assertEquals('/dynamic/{id}', $matchedRoute->route->uri); + $this->assertTrue($matchedRoute->isDynamic); + $this->assertEquals('/dynamic/{id}', $matchedRoute->uri); } public function test_match_on_dynamic_route_with_many_parameters(): void @@ -96,7 +96,7 @@ public function test_match_on_dynamic_route_with_many_parameters(): void $matchedRoute = $this->subject->match($request); $this->assertEquals([ 'id' => '6', 'tag' => 'brendt', 'name' => 'brent' ], $matchedRoute->params); - $this->assertTrue($matchedRoute->route->isDynamic); - $this->assertEquals('/dynamic/{id}/{tag}/{name}/{id}', $matchedRoute->route->uri); + $this->assertTrue($matchedRoute->isDynamic); + $this->assertEquals('/dynamic/{id}/{tag}/{name}/{id}', $matchedRoute->uri); } }