Skip to content

Commit 30e589c

Browse files
committed
fix(router): use route registry to generate uris
1 parent bfe1f8d commit 30e589c

File tree

7 files changed

+95
-25
lines changed

7 files changed

+95
-25
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Router\Exceptions;
6+
7+
use Exception;
8+
9+
final class ControllerActionDoesNotExist extends Exception implements RouterException
10+
{
11+
public static function controllerNotFound(string $controller): self
12+
{
13+
return new self("The controller class `{$controller}` does not exist.");
14+
}
15+
16+
public static function actionNotFound(string $controller, string $method): self
17+
{
18+
return new self("The method `{$method}()` does not exist in controller class `{$controller}`.");
19+
}
20+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Router\Exceptions;
6+
7+
use Exception;
8+
9+
final class ControllerMethodHadNoRoute extends Exception implements RouterException
10+
{
11+
public function __construct(string $controllerClass, string $controllerMethod)
12+
{
13+
parent::__construct("No route found for `{$controllerClass}::{$controllerMethod}()`. Did you forget to add a `Route` attribute?");
14+
}
15+
}

packages/router/src/Exceptions/ControllerMethodHadNoRouteAttribute.php

Lines changed: 0 additions & 15 deletions
This file was deleted.

packages/router/src/RouteConfig.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public function __construct(
1818
/** @var array<string,\Tempest\Router\Routing\Matching\MatchingRegex> */
1919
public array $matchingRegexes = [],
2020

21+
/** @var array<string,string> */
22+
public array $handlerIndex = [],
23+
2124
/** @var class-string<\Tempest\Router\ResponseProcessor>[] */
2225
public array $responseProcessors = [],
2326

@@ -37,6 +40,7 @@ public function apply(RouteConfig $newConfig): void
3740
$this->staticRoutes = $newConfig->staticRoutes;
3841
$this->dynamicRoutes = $newConfig->dynamicRoutes;
3942
$this->matchingRegexes = $newConfig->matchingRegexes;
43+
$this->handlerIndex = $newConfig->handlerIndex;
4044
}
4145

4246
public function addResponseProcessor(string $responseProcessor): void

packages/router/src/Routing/Construction/RouteConfigurator.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ final class RouteConfigurator
2222
/** @var array<string,array<string,\Tempest\Router\Routing\Construction\DiscoveredRoute>> */
2323
private array $dynamicRoutes = [];
2424

25+
/** @var array<string,string[]> */
26+
private array $handlerIndex = [];
27+
2528
private bool $isDirty = false;
2629

2730
private RoutingTree $routingTree;
@@ -34,6 +37,7 @@ public function __construct()
3437
public function addRoute(DiscoveredRoute $route): void
3538
{
3639
$this->isDirty = true;
40+
$this->handlerIndex[$route->handler->getDeclaringClass()->getName() . '::' . $route->handler->getName()] = $route->uri;
3741

3842
if ($route->isDynamic) {
3943
$this->addDynamicRoute($route);
@@ -76,6 +80,7 @@ public function toRouteConfig(): RouteConfig
7680
$this->staticRoutes,
7781
$this->dynamicRoutes,
7882
$this->routingTree->toMatchingRegexes(),
83+
$this->handlerIndex,
7984
);
8085
}
8186

packages/router/src/UriGenerator.php

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
use Tempest\Http\Request;
1414
use Tempest\Reflection\ClassReflector;
1515
use Tempest\Reflection\MethodReflector;
16-
use Tempest\Router\Exceptions\ControllerMethodHadNoRouteAttribute;
16+
use Tempest\Router\Exceptions\ControllerActionDoesNotExist;
17+
use Tempest\Router\Exceptions\ControllerMethodHadNoRoute;
1718
use Tempest\Router\Routing\Construction\DiscoveredRoute;
1819
use Tempest\Support\Arr;
1920
use Tempest\Support\Regex;
@@ -25,6 +26,7 @@ final class UriGenerator
2526
{
2627
public function __construct(
2728
private AppConfig $appConfig,
29+
private RouteConfig $routeConfig,
2830
private Signer $signer,
2931
private Container $container,
3032
) {}
@@ -178,7 +180,7 @@ public function isCurrentUri(array|string|MethodReflector $action, mixed ...$par
178180

179181
$matchedRoute = $this->container->get(MatchedRoute::class);
180182
$candidateUri = $this->createUri($action, ...[...$matchedRoute->params, ...$params]);
181-
$currentUri = $this->createUri([$matchedRoute->route->handler->getDeclaringClass(), $matchedRoute->route->handler->getName()]);
183+
$currentUri = $this->createUri([$matchedRoute->route->handler->getDeclaringClass()->getName(), $matchedRoute->route->handler->getName()]);
182184

183185
foreach ($matchedRoute->params as $key => $value) {
184186
if ($value instanceof BackedEnum) {
@@ -206,14 +208,20 @@ private function normalizeActionToUri(array|string|MethodReflector $action): str
206208

207209
[$controllerClass, $controllerMethod] = is_array($action) ? $action : [$action, '__invoke'];
208210

209-
$routeAttribute = new ClassReflector($controllerClass)
210-
->getMethod($controllerMethod)
211-
->getAttribute(Route::class);
211+
$uri = $this->routeConfig->handlerIndex[$controllerClass . '::' . $controllerMethod] ?? null;
212212

213-
if ($routeAttribute === null) {
214-
throw new ControllerMethodHadNoRouteAttribute($controllerClass, $controllerMethod);
213+
if (! $uri) {
214+
if (! class_exists($controllerClass)) {
215+
throw ControllerActionDoesNotExist::controllerNotFound($controllerClass, $controllerMethod);
216+
}
217+
218+
if (! method_exists($controllerClass, $controllerMethod)) {
219+
throw ControllerActionDoesNotExist::actionNotFound($controllerClass, $controllerMethod);
220+
}
221+
222+
throw new ControllerMethodHadNoRoute($controllerClass, $controllerMethod);
215223
}
216224

217-
return Str\ensure_starts_with($routeAttribute->uri, '/');
225+
return Str\ensure_starts_with($uri, '/');
218226
}
219227
}

tests/Integration/Route/UriGeneratorTest.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@
44

55
use PHPUnit\Framework\Attributes\Test;
66
use PHPUnit\Framework\Attributes\TestWith;
7-
use ReflectionException;
87
use Tempest\Core\AppConfig;
98
use Tempest\Database\PrimaryKey;
109
use Tempest\DateTime\Duration;
1110
use Tempest\Http\GenericRequest;
1211
use Tempest\Http\Method;
12+
use Tempest\Router\Exceptions\ControllerActionDoesNotExist;
1313
use Tempest\Router\UriGenerator;
1414
use Tests\Tempest\Fixtures\Controllers\ControllerWithEnumBinding;
1515
use Tests\Tempest\Fixtures\Controllers\EnumForController;
16+
use Tests\Tempest\Fixtures\Controllers\PrefixController;
1617
use Tests\Tempest\Fixtures\Controllers\TestController;
1718
use Tests\Tempest\Fixtures\Controllers\UriGeneratorController;
1819
use Tests\Tempest\Fixtures\Modules\Books\BookController;
@@ -56,11 +57,21 @@ public function uri_functions(): void
5657
#[Test]
5758
public function uri_generation_with_invalid_fqcn(): void
5859
{
59-
$this->expectException(ReflectionException::class);
60+
$this->expectException(ControllerActionDoesNotExist::class);
61+
$this->expectExceptionMessage('The controller class `Tests\Tempest\Fixtures\Controllers\TestControllerInvalid` does not exist.');
6062

6163
$this->generator->createUri(TestController::class . 'Invalid');
6264
}
6365

66+
#[Test]
67+
public function uri_generation_with_invalid_method(): void
68+
{
69+
$this->expectException(ControllerActionDoesNotExist::class);
70+
$this->expectExceptionMessage('The method `invalid()` does not exist in controller class `Tests\Tempest\Fixtures\Controllers\TestController`.');
71+
72+
$this->generator->createUri([TestController::class, 'invalid']);
73+
}
74+
6475
#[Test]
6576
public function uri_generation_with_query_param(): void
6677
{
@@ -249,4 +260,26 @@ public function cannot_add_custom_signature(): void
249260
signature: 'uwu',
250261
);
251262
}
263+
264+
#[Test]
265+
public function generates_uri_with_prefix_decorator(): void
266+
{
267+
$this->assertSame(
268+
'/prefix/endpoint',
269+
$this->generator->createUri(PrefixController::class),
270+
);
271+
272+
$this->assertSame(
273+
'/prefix/endpoint',
274+
uri(PrefixController::class),
275+
);
276+
}
277+
278+
#[Test]
279+
public function is_current_uri_with_prefix_decorator(): void
280+
{
281+
$this->http->get('/prefix/endpoint')->assertOk();
282+
283+
$this->assertTrue($this->generator->isCurrentUri(PrefixController::class));
284+
}
252285
}

0 commit comments

Comments
 (0)