Skip to content

Commit b58bfab

Browse files
loks0nclaude
andcommitted
Make 'route' injection frame-local instead of stateful
The save/restore pattern was just bookkeeping around a shared mutable slot — anything else writing to context['route'] during dispatch would break the restore, and a missed restore in any branch leaks the inner match into the outer frame. Drop context['route'] entirely. Pass the dispatch frame's Route through to getArguments and special-case the 'route' injection there. Each dispatch frame (including sub-requests via execute()) carries its own matched Route as a parameter; nested calls can't trample each other because there's no shared state to trample. Telemetry in run() now reads the outer match by calling match() once locally — match() is pure and cheap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 30e1405 commit b58bfab

1 file changed

Lines changed: 19 additions & 20 deletions

File tree

src/Http/Http.php

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -552,23 +552,23 @@ public function execute(Request $request, Response $response): static
552552
foreach (self::$options as $option) { // Group options hooks
553553
/** @var Hook $option */
554554
if (\in_array($group, $option->getGroups())) {
555-
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
555+
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams(), $match->route));
556556
}
557557
}
558558
}
559559

560560
foreach (self::$options as $option) { // Global options hooks
561561
/** @var Hook $option */
562562
if (\in_array('*', $option->getGroups())) {
563-
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams()));
563+
\call_user_func_array($option->getAction(), $this->getArguments($option, [], $request->getParams(), $match?->route));
564564
}
565565
}
566566
} catch (\Throwable $e) {
567567
foreach (self::$errors as $error) { // Global error hooks
568568
/** @var Hook $error */
569569
if (\in_array('*', $error->getGroups())) {
570570
$this->context()->set('error', fn() => $e, []);
571-
\call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams()));
571+
\call_user_func_array($error->getAction(), $this->getArguments($error, [], $request->getParams(), $match?->route));
572572
}
573573
}
574574
}
@@ -591,15 +591,11 @@ public function execute(Request $request, Response $response): static
591591
$arguments = [];
592592
$groups = $route->getGroups();
593593

594-
$context = $this->context();
595-
$priorRoute = $context->has('route') ? $context->get('route') : null;
596-
$context->set('route', fn() => $route, []);
597-
598594
try {
599595
if ($route->getHook()) {
600596
foreach (self::$init as $hook) { // Global init hooks
601597
if (\in_array('*', $hook->getGroups())) {
602-
$arguments = $this->getArguments($hook, $match->params, $request->getParams());
598+
$arguments = $this->getArguments($hook, $match->params, $request->getParams(), $route);
603599
\call_user_func_array($hook->getAction(), $arguments);
604600
}
605601
}
@@ -608,21 +604,21 @@ public function execute(Request $request, Response $response): static
608604
foreach ($groups as $group) {
609605
foreach (self::$init as $hook) { // Group init hooks
610606
if (\in_array($group, $hook->getGroups())) {
611-
$arguments = $this->getArguments($hook, $match->params, $request->getParams());
607+
$arguments = $this->getArguments($hook, $match->params, $request->getParams(), $route);
612608
\call_user_func_array($hook->getAction(), $arguments);
613609
}
614610
}
615611
}
616612

617613
if (!$response->isSent()) {
618-
$arguments = $this->getArguments($route, $match->params, $request->getParams());
614+
$arguments = $this->getArguments($route, $match->params, $request->getParams(), $route);
619615
\call_user_func_array($route->getAction(), $arguments);
620616
}
621617

622618
foreach ($groups as $group) {
623619
foreach (self::$shutdown as $hook) { // Group shutdown hooks
624620
if (\in_array($group, $hook->getGroups())) {
625-
$arguments = $this->getArguments($hook, $match->params, $request->getParams());
621+
$arguments = $this->getArguments($hook, $match->params, $request->getParams(), $route);
626622
\call_user_func_array($hook->getAction(), $arguments);
627623
}
628624
}
@@ -631,7 +627,7 @@ public function execute(Request $request, Response $response): static
631627
if ($route->getHook()) {
632628
foreach (self::$shutdown as $hook) { // Group shutdown hooks
633629
if (\in_array('*', $hook->getGroups())) {
634-
$arguments = $this->getArguments($hook, $match->params, $request->getParams());
630+
$arguments = $this->getArguments($hook, $match->params, $request->getParams(), $route);
635631
\call_user_func_array($hook->getAction(), $arguments);
636632
}
637633
}
@@ -643,7 +639,7 @@ public function execute(Request $request, Response $response): static
643639
foreach (self::$errors as $error) { // Group error hooks
644640
if (\in_array($group, $error->getGroups())) {
645641
try {
646-
$arguments = $this->getArguments($error, $match->params, $request->getParams());
642+
$arguments = $this->getArguments($error, $match->params, $request->getParams(), $route);
647643
\call_user_func_array($error->getAction(), $arguments);
648644
} catch (\Throwable $e) {
649645
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
@@ -655,15 +651,13 @@ public function execute(Request $request, Response $response): static
655651
foreach (self::$errors as $error) { // Global error hooks
656652
if (\in_array('*', $error->getGroups())) {
657653
try {
658-
$arguments = $this->getArguments($error, $match->params, $request->getParams());
654+
$arguments = $this->getArguments($error, $match->params, $request->getParams(), $route);
659655
\call_user_func_array($error->getAction(), $arguments);
660656
} catch (\Throwable $e) {
661657
throw new Exception('Error handler had an error: ' . $e->getMessage(), 500, $e);
662658
}
663659
}
664660
}
665-
} finally {
666-
$context->set('route', fn() => $priorRoute, []);
667661
}
668662

669663
return $this;
@@ -677,7 +671,7 @@ public function execute(Request $request, Response $response): static
677671
* @return array<int, mixed>
678672
* @throws Exception
679673
*/
680-
protected function getArguments(Hook $hook, array $values, array $requestParams): array
674+
protected function getArguments(Hook $hook, array $values, array $requestParams, ?Route $route = null): array
681675
{
682676
$arguments = [];
683677
foreach ($hook->getParams() as $key => $param) { // Get value from route or request object
@@ -727,6 +721,13 @@ protected function getArguments(Hook $hook, array $values, array $requestParams)
727721
}
728722

729723
foreach ($hook->getInjections() as $injection) {
724+
// 'route' is frame-local: pass the dispatch frame's matched Route
725+
// through directly instead of routing through shared context.
726+
if ($injection['name'] === 'route') {
727+
$arguments[$injection['order']] = $route;
728+
continue;
729+
}
730+
730731
$arguments[$injection['order']] = $this->adapter->context()->get($injection['name']);
731732
}
732733

@@ -756,13 +757,11 @@ public function run(Request $request, Response $response): static
756757
$start = microtime(true);
757758
$result = $this->runInternal($request, $response);
758759

759-
$route = $this->context()->has('route') ? $this->context()->get('route') : null;
760-
761760
$requestDuration = microtime(true) - $start;
762761
$attributes = [
763762
'url.scheme' => $request->getProtocol(),
764763
'http.request.method' => $request->getMethod(),
765-
'http.route' => $route instanceof Route ? $route->getPath() : null,
764+
'http.route' => $this->match($request)?->route->getPath(),
766765
'http.response.status_code' => $response->getStatusCode(),
767766
];
768767
$this->requestDuration->record($requestDuration, $attributes);

0 commit comments

Comments
 (0)