From 30e589ced99d744042b629654bf14af3c2c12ca5 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Tue, 18 Nov 2025 19:36:43 +0100 Subject: [PATCH 1/6] fix(router): use route registry to generate uris --- .../ControllerActionDoesNotExist.php | 20 ++++++++++ .../Exceptions/ControllerMethodHadNoRoute.php | 15 ++++++++ .../ControllerMethodHadNoRouteAttribute.php | 15 -------- packages/router/src/RouteConfig.php | 4 ++ .../Construction/RouteConfigurator.php | 5 +++ packages/router/src/UriGenerator.php | 24 ++++++++---- tests/Integration/Route/UriGeneratorTest.php | 37 ++++++++++++++++++- 7 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 packages/router/src/Exceptions/ControllerActionDoesNotExist.php create mode 100644 packages/router/src/Exceptions/ControllerMethodHadNoRoute.php delete mode 100644 packages/router/src/Exceptions/ControllerMethodHadNoRouteAttribute.php diff --git a/packages/router/src/Exceptions/ControllerActionDoesNotExist.php b/packages/router/src/Exceptions/ControllerActionDoesNotExist.php new file mode 100644 index 000000000..2603dad93 --- /dev/null +++ b/packages/router/src/Exceptions/ControllerActionDoesNotExist.php @@ -0,0 +1,20 @@ + */ public array $matchingRegexes = [], + /** @var array */ + public array $handlerIndex = [], + /** @var class-string<\Tempest\Router\ResponseProcessor>[] */ public array $responseProcessors = [], @@ -37,6 +40,7 @@ public function apply(RouteConfig $newConfig): void $this->staticRoutes = $newConfig->staticRoutes; $this->dynamicRoutes = $newConfig->dynamicRoutes; $this->matchingRegexes = $newConfig->matchingRegexes; + $this->handlerIndex = $newConfig->handlerIndex; } public function addResponseProcessor(string $responseProcessor): void diff --git a/packages/router/src/Routing/Construction/RouteConfigurator.php b/packages/router/src/Routing/Construction/RouteConfigurator.php index 0c7d65861..159981044 100644 --- a/packages/router/src/Routing/Construction/RouteConfigurator.php +++ b/packages/router/src/Routing/Construction/RouteConfigurator.php @@ -22,6 +22,9 @@ final class RouteConfigurator /** @var array> */ private array $dynamicRoutes = []; + /** @var array */ + private array $handlerIndex = []; + private bool $isDirty = false; private RoutingTree $routingTree; @@ -34,6 +37,7 @@ public function __construct() public function addRoute(DiscoveredRoute $route): void { $this->isDirty = true; + $this->handlerIndex[$route->handler->getDeclaringClass()->getName() . '::' . $route->handler->getName()] = $route->uri; if ($route->isDynamic) { $this->addDynamicRoute($route); @@ -76,6 +80,7 @@ public function toRouteConfig(): RouteConfig $this->staticRoutes, $this->dynamicRoutes, $this->routingTree->toMatchingRegexes(), + $this->handlerIndex, ); } diff --git a/packages/router/src/UriGenerator.php b/packages/router/src/UriGenerator.php index 195a78846..aface435e 100644 --- a/packages/router/src/UriGenerator.php +++ b/packages/router/src/UriGenerator.php @@ -13,7 +13,8 @@ use Tempest\Http\Request; use Tempest\Reflection\ClassReflector; use Tempest\Reflection\MethodReflector; -use Tempest\Router\Exceptions\ControllerMethodHadNoRouteAttribute; +use Tempest\Router\Exceptions\ControllerActionDoesNotExist; +use Tempest\Router\Exceptions\ControllerMethodHadNoRoute; use Tempest\Router\Routing\Construction\DiscoveredRoute; use Tempest\Support\Arr; use Tempest\Support\Regex; @@ -25,6 +26,7 @@ final class UriGenerator { public function __construct( private AppConfig $appConfig, + private RouteConfig $routeConfig, private Signer $signer, private Container $container, ) {} @@ -178,7 +180,7 @@ public function isCurrentUri(array|string|MethodReflector $action, mixed ...$par $matchedRoute = $this->container->get(MatchedRoute::class); $candidateUri = $this->createUri($action, ...[...$matchedRoute->params, ...$params]); - $currentUri = $this->createUri([$matchedRoute->route->handler->getDeclaringClass(), $matchedRoute->route->handler->getName()]); + $currentUri = $this->createUri([$matchedRoute->route->handler->getDeclaringClass()->getName(), $matchedRoute->route->handler->getName()]); foreach ($matchedRoute->params as $key => $value) { if ($value instanceof BackedEnum) { @@ -206,14 +208,20 @@ private function normalizeActionToUri(array|string|MethodReflector $action): str [$controllerClass, $controllerMethod] = is_array($action) ? $action : [$action, '__invoke']; - $routeAttribute = new ClassReflector($controllerClass) - ->getMethod($controllerMethod) - ->getAttribute(Route::class); + $uri = $this->routeConfig->handlerIndex[$controllerClass . '::' . $controllerMethod] ?? null; - if ($routeAttribute === null) { - throw new ControllerMethodHadNoRouteAttribute($controllerClass, $controllerMethod); + if (! $uri) { + if (! class_exists($controllerClass)) { + throw ControllerActionDoesNotExist::controllerNotFound($controllerClass, $controllerMethod); + } + + if (! method_exists($controllerClass, $controllerMethod)) { + throw ControllerActionDoesNotExist::actionNotFound($controllerClass, $controllerMethod); + } + + throw new ControllerMethodHadNoRoute($controllerClass, $controllerMethod); } - return Str\ensure_starts_with($routeAttribute->uri, '/'); + return Str\ensure_starts_with($uri, '/'); } } diff --git a/tests/Integration/Route/UriGeneratorTest.php b/tests/Integration/Route/UriGeneratorTest.php index 125e45d83..98cc0b5cf 100644 --- a/tests/Integration/Route/UriGeneratorTest.php +++ b/tests/Integration/Route/UriGeneratorTest.php @@ -4,15 +4,16 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\TestWith; -use ReflectionException; use Tempest\Core\AppConfig; use Tempest\Database\PrimaryKey; use Tempest\DateTime\Duration; use Tempest\Http\GenericRequest; use Tempest\Http\Method; +use Tempest\Router\Exceptions\ControllerActionDoesNotExist; use Tempest\Router\UriGenerator; use Tests\Tempest\Fixtures\Controllers\ControllerWithEnumBinding; use Tests\Tempest\Fixtures\Controllers\EnumForController; +use Tests\Tempest\Fixtures\Controllers\PrefixController; use Tests\Tempest\Fixtures\Controllers\TestController; use Tests\Tempest\Fixtures\Controllers\UriGeneratorController; use Tests\Tempest\Fixtures\Modules\Books\BookController; @@ -56,11 +57,21 @@ public function uri_functions(): void #[Test] public function uri_generation_with_invalid_fqcn(): void { - $this->expectException(ReflectionException::class); + $this->expectException(ControllerActionDoesNotExist::class); + $this->expectExceptionMessage('The controller class `Tests\Tempest\Fixtures\Controllers\TestControllerInvalid` does not exist.'); $this->generator->createUri(TestController::class . 'Invalid'); } + #[Test] + public function uri_generation_with_invalid_method(): void + { + $this->expectException(ControllerActionDoesNotExist::class); + $this->expectExceptionMessage('The method `invalid()` does not exist in controller class `Tests\Tempest\Fixtures\Controllers\TestController`.'); + + $this->generator->createUri([TestController::class, 'invalid']); + } + #[Test] public function uri_generation_with_query_param(): void { @@ -249,4 +260,26 @@ public function cannot_add_custom_signature(): void signature: 'uwu', ); } + + #[Test] + public function generates_uri_with_prefix_decorator(): void + { + $this->assertSame( + '/prefix/endpoint', + $this->generator->createUri(PrefixController::class), + ); + + $this->assertSame( + '/prefix/endpoint', + uri(PrefixController::class), + ); + } + + #[Test] + public function is_current_uri_with_prefix_decorator(): void + { + $this->http->get('/prefix/endpoint')->assertOk(); + + $this->assertTrue($this->generator->isCurrentUri(PrefixController::class)); + } } From 6d662fb51d02e0e0fb20f1e9025c3be96b95c6b6 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Tue, 18 Nov 2025 19:36:54 +0100 Subject: [PATCH 2/6] style: remove new rector rule --- rector.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rector.php b/rector.php index 3e383346f..76d45de6c 100644 --- a/rector.php +++ b/rector.php @@ -5,13 +5,11 @@ use Rector\Arguments\Rector\ClassMethod\ArgumentAdderRector; use Rector\Caching\ValueObject\Storage\FileCacheStorage; use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector; -use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector; use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector; use Rector\Php70\Rector\StaticCall\StaticCallOnNonStaticToInstanceCallRector; use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector; use Rector\Php74\Rector\Property\RestoreDefaultNullToNullableTypePropertyRector; -use Rector\Php74\Rector\Ternary\ParenthesizeNestedTernaryRector; use Rector\Php81\Rector\Array_\FirstClassCallableRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; use Rector\Php81\Rector\Property\ReadOnlyPropertyRector; @@ -21,8 +19,8 @@ use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector; use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector; use Rector\TypeDeclaration\Rector\ArrowFunction\AddArrowFunctionReturnTypeRector; +use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector; -use Rector\TypeDeclaration\Rector\Closure\ClosureReturnTypeRector; use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector; return RectorConfig::configure() @@ -54,10 +52,10 @@ RestoreDefaultNullToNullableTypePropertyRector::class, ReturnNeverTypeRector::class, StaticCallOnNonStaticToInstanceCallRector::class, - ClosureReturnTypeRector::class, EncapsedStringsToSprintfRector::class, AddArrowFunctionReturnTypeRector::class, PrivatizeFinalClassMethodRector::class, + NarrowObjectReturnTypeRector::class, ]) ->withParallel(300, 10, 10) ->withPreparedSets( From 330b60e35199e639aa91cbf2818fcde3d6385318 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Tue, 18 Nov 2025 20:02:08 +0100 Subject: [PATCH 3/6] feat(router): throw exception when generating an uri to a controller action that has multiple different routes --- .../ControllerMethodHasMultipleRoutes.php | 24 +++++++++++++++++++ packages/router/src/RouteConfig.php | 2 +- .../Construction/RouteConfigurator.php | 5 +++- packages/router/src/UriGenerator.php | 11 ++++++--- .../ControllerWithRepeatedRoutes.php | 1 + tests/Integration/Route/UriGeneratorTest.php | 20 ++++++++++++++++ 6 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php diff --git a/packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php b/packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php new file mode 100644 index 000000000..ee6814a77 --- /dev/null +++ b/packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php @@ -0,0 +1,24 @@ + */ public array $matchingRegexes = [], - /** @var array */ + /** @var array */ public array $handlerIndex = [], /** @var class-string<\Tempest\Router\ResponseProcessor>[] */ diff --git a/packages/router/src/Routing/Construction/RouteConfigurator.php b/packages/router/src/Routing/Construction/RouteConfigurator.php index 159981044..c6d74b308 100644 --- a/packages/router/src/Routing/Construction/RouteConfigurator.php +++ b/packages/router/src/Routing/Construction/RouteConfigurator.php @@ -37,7 +37,10 @@ public function __construct() public function addRoute(DiscoveredRoute $route): void { $this->isDirty = true; - $this->handlerIndex[$route->handler->getDeclaringClass()->getName() . '::' . $route->handler->getName()] = $route->uri; + + $handler = $route->handler->getDeclaringClass()->getName() . '::' . $route->handler->getName(); + $this->handlerIndex[$handler] ??= []; + $this->handlerIndex[$handler][] = $route->uri; if ($route->isDynamic) { $this->addDynamicRoute($route); diff --git a/packages/router/src/UriGenerator.php b/packages/router/src/UriGenerator.php index aface435e..60a16b670 100644 --- a/packages/router/src/UriGenerator.php +++ b/packages/router/src/UriGenerator.php @@ -15,6 +15,7 @@ use Tempest\Reflection\MethodReflector; use Tempest\Router\Exceptions\ControllerActionDoesNotExist; use Tempest\Router\Exceptions\ControllerMethodHadNoRoute; +use Tempest\Router\Exceptions\ControllerMethodHasMultipleRoutes; use Tempest\Router\Routing\Construction\DiscoveredRoute; use Tempest\Support\Arr; use Tempest\Support\Regex; @@ -208,9 +209,9 @@ private function normalizeActionToUri(array|string|MethodReflector $action): str [$controllerClass, $controllerMethod] = is_array($action) ? $action : [$action, '__invoke']; - $uri = $this->routeConfig->handlerIndex[$controllerClass . '::' . $controllerMethod] ?? null; + $routes = array_unique($this->routeConfig->handlerIndex[$controllerClass . '::' . $controllerMethod] ?? []); - if (! $uri) { + if ($routes === []) { if (! class_exists($controllerClass)) { throw ControllerActionDoesNotExist::controllerNotFound($controllerClass, $controllerMethod); } @@ -222,6 +223,10 @@ private function normalizeActionToUri(array|string|MethodReflector $action): str throw new ControllerMethodHadNoRoute($controllerClass, $controllerMethod); } - return Str\ensure_starts_with($uri, '/'); + if (count($routes) > 1) { + throw new ControllerMethodHasMultipleRoutes($controllerClass, $controllerMethod, $routes); + } + + return Str\ensure_starts_with($routes[0], '/'); } } diff --git a/tests/Fixtures/Controllers/ControllerWithRepeatedRoutes.php b/tests/Fixtures/Controllers/ControllerWithRepeatedRoutes.php index 13797c517..367853db2 100644 --- a/tests/Fixtures/Controllers/ControllerWithRepeatedRoutes.php +++ b/tests/Fixtures/Controllers/ControllerWithRepeatedRoutes.php @@ -17,6 +17,7 @@ #[Get('/repeated/d')] #[Post('/repeated/e')] #[Post('/repeated/f')] + #[Get('/repeated/f')] public function __invoke(): Response { return new Ok(); diff --git a/tests/Integration/Route/UriGeneratorTest.php b/tests/Integration/Route/UriGeneratorTest.php index 98cc0b5cf..ac6254308 100644 --- a/tests/Integration/Route/UriGeneratorTest.php +++ b/tests/Integration/Route/UriGeneratorTest.php @@ -10,8 +10,10 @@ use Tempest\Http\GenericRequest; use Tempest\Http\Method; use Tempest\Router\Exceptions\ControllerActionDoesNotExist; +use Tempest\Router\Exceptions\ControllerMethodHasMultipleRoutes; use Tempest\Router\UriGenerator; use Tests\Tempest\Fixtures\Controllers\ControllerWithEnumBinding; +use Tests\Tempest\Fixtures\Controllers\ControllerWithRepeatedRoutes; use Tests\Tempest\Fixtures\Controllers\EnumForController; use Tests\Tempest\Fixtures\Controllers\PrefixController; use Tests\Tempest\Fixtures\Controllers\TestController; @@ -282,4 +284,22 @@ public function is_current_uri_with_prefix_decorator(): void $this->assertTrue($this->generator->isCurrentUri(PrefixController::class)); } + + #[Test] + public function controller_with_multiple_routes(): void + { + $this->expectException(ControllerMethodHasMultipleRoutes::class); + $this->expectExceptionMessage('Controller method `' . ControllerWithRepeatedRoutes::class . '::__invoke()` has multiple different routes'); + + $this->generator->createUri(ControllerWithRepeatedRoutes::class); + } + + #[Test] + public function uri_to_controller_with_multiple_routes(): void + { + $this->assertSame( + '/repeated/a', + $this->generator->createUri('/repeated/a'), + ); + } } From e8161094d8190bc28874e55ab07771aa1b2e5524 Mon Sep 17 00:00:00 2001 From: brendt Date: Thu, 20 Nov 2025 08:12:54 +0100 Subject: [PATCH 4/6] Remove multi route check --- .../ControllerMethodHasMultipleRoutes.php | 24 ------------------- packages/router/src/UriGenerator.php | 4 ---- tests/Integration/Route/UriGeneratorTest.php | 10 -------- 3 files changed, 38 deletions(-) delete mode 100644 packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php diff --git a/packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php b/packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php deleted file mode 100644 index ee6814a77..000000000 --- a/packages/router/src/Exceptions/ControllerMethodHasMultipleRoutes.php +++ /dev/null @@ -1,24 +0,0 @@ - 1) { - throw new ControllerMethodHasMultipleRoutes($controllerClass, $controllerMethod, $routes); - } - return Str\ensure_starts_with($routes[0], '/'); } } diff --git a/tests/Integration/Route/UriGeneratorTest.php b/tests/Integration/Route/UriGeneratorTest.php index ac6254308..d825612df 100644 --- a/tests/Integration/Route/UriGeneratorTest.php +++ b/tests/Integration/Route/UriGeneratorTest.php @@ -10,7 +10,6 @@ use Tempest\Http\GenericRequest; use Tempest\Http\Method; use Tempest\Router\Exceptions\ControllerActionDoesNotExist; -use Tempest\Router\Exceptions\ControllerMethodHasMultipleRoutes; use Tempest\Router\UriGenerator; use Tests\Tempest\Fixtures\Controllers\ControllerWithEnumBinding; use Tests\Tempest\Fixtures\Controllers\ControllerWithRepeatedRoutes; @@ -285,15 +284,6 @@ public function is_current_uri_with_prefix_decorator(): void $this->assertTrue($this->generator->isCurrentUri(PrefixController::class)); } - #[Test] - public function controller_with_multiple_routes(): void - { - $this->expectException(ControllerMethodHasMultipleRoutes::class); - $this->expectExceptionMessage('Controller method `' . ControllerWithRepeatedRoutes::class . '::__invoke()` has multiple different routes'); - - $this->generator->createUri(ControllerWithRepeatedRoutes::class); - } - #[Test] public function uri_to_controller_with_multiple_routes(): void { From 7e5422b072dde9094bc3e21135bc4f09324f4bfd Mon Sep 17 00:00:00 2001 From: brendt Date: Thu, 20 Nov 2025 08:14:08 +0100 Subject: [PATCH 5/6] Multi prefixs support --- packages/router/src/RouteDiscovery.php | 2 +- tests/Fixtures/Controllers/PrefixController.php | 2 +- tests/Integration/Route/RouterTest.php | 2 +- tests/Integration/Route/UriGeneratorTest.php | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/router/src/RouteDiscovery.php b/packages/router/src/RouteDiscovery.php index 931519cde..f9556bd20 100644 --- a/packages/router/src/RouteDiscovery.php +++ b/packages/router/src/RouteDiscovery.php @@ -27,8 +27,8 @@ public function discover(DiscoveryLocation $location, ClassReflector $class): vo foreach ($routeAttributes as $routeAttribute) { $decorators = [ - ...$method->getDeclaringClass()->getAttributes(RouteDecorator::class), ...$method->getAttributes(RouteDecorator::class), + ...$method->getDeclaringClass()->getAttributes(RouteDecorator::class), ]; $route = DiscoveredRoute::fromRoute($routeAttribute, $decorators, $method); diff --git a/tests/Fixtures/Controllers/PrefixController.php b/tests/Fixtures/Controllers/PrefixController.php index e61e6c8dc..7b7b8baa9 100644 --- a/tests/Fixtures/Controllers/PrefixController.php +++ b/tests/Fixtures/Controllers/PrefixController.php @@ -9,7 +9,7 @@ #[Prefix('/prefix')] final class PrefixController { - #[Get('/endpoint')] + #[Prefix('/method'), Get('/endpoint')] public function __invoke(): Ok { return new Ok(); diff --git a/tests/Integration/Route/RouterTest.php b/tests/Integration/Route/RouterTest.php index b97c7d544..a4478c43a 100644 --- a/tests/Integration/Route/RouterTest.php +++ b/tests/Integration/Route/RouterTest.php @@ -272,7 +272,7 @@ public function test_stateless_decorator(): void public function test_prefix_decorator(): void { $this->http - ->get('/prefix/endpoint') + ->get('/prefix/method/endpoint') ->assertOk(); } diff --git a/tests/Integration/Route/UriGeneratorTest.php b/tests/Integration/Route/UriGeneratorTest.php index d825612df..5b59b02dc 100644 --- a/tests/Integration/Route/UriGeneratorTest.php +++ b/tests/Integration/Route/UriGeneratorTest.php @@ -266,12 +266,12 @@ public function cannot_add_custom_signature(): void public function generates_uri_with_prefix_decorator(): void { $this->assertSame( - '/prefix/endpoint', + '/prefix/method/endpoint', $this->generator->createUri(PrefixController::class), ); $this->assertSame( - '/prefix/endpoint', + '/prefix/method/endpoint', uri(PrefixController::class), ); } @@ -279,7 +279,7 @@ public function generates_uri_with_prefix_decorator(): void #[Test] public function is_current_uri_with_prefix_decorator(): void { - $this->http->get('/prefix/endpoint')->assertOk(); + $this->http->get('/prefix/method/endpoint')->assertOk(); $this->assertTrue($this->generator->isCurrentUri(PrefixController::class)); } From ce8fa86603e57fbf6b2553be0c24bdaad0357159 Mon Sep 17 00:00:00 2001 From: brendt Date: Thu, 20 Nov 2025 08:14:41 +0100 Subject: [PATCH 6/6] QA --- packages/router/src/UriGenerator.php | 1 - tests/Integration/Route/UriGeneratorTest.php | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/router/src/UriGenerator.php b/packages/router/src/UriGenerator.php index bdd7c760f..1d1cc92ae 100644 --- a/packages/router/src/UriGenerator.php +++ b/packages/router/src/UriGenerator.php @@ -15,7 +15,6 @@ use Tempest\Reflection\MethodReflector; use Tempest\Router\Exceptions\ControllerActionDoesNotExist; use Tempest\Router\Exceptions\ControllerMethodHadNoRoute; -use Tempest\Router\Exceptions\ControllerMethodHasMultipleRoutes; use Tempest\Router\Routing\Construction\DiscoveredRoute; use Tempest\Support\Arr; use Tempest\Support\Regex; diff --git a/tests/Integration/Route/UriGeneratorTest.php b/tests/Integration/Route/UriGeneratorTest.php index 5b59b02dc..1f673a2c1 100644 --- a/tests/Integration/Route/UriGeneratorTest.php +++ b/tests/Integration/Route/UriGeneratorTest.php @@ -12,7 +12,6 @@ use Tempest\Router\Exceptions\ControllerActionDoesNotExist; use Tempest\Router\UriGenerator; use Tests\Tempest\Fixtures\Controllers\ControllerWithEnumBinding; -use Tests\Tempest\Fixtures\Controllers\ControllerWithRepeatedRoutes; use Tests\Tempest\Fixtures\Controllers\EnumForController; use Tests\Tempest\Fixtures\Controllers\PrefixController; use Tests\Tempest\Fixtures\Controllers\TestController;