From 7618804bd1ce9f52e8d5005dd7fe74a8fbfed9ff Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 09:08:03 +0530 Subject: [PATCH 01/12] (feat): method aliases for routes via aliasMethod() --- README.md | 31 +++++++++++++ src/Http/Route.php | 59 +++++++++++++++++++++++++ src/Http/Router.php | 42 ++++++++++++++++-- tests/HttpTest.php | 26 +++++++++++ tests/RouterTest.php | 103 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 257 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 25a00afe..9e25b5e2 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,37 @@ curl http://localhost:8000/hello-world?name=Appwrite It's always recommended to use params instead of getting params or body directly from the request resource. If you do that intentionally, always make sure to run validation right after fetching such a raw input. +### Aliases + +A route can be registered under additional paths and HTTP methods using aliases. All aliases dispatch to the same route, so the action, params, and hooks are defined only once. + +Use `alias()` to serve the same route under another path, for example to keep a legacy URL working: + +```php +Http::get('/users/:id') + ->alias('/members/:id') + ->param('id', '', new Text(256), 'User ID') + ->inject('response') + ->action(function(string $id, Response $response) { + $response->json(['id' => $id]); + }); +``` + +Use `aliasMethod()` to serve the same route under another HTTP method. For example, the OpenID Connect UserInfo endpoint must support both GET and POST: + +```php +Http::get('/oauth/userinfo') + ->aliasMethod(Http::REQUEST_METHOD_POST) + ->inject('request') + ->inject('response') + ->action(function(Request $request, Response $response) { + // $request->getMethod() tells how the request arrived (GET or POST) + $response->json(['sub' => 'user-id']); + }); +``` + +Path and method aliases combine: a route with both responds on every method under every path. Note that `getMethod()` on the route keeps returning the primary method it was defined with; use the request resource to tell how a request arrived. + ### Hooks There are three types of hooks: diff --git a/src/Http/Route.php b/src/Http/Route.php index ab1d769c..10ce8147 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -28,6 +28,20 @@ class Route extends Hook */ protected array $pathParams = []; + /** + * Alias paths this route is also registered under. + * + * @var array + */ + protected array $aliasPaths = []; + + /** + * Alias HTTP methods this route is also registered under. + * + * @var array + */ + protected array $aliasMethods = []; + /** * Internal counter. */ @@ -71,6 +85,31 @@ public function alias(string $path): self { Router::addRouteAlias($path, $this); + foreach ($this->aliasMethods as $method) { + Router::addRouteAlias($path, $this, $method); + } + + $this->aliasPaths[] = $path; + + return $this; + } + + /** + * Register this route under an additional HTTP method. + * + * The route keeps reporting its primary method via getMethod(); + * use the request's method to tell how a request arrived. + */ + public function aliasMethod(string $method): self + { + Router::addRouteMethodAlias($method, $this); + + foreach ($this->aliasPaths as $path) { + Router::addRouteAlias($path, $this, $method); + } + + $this->aliasMethods[] = $method; + return $this; } @@ -108,6 +147,26 @@ public function getHook(): bool return $this->hook; } + /** + * Get alias paths. + * + * @return array + */ + public function getAliasPaths(): array + { + return $this->aliasPaths; + } + + /** + * Get alias methods. + * + * @return array + */ + public function getAliasMethods(): array + { + return $this->aliasMethods; + } + /** * Set path param. */ diff --git a/src/Http/Router.php b/src/Http/Router.php index 81ef21dc..0c8f1b5d 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -95,19 +95,53 @@ public static function addRoute(Route $route): void * * @throws \Exception */ - public static function addRouteAlias(string $path, Route $route): void + public static function addRouteAlias(string $path, Route $route, ?string $method = null): void { + $method ??= $route->getMethod(); + + if (!\array_key_exists($method, self::$routes)) { + throw new Exception("Method ({$method}) not supported."); + } + [$alias, $params] = self::preparePath($path); - if (\array_key_exists($alias, self::$routes[$route->getMethod()]) && !self::$allowOverride) { - throw new Exception("Route for ({$route->getMethod()}:{$alias}) already registered."); + if (\array_key_exists($alias, self::$routes[$method]) && !self::$allowOverride) { + throw new Exception("Route for ({$method}:{$alias}) already registered."); } foreach ($params as $key => $index) { $route->setPathParam($key, $index, $alias); } - self::$routes[$route->getMethod()][$alias] = $route; + self::$routes[$method][$alias] = $route; + } + + /** + * Register a route under an additional HTTP method, using its own path. + * + * @throws \Exception + */ + public static function addRouteMethodAlias(string $method, Route $route): void + { + if (!\array_key_exists($method, self::$routes)) { + throw new Exception("Method ({$method}) not supported."); + } + + if ($route->getPath() === '') { + throw new Exception('Method aliases are not supported for the wildcard route.'); + } + + [$path, $params] = self::preparePath($route->getPath()); + + if (\array_key_exists($path, self::$routes[$method]) && !self::$allowOverride) { + throw new Exception("Route for ({$method}:{$path}) already registered."); + } + + foreach ($params as $key => $index) { + $route->setPathParam($key, $index, $path); + } + + self::$routes[$method][$path] = $route; } /** diff --git a/tests/HttpTest.php b/tests/HttpTest.php index e1fa32e0..a709b7a1 100755 --- a/tests/HttpTest.php +++ b/tests/HttpTest.php @@ -250,6 +250,32 @@ public function testCanExecuteRoute(): void $this->assertSame('init-' . $resource . '-(init-homepage)-param-x*param-y-(shutdown-homepage)-shutdown', $result); } + public function testCanExecuteRouteWithMethodAlias(): void + { + $route = Http::get('/v1/oauth/userinfo'); + + $route + ->aliasMethod(Http::REQUEST_METHOD_POST) + ->inject('request') + ->action(function ($request) { + echo 'userinfo:' . $request->getMethod(); + }); + + $_SERVER['REQUEST_URI'] = '/v1/oauth/userinfo'; + + $_SERVER['REQUEST_METHOD'] = 'GET'; + ob_start(); + $this->http->run(new Request(), new Response()); + $result = ob_get_clean(); + $this->assertSame('userinfo:GET', $result); + + $_SERVER['REQUEST_METHOD'] = 'POST'; + ob_start(); + $this->http->run(new Request(), new Response()); + $result = ob_get_clean(); + $this->assertSame('userinfo:POST', $result); + } + public function testCanAddAndExecuteHooks(): void { Http::setAllowOverride(true); diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 5eaa0c72..aa865111 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -8,6 +8,11 @@ final class RouterTest extends TestCase { + public function setUp(): void + { + Router::setAllowOverride(false); + } + public function tearDown(): void { Router::reset(); @@ -137,6 +142,104 @@ public function testCanMatchMix(): void $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/register/lorem/ipsum')?->route); } + public function testCanMatchMethodAlias(): void + { + $route = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); + $route->aliasMethod(Http::REQUEST_METHOD_POST); + + Router::addRoute($route); + + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/userinfo')?->route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); + $this->assertNull(Router::match(Http::REQUEST_METHOD_PUT, '/userinfo')); + + $this->assertSame(Http::REQUEST_METHOD_GET, $route->getMethod()); + $this->assertSame([Http::REQUEST_METHOD_POST], $route->getAliasMethods()); + } + + public function testCanMatchMethodAliasWithPlaceholder(): void + { + $route = new Route(Http::REQUEST_METHOD_GET, '/users/:id'); + $route->aliasMethod(Http::REQUEST_METHOD_POST); + + Router::addRoute($route); + + $match = Router::match(Http::REQUEST_METHOD_POST, '/users/abc-123'); + + $this->assertEquals($route, $match?->route); + $this->assertSame(['id' => 'abc-123'], $match?->params); + } + + public function testMethodAliasCrossesPathAliasesRegardlessOfOrder(): void + { + $routeA = new Route(Http::REQUEST_METHOD_GET, '/a'); + $routeA + ->alias('/a-old') + ->aliasMethod(Http::REQUEST_METHOD_POST); + + Router::addRoute($routeA); + + $routeB = new Route(Http::REQUEST_METHOD_GET, '/b'); + $routeB + ->aliasMethod(Http::REQUEST_METHOD_POST) + ->alias('/b-old'); + + Router::addRoute($routeB); + + $this->assertEquals($routeA, Router::match(Http::REQUEST_METHOD_POST, '/a')?->route); + $this->assertEquals($routeA, Router::match(Http::REQUEST_METHOD_POST, '/a-old')?->route); + $this->assertEquals($routeB, Router::match(Http::REQUEST_METHOD_POST, '/b')?->route); + $this->assertEquals($routeB, Router::match(Http::REQUEST_METHOD_POST, '/b-old')?->route); + } + + public function testCannotRegisterDuplicateMethodAlias(): void + { + $routePOST = new Route(Http::REQUEST_METHOD_POST, '/userinfo'); + Router::addRoute($routePOST); + + $routeGET = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Route for (POST:userinfo) already registered.'); + $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); + } + + public function testCanOverrideMethodAlias(): void + { + Router::setAllowOverride(true); + + try { + $routePOST = new Route(Http::REQUEST_METHOD_POST, '/userinfo'); + Router::addRoute($routePOST); + + $routeGET = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); + Router::addRoute($routeGET); + $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); + + $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); + } finally { + Router::setAllowOverride(false); + } + } + + public function testCannotRegisterMethodAliasForUnknownMethod(): void + { + $route = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Method (TRACE) not supported.'); + $route->aliasMethod('TRACE'); + } + + public function testCannotRegisterMethodAliasOnWildcardRoute(): void + { + $route = new Route('', ''); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Method aliases are not supported for the wildcard route.'); + $route->aliasMethod(Http::REQUEST_METHOD_POST); + } + public function testCanMatchFilename(): void { $routeGET = new Route(Http::REQUEST_METHOD_GET, '/robots.txt'); From 21793e3cd5bdfb989b0776aab9167c6a258c4667 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 09:29:27 +0530 Subject: [PATCH 02/12] (fix): reset allowOverride in Router::reset() and guard duplicate alias entries --- src/Http/Route.php | 8 ++++++-- src/Http/Router.php | 1 + tests/RouterTest.php | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Http/Route.php b/src/Http/Route.php index 10ce8147..ab73eb91 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -89,7 +89,9 @@ public function alias(string $path): self Router::addRouteAlias($path, $this, $method); } - $this->aliasPaths[] = $path; + if (!\in_array($path, $this->aliasPaths, true)) { + $this->aliasPaths[] = $path; + } return $this; } @@ -108,7 +110,9 @@ public function aliasMethod(string $method): self Router::addRouteAlias($path, $this, $method); } - $this->aliasMethods[] = $method; + if (!\in_array($method, $this->aliasMethods, true)) { + $this->aliasMethods[] = $method; + } return $this; } diff --git a/src/Http/Router.php b/src/Http/Router.php index 0c8f1b5d..d8350532 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -266,6 +266,7 @@ public static function reset(): void { self::$params = []; self::$wildcard = null; + self::$allowOverride = false; self::$routes = [ Http::REQUEST_METHOD_GET => [], Http::REQUEST_METHOD_POST => [], diff --git a/tests/RouterTest.php b/tests/RouterTest.php index aa865111..c38f67b9 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -215,8 +215,10 @@ public function testCanOverrideMethodAlias(): void $routeGET = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); Router::addRoute($routeGET); $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); + $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); + $this->assertSame([Http::REQUEST_METHOD_POST], $routeGET->getAliasMethods()); } finally { Router::setAllowOverride(false); } From 741f695f41f748daf451951342f832fb4641b0b1 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 13:45:42 +0530 Subject: [PATCH 03/12] Refactor route method aliases to routes API --- README.md | 11 +++--- src/Http/Http.php | 32 ++++++++++++++--- src/Http/Route.php | 39 ++++++--------------- src/Http/Router.php | 8 +++-- tests/HttpTest.php | 7 ++-- tests/RouterTest.php | 81 +++++++++++++++++++------------------------- 6 files changed, 85 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 9e25b5e2..70f99ac4 100644 --- a/README.md +++ b/README.md @@ -143,9 +143,9 @@ curl http://localhost:8000/hello-world?name=Appwrite It's always recommended to use params instead of getting params or body directly from the request resource. If you do that intentionally, always make sure to run validation right after fetching such a raw input. -### Aliases +### Aliases and Multiple Methods -A route can be registered under additional paths and HTTP methods using aliases. All aliases dispatch to the same route, so the action, params, and hooks are defined only once. +A route can be registered under additional paths and multiple HTTP methods. All matching paths and methods dispatch to the same route, so the action, params, and hooks are defined only once. Use `alias()` to serve the same route under another path, for example to keep a legacy URL working: @@ -159,11 +159,10 @@ Http::get('/users/:id') }); ``` -Use `aliasMethod()` to serve the same route under another HTTP method. For example, the OpenID Connect UserInfo endpoint must support both GET and POST: +Use `routes()` to serve the same route under multiple HTTP methods. For example, the OpenID Connect UserInfo endpoint must support both GET and POST: ```php -Http::get('/oauth/userinfo') - ->aliasMethod(Http::REQUEST_METHOD_POST) +Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/oauth/userinfo') ->inject('request') ->inject('response') ->action(function(Request $request, Response $response) { @@ -172,7 +171,7 @@ Http::get('/oauth/userinfo') }); ``` -Path and method aliases combine: a route with both responds on every method under every path. Note that `getMethod()` on the route keeps returning the primary method it was defined with; use the request resource to tell how a request arrived. +Path aliases and multiple methods combine: a route with both responds on every method under every path. Note that `getMethod()` on the route returns the first method it was defined with; use the request resource to tell how a request arrived. ### Hooks diff --git a/src/Http/Http.php b/src/Http/Http.php index d13e0491..4e273e09 100755 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -179,7 +179,7 @@ public function setCompressionSupported(mixed $compressionSupported): void */ public static function get(string $url): Route { - return self::addRoute(self::REQUEST_METHOD_GET, $url); + return self::routes(self::REQUEST_METHOD_GET, $url); } /** @@ -189,7 +189,7 @@ public static function get(string $url): Route */ public static function post(string $url): Route { - return self::addRoute(self::REQUEST_METHOD_POST, $url); + return self::routes(self::REQUEST_METHOD_POST, $url); } /** @@ -199,7 +199,7 @@ public static function post(string $url): Route */ public static function put(string $url): Route { - return self::addRoute(self::REQUEST_METHOD_PUT, $url); + return self::routes(self::REQUEST_METHOD_PUT, $url); } /** @@ -209,7 +209,7 @@ public static function put(string $url): Route */ public static function patch(string $url): Route { - return self::addRoute(self::REQUEST_METHOD_PATCH, $url); + return self::routes(self::REQUEST_METHOD_PATCH, $url); } /** @@ -219,7 +219,29 @@ public static function patch(string $url): Route */ public static function delete(string $url): Route { - return self::addRoute(self::REQUEST_METHOD_DELETE, $url); + return self::routes(self::REQUEST_METHOD_DELETE, $url); + } + + /** + * ROUTES + * + * Add one route under one or more request methods + * + * @param string|array $methods + */ + public static function routes(string|array $methods, string $url): Route + { + $methods = \is_array($methods) ? $methods : [$methods]; + $methods = array_values(array_unique($methods)); + + if (empty($methods)) { + throw new \Exception('At least one HTTP method is required.'); + } + + $route = new Route($methods[0], $url, \array_slice($methods, 1)); + Router::addRoute($route); + + return $route; } /** diff --git a/src/Http/Route.php b/src/Http/Route.php index ab73eb91..5e51b36f 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -36,11 +36,11 @@ class Route extends Hook protected array $aliasPaths = []; /** - * Alias HTTP methods this route is also registered under. + * Additional HTTP methods this route is also registered under. * * @var array */ - protected array $aliasMethods = []; + protected array $additionalMethods = []; /** * Internal counter. @@ -52,11 +52,15 @@ class Route extends Hook */ protected int $order; - public function __construct(string $method, string $path) + /** + * @param array $additionalMethods + */ + public function __construct(string $method, string $path, array $additionalMethods = []) { parent::__construct(); $this->path($path); $this->method = $method; + $this->additionalMethods = $additionalMethods; $this->order = ++self::$counter; } @@ -85,7 +89,7 @@ public function alias(string $path): self { Router::addRouteAlias($path, $this); - foreach ($this->aliasMethods as $method) { + foreach ($this->additionalMethods as $method) { Router::addRouteAlias($path, $this, $method); } @@ -96,27 +100,6 @@ public function alias(string $path): self return $this; } - /** - * Register this route under an additional HTTP method. - * - * The route keeps reporting its primary method via getMethod(); - * use the request's method to tell how a request arrived. - */ - public function aliasMethod(string $method): self - { - Router::addRouteMethodAlias($method, $this); - - foreach ($this->aliasPaths as $path) { - Router::addRouteAlias($path, $this, $method); - } - - if (!\in_array($method, $this->aliasMethods, true)) { - $this->aliasMethods[] = $method; - } - - return $this; - } - /** * When set false, hooks for this route will be skipped. */ @@ -162,13 +145,13 @@ public function getAliasPaths(): array } /** - * Get alias methods. + * Get methods this route is registered under. * * @return array */ - public function getAliasMethods(): array + public function getMethods(): array { - return $this->aliasMethods; + return array_merge([$this->method], $this->additionalMethods); } /** diff --git a/src/Http/Router.php b/src/Http/Router.php index d8350532..ae294b39 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -88,6 +88,10 @@ public static function addRoute(Route $route): void } self::$routes[$route->getMethod()][$path] = $route; + + foreach (\array_slice($route->getMethods(), 1) as $method) { + self::addRouteMethod($method, $route); + } } /** @@ -121,14 +125,14 @@ public static function addRouteAlias(string $path, Route $route, ?string $method * * @throws \Exception */ - public static function addRouteMethodAlias(string $method, Route $route): void + public static function addRouteMethod(string $method, Route $route): void { if (!\array_key_exists($method, self::$routes)) { throw new Exception("Method ({$method}) not supported."); } if ($route->getPath() === '') { - throw new Exception('Method aliases are not supported for the wildcard route.'); + throw new Exception('Additional route methods are not supported for the wildcard route.'); } [$path, $params] = self::preparePath($route->getPath()); diff --git a/tests/HttpTest.php b/tests/HttpTest.php index a709b7a1..079f430b 100755 --- a/tests/HttpTest.php +++ b/tests/HttpTest.php @@ -250,12 +250,9 @@ public function testCanExecuteRoute(): void $this->assertSame('init-' . $resource . '-(init-homepage)-param-x*param-y-(shutdown-homepage)-shutdown', $result); } - public function testCanExecuteRouteWithMethodAlias(): void + public function testCanExecuteRouteWithMultipleMethods(): void { - $route = Http::get('/v1/oauth/userinfo'); - - $route - ->aliasMethod(Http::REQUEST_METHOD_POST) + Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/v1/oauth/userinfo') ->inject('request') ->action(function ($request) { echo 'userinfo:' . $request->getMethod(); diff --git a/tests/RouterTest.php b/tests/RouterTest.php index c38f67b9..54e11afa 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -142,27 +142,30 @@ public function testCanMatchMix(): void $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/register/lorem/ipsum')?->route); } - public function testCanMatchMethodAlias(): void + public function testCanMatchRouteWithMultipleMethods(): void { - $route = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); - $route->aliasMethod(Http::REQUEST_METHOD_POST); - - Router::addRoute($route); + $route = Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/userinfo'); $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/userinfo')?->route); $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); $this->assertNull(Router::match(Http::REQUEST_METHOD_PUT, '/userinfo')); $this->assertSame(Http::REQUEST_METHOD_GET, $route->getMethod()); - $this->assertSame([Http::REQUEST_METHOD_POST], $route->getAliasMethods()); + $this->assertSame([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], $route->getMethods()); } - public function testCanMatchMethodAliasWithPlaceholder(): void + public function testCanMatchRouteWithStringMethod(): void { - $route = new Route(Http::REQUEST_METHOD_GET, '/users/:id'); - $route->aliasMethod(Http::REQUEST_METHOD_POST); + $route = Http::routes(Http::REQUEST_METHOD_GET, '/userinfo'); - Router::addRoute($route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/userinfo')?->route); + $this->assertNull(Router::match(Http::REQUEST_METHOD_POST, '/userinfo')); + $this->assertSame([Http::REQUEST_METHOD_GET], $route->getMethods()); + } + + public function testCanMatchRouteWithMultipleMethodsAndPlaceholder(): void + { + $route = Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/users/:id'); $match = Router::match(Http::REQUEST_METHOD_POST, '/users/abc-123'); @@ -170,41 +173,28 @@ public function testCanMatchMethodAliasWithPlaceholder(): void $this->assertSame(['id' => 'abc-123'], $match?->params); } - public function testMethodAliasCrossesPathAliasesRegardlessOfOrder(): void + public function testRoutesCrossPathAliases(): void { - $routeA = new Route(Http::REQUEST_METHOD_GET, '/a'); - $routeA - ->alias('/a-old') - ->aliasMethod(Http::REQUEST_METHOD_POST); + $route = Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/a') + ->alias('/a-old'); - Router::addRoute($routeA); - - $routeB = new Route(Http::REQUEST_METHOD_GET, '/b'); - $routeB - ->aliasMethod(Http::REQUEST_METHOD_POST) - ->alias('/b-old'); - - Router::addRoute($routeB); - - $this->assertEquals($routeA, Router::match(Http::REQUEST_METHOD_POST, '/a')?->route); - $this->assertEquals($routeA, Router::match(Http::REQUEST_METHOD_POST, '/a-old')?->route); - $this->assertEquals($routeB, Router::match(Http::REQUEST_METHOD_POST, '/b')?->route); - $this->assertEquals($routeB, Router::match(Http::REQUEST_METHOD_POST, '/b-old')?->route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/a')?->route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/a')?->route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/a-old')?->route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/a-old')?->route); } - public function testCannotRegisterDuplicateMethodAlias(): void + public function testCannotRegisterDuplicateRouteMethod(): void { $routePOST = new Route(Http::REQUEST_METHOD_POST, '/userinfo'); Router::addRoute($routePOST); - $routeGET = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); - $this->expectException(\Exception::class); $this->expectExceptionMessage('Route for (POST:userinfo) already registered.'); - $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); + Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/userinfo'); } - public function testCanOverrideMethodAlias(): void + public function testCanOverrideRouteMethod(): void { Router::setAllowOverride(true); @@ -212,34 +202,31 @@ public function testCanOverrideMethodAlias(): void $routePOST = new Route(Http::REQUEST_METHOD_POST, '/userinfo'); Router::addRoute($routePOST); - $routeGET = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); - Router::addRoute($routeGET); - $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); - $routeGET->aliasMethod(Http::REQUEST_METHOD_POST); + $routeGET = Http::routes([ + Http::REQUEST_METHOD_GET, + Http::REQUEST_METHOD_POST, + Http::REQUEST_METHOD_POST, + ], '/userinfo'); $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); - $this->assertSame([Http::REQUEST_METHOD_POST], $routeGET->getAliasMethods()); + $this->assertSame([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], $routeGET->getMethods()); } finally { Router::setAllowOverride(false); } } - public function testCannotRegisterMethodAliasForUnknownMethod(): void + public function testCannotRegisterRouteForUnknownMethod(): void { - $route = new Route(Http::REQUEST_METHOD_GET, '/userinfo'); - $this->expectException(\Exception::class); $this->expectExceptionMessage('Method (TRACE) not supported.'); - $route->aliasMethod('TRACE'); + Http::routes([Http::REQUEST_METHOD_GET, 'TRACE'], '/userinfo'); } - public function testCannotRegisterMethodAliasOnWildcardRoute(): void + public function testCannotRegisterRouteWithoutMethods(): void { - $route = new Route('', ''); - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Method aliases are not supported for the wildcard route.'); - $route->aliasMethod(Http::REQUEST_METHOD_POST); + $this->expectExceptionMessage('At least one HTTP method is required.'); + Http::routes([], '/userinfo'); } public function testCanMatchFilename(): void From f79614e1631e2c0f7fe4b05dbb66d1de11b731c0 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 13:46:11 +0530 Subject: [PATCH 04/12] Rename multiple methods docs section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70f99ac4..36b6c72f 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ curl http://localhost:8000/hello-world?name=Appwrite It's always recommended to use params instead of getting params or body directly from the request resource. If you do that intentionally, always make sure to run validation right after fetching such a raw input. -### Aliases and Multiple Methods +### Multiple Methods A route can be registered under additional paths and multiple HTTP methods. All matching paths and methods dispatch to the same route, so the action, params, and hooks are defined only once. From c0ba6b5f5b7f760bd24a48a501d6cde13fdb3269 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 13:48:30 +0530 Subject: [PATCH 05/12] Remove unrelated route method getters --- src/Http/Route.php | 16 +++------------- src/Http/Router.php | 2 +- tests/RouterTest.php | 3 --- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/Http/Route.php b/src/Http/Route.php index 5e51b36f..256ec0b6 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -135,23 +135,13 @@ public function getHook(): bool } /** - * Get alias paths. + * Get additional methods this route is registered under. * * @return array */ - public function getAliasPaths(): array + public function getAdditionalMethods(): array { - return $this->aliasPaths; - } - - /** - * Get methods this route is registered under. - * - * @return array - */ - public function getMethods(): array - { - return array_merge([$this->method], $this->additionalMethods); + return $this->additionalMethods; } /** diff --git a/src/Http/Router.php b/src/Http/Router.php index ae294b39..8ebdaae5 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -89,7 +89,7 @@ public static function addRoute(Route $route): void self::$routes[$route->getMethod()][$path] = $route; - foreach (\array_slice($route->getMethods(), 1) as $method) { + foreach ($route->getAdditionalMethods() as $method) { self::addRouteMethod($method, $route); } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 54e11afa..f5279701 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -151,7 +151,6 @@ public function testCanMatchRouteWithMultipleMethods(): void $this->assertNull(Router::match(Http::REQUEST_METHOD_PUT, '/userinfo')); $this->assertSame(Http::REQUEST_METHOD_GET, $route->getMethod()); - $this->assertSame([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], $route->getMethods()); } public function testCanMatchRouteWithStringMethod(): void @@ -160,7 +159,6 @@ public function testCanMatchRouteWithStringMethod(): void $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/userinfo')?->route); $this->assertNull(Router::match(Http::REQUEST_METHOD_POST, '/userinfo')); - $this->assertSame([Http::REQUEST_METHOD_GET], $route->getMethods()); } public function testCanMatchRouteWithMultipleMethodsAndPlaceholder(): void @@ -209,7 +207,6 @@ public function testCanOverrideRouteMethod(): void ], '/userinfo'); $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); - $this->assertSame([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], $routeGET->getMethods()); } finally { Router::setAllowOverride(false); } From b7c67b9f62f2e840eebaba884773748fe3aa036f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 14:23:46 +0530 Subject: [PATCH 06/12] Validate route methods before registration --- src/Http/Http.php | 8 ++++++++ tests/RouterTest.php | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Http/Http.php b/src/Http/Http.php index 4e273e09..1a84afae 100755 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -238,6 +238,14 @@ public static function routes(string|array $methods, string $url): Route throw new \Exception('At least one HTTP method is required.'); } + $routes = Router::getRoutes(); + + foreach ($methods as $method) { + if (!\array_key_exists($method, $routes)) { + throw new \Exception("Method ({$method}) not supported."); + } + } + $route = new Route($methods[0], $url, \array_slice($methods, 1)); Router::addRoute($route); diff --git a/tests/RouterTest.php b/tests/RouterTest.php index f5279701..98d99152 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -219,6 +219,24 @@ public function testCannotRegisterRouteForUnknownMethod(): void Http::routes([Http::REQUEST_METHOD_GET, 'TRACE'], '/userinfo'); } + public function testUnknownMethodDoesNotPartiallyRegisterRoute(): void + { + try { + Http::routes([Http::REQUEST_METHOD_GET, 'TRACE'], '/userinfo'); + $this->fail('Expected unknown method exception.'); + } catch (\Exception $exception) { + $this->assertSame('Method (TRACE) not supported.', $exception->getMessage()); + } + + $routes = Router::getRoutes(); + $this->assertArrayNotHasKey('userinfo', $routes[Http::REQUEST_METHOD_GET]); + + $route = Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/userinfo'); + + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/userinfo')?->route); + $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); + } + public function testCannotRegisterRouteWithoutMethods(): void { $this->expectException(\Exception::class); From 856232e02c525fce588914dc3154449b448f6c21 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 14:27:23 +0530 Subject: [PATCH 07/12] Inline additional route method registration --- src/Http/Router.php | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/src/Http/Router.php b/src/Http/Router.php index 8ebdaae5..f956f750 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -90,7 +90,19 @@ public static function addRoute(Route $route): void self::$routes[$route->getMethod()][$path] = $route; foreach ($route->getAdditionalMethods() as $method) { - self::addRouteMethod($method, $route); + if (!\array_key_exists($method, self::$routes)) { + throw new Exception("Method ({$method}) not supported."); + } + + if ($route->getPath() === '') { + throw new Exception('Additional route methods are not supported for the wildcard route.'); + } + + if (\array_key_exists($path, self::$routes[$method]) && !self::$allowOverride) { + throw new Exception("Route for ({$method}:{$path}) already registered."); + } + + self::$routes[$method][$path] = $route; } } @@ -120,34 +132,6 @@ public static function addRouteAlias(string $path, Route $route, ?string $method self::$routes[$method][$alias] = $route; } - /** - * Register a route under an additional HTTP method, using its own path. - * - * @throws \Exception - */ - public static function addRouteMethod(string $method, Route $route): void - { - if (!\array_key_exists($method, self::$routes)) { - throw new Exception("Method ({$method}) not supported."); - } - - if ($route->getPath() === '') { - throw new Exception('Additional route methods are not supported for the wildcard route.'); - } - - [$path, $params] = self::preparePath($route->getPath()); - - if (\array_key_exists($path, self::$routes[$method]) && !self::$allowOverride) { - throw new Exception("Route for ({$method}:{$path}) already registered."); - } - - foreach ($params as $key => $index) { - $route->setPathParam($key, $index, $path); - } - - self::$routes[$method][$path] = $route; - } - /** * Register a method-agnostic catch-all route, used when nothing else matches. */ From fdf42aff24c9a7372f4597800193b1fdb521356f Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 14:38:58 +0530 Subject: [PATCH 08/12] Prevent partial registration on method conflicts --- src/Http/Router.php | 14 ++++++++------ tests/RouterTest.php | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/Http/Router.php b/src/Http/Router.php index f956f750..8212a44e 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -83,12 +83,6 @@ public static function addRoute(Route $route): void throw new Exception("Route for ({$route->getMethod()}:{$path}) already registered."); } - foreach ($params as $key => $index) { - $route->setPathParam($key, $index, $path); - } - - self::$routes[$route->getMethod()][$path] = $route; - foreach ($route->getAdditionalMethods() as $method) { if (!\array_key_exists($method, self::$routes)) { throw new Exception("Method ({$method}) not supported."); @@ -101,7 +95,15 @@ public static function addRoute(Route $route): void if (\array_key_exists($path, self::$routes[$method]) && !self::$allowOverride) { throw new Exception("Route for ({$method}:{$path}) already registered."); } + } + foreach ($params as $key => $index) { + $route->setPathParam($key, $index, $path); + } + + self::$routes[$route->getMethod()][$path] = $route; + + foreach ($route->getAdditionalMethods() as $method) { self::$routes[$method][$path] = $route; } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 98d99152..92a7e4cb 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -187,9 +187,20 @@ public function testCannotRegisterDuplicateRouteMethod(): void $routePOST = new Route(Http::REQUEST_METHOD_POST, '/userinfo'); Router::addRoute($routePOST); - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Route for (POST:userinfo) already registered.'); - Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/userinfo'); + try { + Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/userinfo'); + $this->fail('Expected duplicate route exception.'); + } catch (\Exception $exception) { + $this->assertSame('Route for (POST:userinfo) already registered.', $exception->getMessage()); + } + + $routes = Router::getRoutes(); + $this->assertArrayNotHasKey('userinfo', $routes[Http::REQUEST_METHOD_GET]); + + $routeGET = Http::routes(Http::REQUEST_METHOD_GET, '/userinfo'); + + $this->assertEquals($routeGET, Router::match(Http::REQUEST_METHOD_GET, '/userinfo')?->route); + $this->assertEquals($routePOST, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); } public function testCanOverrideRouteMethod(): void From 74674790fa2e432e44d76f2eaae2b6fd09e6832b Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 14:48:37 +0530 Subject: [PATCH 09/12] Validate route aliases before registration --- src/Http/Route.php | 2 ++ src/Http/Router.php | 22 ++++++++++++++++++++++ tests/RouterTest.php | 13 +++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/Http/Route.php b/src/Http/Route.php index 256ec0b6..bd1dee3c 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -87,6 +87,8 @@ public function path(string $path): self */ public function alias(string $path): self { + Router::validateRouteAlias($path, [$this->method, ...$this->additionalMethods]); + Router::addRouteAlias($path, $this); foreach ($this->additionalMethods as $method) { diff --git a/src/Http/Router.php b/src/Http/Router.php index 8212a44e..382a5536 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -108,6 +108,28 @@ public static function addRoute(Route $route): void } } + /** + * Validate that a route alias can be registered for every supplied method. + * + * @param array $methods + * + * @throws Exception + */ + public static function validateRouteAlias(string $path, array $methods): void + { + [$alias] = self::preparePath($path); + + foreach ($methods as $method) { + if (!\array_key_exists($method, self::$routes)) { + throw new Exception("Method ({$method}) not supported."); + } + + if (\array_key_exists($alias, self::$routes[$method]) && !self::$allowOverride) { + throw new Exception("Route for ({$method}:{$alias}) already registered."); + } + } + } + /** * Add route to router. * diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 92a7e4cb..25021e28 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -180,6 +180,19 @@ public function testRoutesCrossPathAliases(): void $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/a')?->route); $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_GET, '/a-old')?->route); $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/a-old')?->route); + + $routePOST = Http::routes(Http::REQUEST_METHOD_POST, '/b')->alias('/b-old'); + $routeGETPOST = Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/c'); + + try { + $routeGETPOST->alias('/b-old'); + $this->fail('Expected duplicate route alias exception.'); + } catch (\Exception $exception) { + $this->assertSame('Route for (POST:b-old) already registered.', $exception->getMessage()); + } + + $this->assertNull(Router::match(Http::REQUEST_METHOD_GET, '/b-old')); + $this->assertEquals($routePOST, Router::match(Http::REQUEST_METHOD_POST, '/b-old')?->route); } public function testCannotRegisterDuplicateRouteMethod(): void From e1517531304135555be5621244f314b03833a9aa Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 15:49:57 +0530 Subject: [PATCH 10/12] Store route methods as array --- README.md | 2 +- src/Http/Http.php | 2 +- src/Http/Route.php | 36 ++++++++++++++++-------------------- src/Http/Router.php | 29 ++++++++++++++++------------- tests/RouteTest.php | 1 + tests/RouterTest.php | 2 +- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 36b6c72f..8f8b3ded 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Http::routes([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], '/oauth/user }); ``` -Path aliases and multiple methods combine: a route with both responds on every method under every path. Note that `getMethod()` on the route returns the first method it was defined with; use the request resource to tell how a request arrived. +Path aliases and multiple methods combine: a route with both responds on every method under every path. Use `getMethods()` to inspect the methods a route was registered with, and use the request resource to tell how a request arrived. ### Hooks diff --git a/src/Http/Http.php b/src/Http/Http.php index 1a84afae..9c35a9b4 100755 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -246,7 +246,7 @@ public static function routes(string|array $methods, string $url): Route } } - $route = new Route($methods[0], $url, \array_slice($methods, 1)); + $route = new Route($methods, $url); Router::addRoute($route); return $route; diff --git a/src/Http/Route.php b/src/Http/Route.php index bd1dee3c..b85fca32 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -7,9 +7,11 @@ class Route extends Hook { /** - * HTTP Method + * HTTP Methods + * + * @var array */ - protected string $method = ''; + protected array $methods = []; /** * Whether to use hook @@ -35,13 +37,6 @@ class Route extends Hook */ protected array $aliasPaths = []; - /** - * Additional HTTP methods this route is also registered under. - * - * @var array - */ - protected array $additionalMethods = []; - /** * Internal counter. */ @@ -53,14 +48,13 @@ class Route extends Hook protected int $order; /** - * @param array $additionalMethods + * @param string|array $methods */ - public function __construct(string $method, string $path, array $additionalMethods = []) + public function __construct(string|array $methods, string $path) { parent::__construct(); $this->path($path); - $this->method = $method; - $this->additionalMethods = $additionalMethods; + $this->methods = \is_array($methods) ? array_values(array_unique($methods)) : [$methods]; $this->order = ++self::$counter; } @@ -87,11 +81,11 @@ public function path(string $path): self */ public function alias(string $path): self { - Router::validateRouteAlias($path, [$this->method, ...$this->additionalMethods]); + Router::validateRouteAlias($path, $this->methods); Router::addRouteAlias($path, $this); - foreach ($this->additionalMethods as $method) { + foreach (\array_slice($this->methods, 1) as $method) { Router::addRouteAlias($path, $this, $method); } @@ -113,11 +107,13 @@ public function hook(bool $hook = true): self } /** - * Get HTTP Method + * Get primary HTTP method. + * + * @deprecated Use getMethods() instead. */ public function getMethod(): string { - return $this->method; + return $this->methods[0] ?? ''; } /** @@ -137,13 +133,13 @@ public function getHook(): bool } /** - * Get additional methods this route is registered under. + * Get HTTP methods this route is registered under. * * @return array */ - public function getAdditionalMethods(): array + public function getMethods(): array { - return $this->additionalMethods; + return $this->methods; } /** diff --git a/src/Http/Router.php b/src/Http/Router.php index 382a5536..9e47e683 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -74,26 +74,29 @@ public static function setAllowOverride(bool $value): void public static function addRoute(Route $route): void { [$path, $params] = self::preparePath($route->getPath()); + $methods = $route->getMethods(); + $method = $methods[0] ?? ''; + $additionalMethods = \array_slice($methods, 1); - if (!\array_key_exists($route->getMethod(), self::$routes)) { - throw new Exception("Method ({$route->getMethod()}) not supported."); + if (!\array_key_exists($method, self::$routes)) { + throw new Exception("Method ({$method}) not supported."); } - if (\array_key_exists($path, self::$routes[$route->getMethod()]) && !self::$allowOverride) { - throw new Exception("Route for ({$route->getMethod()}:{$path}) already registered."); + if (\array_key_exists($path, self::$routes[$method]) && !self::$allowOverride) { + throw new Exception("Route for ({$method}:{$path}) already registered."); } - foreach ($route->getAdditionalMethods() as $method) { - if (!\array_key_exists($method, self::$routes)) { - throw new Exception("Method ({$method}) not supported."); + foreach ($additionalMethods as $additionalMethod) { + if (!\array_key_exists($additionalMethod, self::$routes)) { + throw new Exception("Method ({$additionalMethod}) not supported."); } if ($route->getPath() === '') { throw new Exception('Additional route methods are not supported for the wildcard route.'); } - if (\array_key_exists($path, self::$routes[$method]) && !self::$allowOverride) { - throw new Exception("Route for ({$method}:{$path}) already registered."); + if (\array_key_exists($path, self::$routes[$additionalMethod]) && !self::$allowOverride) { + throw new Exception("Route for ({$additionalMethod}:{$path}) already registered."); } } @@ -101,10 +104,10 @@ public static function addRoute(Route $route): void $route->setPathParam($key, $index, $path); } - self::$routes[$route->getMethod()][$path] = $route; + self::$routes[$method][$path] = $route; - foreach ($route->getAdditionalMethods() as $method) { - self::$routes[$method][$path] = $route; + foreach ($additionalMethods as $additionalMethod) { + self::$routes[$additionalMethod][$path] = $route; } } @@ -137,7 +140,7 @@ public static function validateRouteAlias(string $path, array $methods): void */ public static function addRouteAlias(string $path, Route $route, ?string $method = null): void { - $method ??= $route->getMethod(); + $method ??= $route->getMethods()[0] ?? ''; if (!\array_key_exists($method, self::$routes)) { throw new Exception("Method ({$method}) not supported."); diff --git a/tests/RouteTest.php b/tests/RouteTest.php index 8c766ed4..81d91590 100755 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -19,6 +19,7 @@ public function setUp(): void public function testCanGetMethod(): void { $this->assertSame('GET', $this->route->getMethod()); + $this->assertSame(['GET'], $this->route->getMethods()); } public function testCanGetAndSetPath(): void diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 25021e28..9d5b67b3 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -150,7 +150,7 @@ public function testCanMatchRouteWithMultipleMethods(): void $this->assertEquals($route, Router::match(Http::REQUEST_METHOD_POST, '/userinfo')?->route); $this->assertNull(Router::match(Http::REQUEST_METHOD_PUT, '/userinfo')); - $this->assertSame(Http::REQUEST_METHOD_GET, $route->getMethod()); + $this->assertSame([Http::REQUEST_METHOD_GET, Http::REQUEST_METHOD_POST], $route->getMethods()); } public function testCanMatchRouteWithStringMethod(): void From 3d124cfcfa62214a382710cc6b2f1fdc0466036a Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 16:43:31 +0530 Subject: [PATCH 11/12] Simplify route alias registration --- src/Http/Route.php | 6 ------ src/Http/Router.php | 35 ++++++++--------------------------- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/src/Http/Route.php b/src/Http/Route.php index b85fca32..64b00365 100755 --- a/src/Http/Route.php +++ b/src/Http/Route.php @@ -81,14 +81,8 @@ public function path(string $path): self */ public function alias(string $path): self { - Router::validateRouteAlias($path, $this->methods); - Router::addRouteAlias($path, $this); - foreach (\array_slice($this->methods, 1) as $method) { - Router::addRouteAlias($path, $this, $method); - } - if (!\in_array($path, $this->aliasPaths, true)) { $this->aliasPaths[] = $path; } diff --git a/src/Http/Router.php b/src/Http/Router.php index 9e47e683..09744ba6 100644 --- a/src/Http/Router.php +++ b/src/Http/Router.php @@ -112,15 +112,14 @@ public static function addRoute(Route $route): void } /** - * Validate that a route alias can be registered for every supplied method. - * - * @param array $methods + * Add route to router. * - * @throws Exception + * @throws \Exception */ - public static function validateRouteAlias(string $path, array $methods): void + public static function addRouteAlias(string $path, Route $route): void { - [$alias] = self::preparePath($path); + $methods = $route->getMethods(); + [$alias, $params] = self::preparePath($path); foreach ($methods as $method) { if (!\array_key_exists($method, self::$routes)) { @@ -131,32 +130,14 @@ public static function validateRouteAlias(string $path, array $methods): void throw new Exception("Route for ({$method}:{$alias}) already registered."); } } - } - - /** - * Add route to router. - * - * @throws \Exception - */ - public static function addRouteAlias(string $path, Route $route, ?string $method = null): void - { - $method ??= $route->getMethods()[0] ?? ''; - - if (!\array_key_exists($method, self::$routes)) { - throw new Exception("Method ({$method}) not supported."); - } - - [$alias, $params] = self::preparePath($path); - - if (\array_key_exists($alias, self::$routes[$method]) && !self::$allowOverride) { - throw new Exception("Route for ({$method}:{$alias}) already registered."); - } foreach ($params as $key => $index) { $route->setPathParam($key, $index, $alias); } - self::$routes[$method][$alias] = $route; + foreach ($methods as $method) { + self::$routes[$method][$alias] = $route; + } } /** From 82e2778f1bbcd154fac33c80db403bb2d440015c Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Wed, 10 Jun 2026 16:47:25 +0530 Subject: [PATCH 12/12] Fix Docker test image file list --- Dockerfile.fpm | 2 +- Dockerfile.swoole | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.fpm b/Dockerfile.fpm index b13a6496..dc243eb7 100644 --- a/Dockerfile.fpm +++ b/Dockerfile.fpm @@ -41,7 +41,7 @@ COPY --from=composer:2.7 /usr/bin/composer /usr/local/bin/composer COPY ./src /usr/share/nginx/html/src COPY ./tests /usr/share/nginx/html/tests COPY ./phpunit.xml /usr/share/nginx/html/phpunit.xml -COPY ./composer.json ./composer.lock ./phpstan.neon ./pint.json /usr/share/nginx/html/ +COPY ./composer.json ./composer.lock ./phpstan.neon /usr/share/nginx/html/ COPY --from=step0 /usr/local/src/vendor /usr/share/nginx/html/vendor # Supervisord Conf diff --git a/Dockerfile.swoole b/Dockerfile.swoole index 6ddb4309..4355b8ad 100644 --- a/Dockerfile.swoole +++ b/Dockerfile.swoole @@ -24,7 +24,7 @@ COPY --from=composer:2.7 /usr/bin/composer /usr/local/bin/composer COPY ./src /usr/src/code/src COPY ./tests /usr/src/code/tests COPY ./phpunit.xml /usr/src/code/phpunit.xml -COPY ./composer.json ./composer.lock ./phpstan.neon ./pint.json /usr/src/code/ +COPY ./composer.json ./composer.lock ./phpstan.neon /usr/src/code/ COPY --from=step0 /usr/local/src/vendor /usr/src/code/vendor EXPOSE 80