Skip to content

Commit 03c51c2

Browse files
loks0nclaude
andcommitted
Introduce Router\Result DTO for match results
Replace the [Route, matchedPath] tuple with a readonly Router\Result value object so callers get named, typed access instead of positional unpacking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9d2e861 commit 03c51c2

3 files changed

Lines changed: 49 additions & 26 deletions

File tree

src/Http/Http.php

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Utopia\Http;
44

55
use Utopia\DI\Container;
6+
use Utopia\Http\Router\Result as RouterResult;
67
use Utopia\Servers\Hook;
78
use Utopia\Telemetry\Adapter as Telemetry;
89
use Utopia\Telemetry\Adapter\None as NoTelemetry;
@@ -540,20 +541,19 @@ public function start(): void
540541
*/
541542
public function match(Request $request, bool $fresh = true): ?Route
542543
{
543-
return $this->matchInternal($request, $fresh)[0] ?? null;
544+
return $this->matchInternal($request, $fresh)?->route;
544545
}
545546

546547
/**
547-
* Match a request and return both the matched Route and the route key it
548-
* matched against. Returning the matched key separately avoids mutating
549-
* the shared Route instance, which would race under coroutines.
548+
* Match a request and return the {@see RouterResult} carrying both the
549+
* Route and the route key it matched against. Returning the matched key
550+
* via the Result avoids mutating the shared Route instance, which would
551+
* race under coroutines.
550552
*
551553
* Caches the result in the per-request context so re-matches with
552554
* $fresh=false hit memory without re-running Router::match.
553-
*
554-
* @return array{0: Route, 1: string}|null
555555
*/
556-
private function matchInternal(Request $request, bool $fresh = true): ?array
556+
private function matchInternal(Request $request, bool $fresh = true): ?RouterResult
557557
{
558558
$context = $this->context();
559559

@@ -563,7 +563,7 @@ private function matchInternal(Request $request, bool $fresh = true): ?array
563563
$matchedPath = $context->has('matchedPath')
564564
? (string) $context->get('matchedPath')
565565
: '';
566-
return [$route, $matchedPath];
566+
return new RouterResult($route, $matchedPath);
567567
}
568568
}
569569

@@ -572,17 +572,18 @@ private function matchInternal(Request $request, bool $fresh = true): ?array
572572
$method = $request->getMethod();
573573
$method = (self::REQUEST_METHOD_HEAD === $method) ? self::REQUEST_METHOD_GET : $method;
574574

575-
$match = Router::match($method, $url);
575+
$result = Router::match($method, $url);
576576

577-
if ($match === null) {
577+
if ($result === null) {
578578
return null;
579579
}
580580

581-
[$route, $matchedPath] = $match;
581+
$route = $result->route;
582+
$matchedPath = $result->matchedPath;
582583
$context->set('route', fn() => $route, []);
583584
$context->set('matchedPath', fn() => $matchedPath, []);
584585

585-
return $match;
586+
return $result;
586587
}
587588

588589
/**
@@ -823,8 +824,8 @@ private function runInternal(Request $request, Response $response): static
823824

824825
$method = $request->getMethod();
825826
$match = $this->matchInternal($request);
826-
$route = $match[0] ?? null;
827-
$matchedPath = $match[1] ?? '';
827+
$route = $match?->route;
828+
$matchedPath = $match->matchedPath ?? '';
828829
$groups = ($route instanceof Route) ? $route->getGroups() : [];
829830

830831
if (self::REQUEST_METHOD_HEAD === $method) {

src/Http/Router.php

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Utopia\Http;
44

55
use Exception;
6+
use Utopia\Http\Router\Result;
67

78
class Router
89
{
@@ -121,17 +122,16 @@ public static function setWildcard(?Route $route): void
121122
/**
122123
* Match route against the method and path.
123124
*
124-
* Returns the matched Route together with the route key it matched against
125-
* (the registered template after placeholder substitution, or '*' for a
126-
* wildcard, or '' for the method-agnostic wildcard). Returning the matched key avoids
127-
* mutating the shared Route instance, which would race under coroutines.
128-
*
129-
* @return array{0: Route, 1: string}|null
125+
* Returns a {@see Result} carrying the matched Route together with the
126+
* route key it matched against (the registered template after placeholder
127+
* substitution, '*' for a wildcard, or '' for the method-agnostic
128+
* wildcard). Returning the key separately avoids mutating the shared
129+
* Route instance, which would race under coroutines.
130130
*/
131-
public static function match(string $method, string $path): ?array
131+
public static function match(string $method, string $path): ?Result
132132
{
133133
if (!\array_key_exists($method, self::$routes)) {
134-
return self::$wildcard !== null ? [self::$wildcard, ''] : null;
134+
return self::$wildcard !== null ? new Result(self::$wildcard, '') : null;
135135
}
136136

137137
$parts = array_values(array_filter(explode('/', $path), fn($segment) => $segment !== ''));
@@ -149,7 +149,7 @@ public static function match(string $method, string $path): ?array
149149
);
150150

151151
if (\array_key_exists($match, self::$routes[$method])) {
152-
return [self::$routes[$method][$match], $match];
152+
return new Result(self::$routes[$method][$match], $match);
153153
}
154154
}
155155

@@ -158,7 +158,7 @@ public static function match(string $method, string $path): ?array
158158
*/
159159
$match = self::WILDCARD_TOKEN;
160160
if (\array_key_exists($match, self::$routes[$method])) {
161-
return [self::$routes[$method][$match], $match];
161+
return new Result(self::$routes[$method][$match], $match);
162162
}
163163

164164
/**
@@ -168,12 +168,12 @@ public static function match(string $method, string $path): ?array
168168
$current = ($current ?? '') . "{$part}/";
169169
$match = $current . self::WILDCARD_TOKEN;
170170
if (\array_key_exists($match, self::$routes[$method])) {
171-
return [self::$routes[$method][$match], $match];
171+
return new Result(self::$routes[$method][$match], $match);
172172
}
173173
}
174174

175175
if (self::$wildcard !== null) {
176-
return [self::$wildcard, ''];
176+
return new Result(self::$wildcard, '');
177177
}
178178

179179
return null;

src/Http/Router/Result.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Utopia\Http\Router;
4+
5+
use Utopia\Http\Route;
6+
7+
/**
8+
* Immutable result of {@see \Utopia\Http\Router::match()}.
9+
*
10+
* Carries the matched Route together with the route key it matched against
11+
* (the registered template after placeholder substitution, '*' for a wildcard,
12+
* or '' for the method-agnostic wildcard). Returning a value object instead of
13+
* mutating the Route avoids racing the shared Route under coroutines.
14+
*/
15+
final readonly class Result
16+
{
17+
public function __construct(
18+
public Route $route,
19+
public string $matchedPath,
20+
) {
21+
}
22+
}

0 commit comments

Comments
 (0)