Skip to content

Commit a6e1372

Browse files
loks0nclaude
andcommitted
Move matched route + matched path into request context
The Http instance is shared across coroutines, so $this->route and $this->matchedPath would race the same way Route's mutable fields did. Store them in the per-request context() container instead, which is already request-scoped post-#254. getRoute()/setRoute() now read/write through the context too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 339f1ce commit a6e1372

1 file changed

Lines changed: 34 additions & 20 deletions

File tree

src/Http/Http.php

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,14 @@ class Http
9999
protected static array $requestHooks = [];
100100

101101
/**
102-
* Route
102+
* Per-request context keys for the matched route + matched template key.
103103
*
104-
* Memory cached result for chosen route
104+
* The match result lives in the request-scoped context() container rather
105+
* than on $this so it does not race when the Http instance is shared
106+
* across coroutines.
105107
*/
106-
protected ?Route $route = null;
107-
108-
/**
109-
* Matched route key (template after placeholder substitution).
110-
*
111-
* Cached alongside $route for $fresh=false re-matches.
112-
*/
113-
protected string $matchedPath = '';
108+
private const string CONTEXT_ROUTE = 'route';
109+
private const string CONTEXT_MATCHED_PATH = 'matchedPath';
114110

115111
/**
116112
* Compression
@@ -423,15 +419,21 @@ public static function getRoutes(): array
423419
*/
424420
public function getRoute(): ?Route
425421
{
426-
return $this->route ?? null;
422+
$context = $this->context();
423+
if (!$context->has(self::CONTEXT_ROUTE)) {
424+
return null;
425+
}
426+
427+
$route = $context->get(self::CONTEXT_ROUTE);
428+
return $route instanceof Route ? $route : null;
427429
}
428430

429431
/**
430432
* Set the current route
431433
*/
432434
public function setRoute(Route $route): self
433435
{
434-
$this->route = $route;
436+
$this->context()->set(self::CONTEXT_ROUTE, fn() => $route, []);
435437

436438
return $this;
437439
}
@@ -556,12 +558,23 @@ public function match(Request $request, bool $fresh = true): ?Route
556558
* matched against. Returning the matched key separately avoids mutating
557559
* the shared Route instance, which would race under coroutines.
558560
*
561+
* Caches the result in the per-request context so re-matches with
562+
* $fresh=false hit memory without re-running Router::match.
563+
*
559564
* @return array{0: Route, 1: string}|null
560565
*/
561566
private function matchInternal(Request $request, bool $fresh = true): ?array
562567
{
563-
if (null !== $this->route && !$fresh) {
564-
return [$this->route, $this->matchedPath];
568+
$context = $this->context();
569+
570+
if (!$fresh && $context->has(self::CONTEXT_ROUTE)) {
571+
$route = $context->get(self::CONTEXT_ROUTE);
572+
if ($route instanceof Route) {
573+
$matchedPath = $context->has(self::CONTEXT_MATCHED_PATH)
574+
? (string) $context->get(self::CONTEXT_MATCHED_PATH)
575+
: '';
576+
return [$route, $matchedPath];
577+
}
565578
}
566579

567580
$url = parse_url($request->getURI(), PHP_URL_PATH);
@@ -572,12 +585,12 @@ private function matchInternal(Request $request, bool $fresh = true): ?array
572585
$match = Router::match($method, $url);
573586

574587
if ($match === null) {
575-
$this->route = null;
576-
$this->matchedPath = '';
577588
return null;
578589
}
579590

580-
[$this->route, $this->matchedPath] = $match;
591+
[$route, $matchedPath] = $match;
592+
$context->set(self::CONTEXT_ROUTE, fn() => $route, []);
593+
$context->set(self::CONTEXT_MATCHED_PATH, fn() => $matchedPath, []);
581594

582595
return $match;
583596
}
@@ -746,11 +759,14 @@ public function run(Request $request, Response $response): static
746759
$start = microtime(true);
747760
$result = $this->runInternal($request, $response);
748761

762+
$context = $this->context();
763+
$matchedRoute = $context->has(self::CONTEXT_ROUTE) ? $context->get(self::CONTEXT_ROUTE) : null;
764+
749765
$requestDuration = microtime(true) - $start;
750766
$attributes = [
751767
'url.scheme' => $request->getProtocol(),
752768
'http.request.method' => $request->getMethod(),
753-
'http.route' => $this->route?->getPath(),
769+
'http.route' => $matchedRoute instanceof Route ? $matchedRoute->getPath() : null,
754770
'http.response.status_code' => $response->getStatusCode(),
755771
];
756772
$this->requestDuration->record($requestDuration, $attributes);
@@ -821,8 +837,6 @@ private function runInternal(Request $request, Response $response): static
821837
$matchedPath = $match[1] ?? '';
822838
$groups = ($route instanceof Route) ? $route->getGroups() : [];
823839

824-
$this->context()->set('route', fn() => $route, []);
825-
826840
if (self::REQUEST_METHOD_HEAD === $method) {
827841
$method = self::REQUEST_METHOD_GET;
828842
$response->disablePayload();

0 commit comments

Comments
 (0)