From cec784970ce56c4426fcb68991e4d737f9dadfe2 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:04:46 +0100 Subject: [PATCH 1/2] Skip action if response already sent by init hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If an init hook (e.g. cache) calls $response->send(), the action should not run — the response has already been delivered to the client. Without this guard the action executes in full, wasting worker time on work whose output is immediately discarded. Shutdown hooks still run so metrics, billing, and other teardown logic are unaffected. Co-Authored-By: Claude Sonnet 4.6 --- src/Http/Http.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Http/Http.php b/src/Http/Http.php index 53a76fd..ce06bd5 100755 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -720,8 +720,10 @@ public function execute(Route $route, Request $request): static } } - $arguments = $this->getArguments($route, $pathValues, $request->getParams()); - \call_user_func_array($route->getAction(), $arguments); + if (!$response->isSent()) { + $arguments = $this->getArguments($route, $pathValues, $request->getParams()); + \call_user_func_array($route->getAction(), $arguments); + } foreach ($groups as $group) { foreach (self::$shutdown as $hook) { // Group shutdown hooks From b3f099d36b607d8c82d375ca9c7ff1a9347ea979 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Sun, 12 Apr 2026 15:18:08 +0100 Subject: [PATCH 2/2] Fix: pass Response to execute() so isSent() guard is in scope The 0.34.x execute() method had dropped the Response parameter compared to 0.33.x, meaning $response->isSent() in the action guard would be an undefined variable. Restore the parameter and update all call sites in the test suite. Co-Authored-By: Claude Sonnet 4.6 --- src/Http/Http.php | 4 ++-- tests/HttpTest.php | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Http/Http.php b/src/Http/Http.php index ce06bd5..7ce6f0e 100755 --- a/src/Http/Http.php +++ b/src/Http/Http.php @@ -693,7 +693,7 @@ public function match(Request $request, bool $fresh = true): ?Route * @param Route $route * @param Request $request */ - public function execute(Route $route, Request $request): static + public function execute(Route $route, Request $request, Response $response): static { $arguments = []; $groups = $route->getGroups(); @@ -955,7 +955,7 @@ private function runInternal(Request $request, Response $response): static } if (null !== $route) { - return $this->execute($route, $request); + return $this->execute($route, $request, $response); } elseif (self::REQUEST_METHOD_OPTIONS == $method) { try { foreach ($groups as $group) { diff --git a/tests/HttpTest.php b/tests/HttpTest.php index 060ec33..a022620 100755 --- a/tests/HttpTest.php +++ b/tests/HttpTest.php @@ -108,7 +108,7 @@ public function testCanExecuteRoute(): void }); \ob_start(); - $this->http->execute($route, new Request()); + $this->http->execute($route, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -132,7 +132,7 @@ public function testCanExecuteRoute(): void \ob_start(); $request = new UtopiaFPMRequestTest(); $request::_setParams(['x' => 'param-x', 'y' => 'param-y', 'z' => 'param-z']); - $this->http->execute($route, $request); + $this->http->execute($route, $request, new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -152,7 +152,7 @@ public function testCanExecuteRoute(): void \ob_start(); $request = new UtopiaFPMRequestTest(); $request::_setParams(['x' => 'param-x', 'y' => 'param-y']); - $this->http->execute($route, $request); + $this->http->execute($route, $request, new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -224,7 +224,7 @@ public function testCanExecuteRoute(): void \ob_start(); $request = new UtopiaFPMRequestTest(); $request::_setParams(['x' => 'param-x', 'y' => 'param-y']); - $this->http->execute($route, $request); + $this->http->execute($route, $request, new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -234,7 +234,7 @@ public function testCanExecuteRoute(): void \ob_start(); $request = new UtopiaFPMRequestTest(); $request::_setParams(['x' => 'param-x', 'y' => 'param-y']); - $this->http->execute($homepage, $request); + $this->http->execute($homepage, $request, new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -264,7 +264,7 @@ public function testCanAddAndExecuteHooks() }); \ob_start(); - $this->http->execute($route, new Request()); + $this->http->execute($route, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -280,7 +280,7 @@ public function testCanAddAndExecuteHooks() }); \ob_start(); - $this->http->execute($route, new Request()); + $this->http->execute($route, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -348,7 +348,7 @@ public function testCanHookThrowExceptions() }); \ob_start(); - $this->http->execute($route, new Request()); + $this->http->execute($route, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -356,7 +356,7 @@ public function testCanHookThrowExceptions() \ob_start(); $_GET['y'] = 'y-def'; - $this->http->execute($route, new Request()); + $this->http->execute($route, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -606,7 +606,7 @@ public function testCallableStringParametersNotExecuted(): void }); \ob_start(); - $this->http->execute($route, new Request()); + $this->http->execute($route, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -624,7 +624,7 @@ public function testCallableStringParametersNotExecuted(): void \ob_start(); $request = new UtopiaFPMRequestTest(); $request::_setParams(['func' => 'system']); - $this->http->execute($route2, $request); + $this->http->execute($route2, $request, new Response()); $result = \ob_get_contents(); \ob_end_clean(); @@ -642,7 +642,7 @@ public function testCallableStringParametersNotExecuted(): void }); \ob_start(); - $this->http->execute($route3, new Request()); + $this->http->execute($route3, new Request(), new Response()); $result = \ob_get_contents(); \ob_end_clean();