Skip to content

Commit 5f86d40

Browse files
loks0nclaude
andcommitted
Inject route's resolved arguments into shutdown/error hooks
The earlier removal of Hook::setParamValue writeback closed the concurrency hole but stranded a real use case: shutdown hooks that need the request's full resolved param map (post-validation, post- coercion) to do label substitution like {request.fileId}. Populate a name-keyed snapshot of the route's resolved arguments on the per-request context as 'arguments', set right before the action runs. Race-free (per-request context container, coroutine-local under Swoole), no per-Hook mutation, and reuses the values getArguments() already resolved — no validator double-invocation. Available via ->inject('arguments') from shutdown / error hooks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0707b7c commit 5f86d40

2 files changed

Lines changed: 41 additions & 0 deletions

File tree

src/Http/Http.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,18 @@ public function execute(Route $route, Request $request, Response $response): sta
631631

632632
if (!$response->isSent()) {
633633
$arguments = $this->getArguments($route, $pathValues, $request->getParams());
634+
635+
// Stash a name-keyed map of the route's resolved+validated
636+
// params on the context so shutdown / error hooks can read
637+
// the same values the action saw — e.g. for label
638+
// substitution like {request.fileId}. Race-free because
639+
// the context container is per-request.
640+
$resolved = [];
641+
foreach ($route->getParams() as $name => $param) {
642+
$resolved[$name] = $arguments[$param['order']] ?? null;
643+
}
644+
$this->setContext('arguments', fn() => $resolved);
645+
634646
\call_user_func_array($route->getAction(), $arguments);
635647
}
636648

tests/HttpTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,35 @@ public function testCanHookThrowExceptions(): void
366366
$this->assertSame('(init)-y-def-x-def-(shutdown)', $result);
367367
}
368368

369+
public function testShutdownHookCanInjectResolvedArguments(): void
370+
{
371+
$route = new Route('GET', '/files/:fileId');
372+
$route
373+
->param('fileId', '', new Text(64), 'file id', false)
374+
->param('width', 0, new Text(8), 'width', true)
375+
->action(function ($fileId, $width) {
376+
echo 'action:' . $fileId . ',' . $width;
377+
});
378+
379+
$this->http
380+
->shutdown()
381+
->inject('arguments')
382+
->action(function (array $arguments) {
383+
echo '|shutdown:fileId=' . $arguments['fileId'] . ',width=' . $arguments['width'];
384+
});
385+
386+
$request = new UtopiaFPMRequestTest();
387+
$request::_setParams(['fileId' => 'abc123', 'width' => '200']);
388+
$_SERVER['REQUEST_URI'] = '/files/abc123';
389+
390+
ob_start();
391+
$this->http->execute($route, $request, new Response());
392+
$result = ob_get_contents();
393+
ob_end_clean();
394+
395+
$this->assertSame('action:abc123,200|shutdown:fileId=abc123,width=200', $result);
396+
}
397+
369398
/**
370399
* @return \Iterator<string, array<int, string>>
371400
*/

0 commit comments

Comments
 (0)