Skip to content

Commit 89af716

Browse files
committed
refactor: simplify handleRequest method in CodeIgniter
1 parent 8c164d0 commit 89af716

2 files changed

Lines changed: 45 additions & 13 deletions

File tree

system/CodeIgniter.php

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use Config\Cache;
3636
use Config\Feature;
3737
use Config\Kint as KintConfig;
38+
use Config\Routing;
3839
use Config\Services;
3940
use Exception;
4041
use Kint;
@@ -475,8 +476,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
475476
if ($routeFilters !== null) {
476477
$filters->enableFilters($routeFilters, 'before');
477478

478-
$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false; // @phpstan-ignore nullCoalesce.property
479-
if (! $oldFilterOrder) {
479+
if (! config(Feature::class)->oldFilterOrder) {
480480
$routeFilters = array_reverse($routeFilters);
481481
}
482482

@@ -501,13 +501,13 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
501501
}
502502

503503
$returned = $this->startController();
504+
$gathered = false;
504505

505506
// If startController returned a Response (from an attribute or Closure), use it
506507
if ($returned instanceof ResponseInterface) {
507508
$this->gatherOutput($cacheConfig, $returned);
508-
}
509-
// Closure controller has run in startController().
510-
elseif (! is_callable($this->controller)) {
509+
$gathered = true;
510+
} elseif (! $this->controller instanceof Closure) {
511511
$controller = $this->createController();
512512

513513
if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
@@ -526,7 +526,9 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
526526
// If $returned is a string, then the controller output something,
527527
// probably a view, instead of echoing it directly. Send it along
528528
// so it can be used with the output.
529-
$this->gatherOutput($cacheConfig, $returned);
529+
if (! $gathered) {
530+
$this->gatherOutput($cacheConfig, $returned);
531+
}
530532

531533
if ($this->enableFilters) {
532534
/** @var Filters $filters */
@@ -543,7 +545,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
543545
}
544546
}
545547

546-
// Execute controller attributes' after() methods AFTER framework filters
548+
// Execute controller attributes' after() methods AFTER framework filters.
547549
if ((config('Routing')->useControllerAttributes ?? true) === true) { // @phpstan-ignore nullCoalesce.property
548550
$this->benchmark->start('route_attributes_after');
549551
$this->response = $this->router->executeAfterAttributes($this->request, $this->response);
@@ -560,8 +562,6 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
560562
$this->storePreviousURL(current_url(true));
561563
}
562564

563-
unset($uri);
564-
565565
return $this->response;
566566
}
567567

@@ -889,7 +889,7 @@ protected function startController()
889889
$this->benchmark->start('controller_constructor');
890890

891891
// Is it routed to a Closure?
892-
if (is_object($this->controller) && ($this->controller::class === 'Closure')) {
892+
if ($this->controller instanceof Closure) {
893893
$controller = $this->controller;
894894

895895
return $controller(...$this->router->params());
@@ -909,8 +909,7 @@ protected function startController()
909909
}
910910

911911
// Execute route attributes' before() methods
912-
// This runs after routing/validation but BEFORE expensive controller instantiation
913-
if ((config('Routing')->useControllerAttributes ?? true) === true) { // @phpstan-ignore nullCoalesce.property
912+
if ((config(Routing::class)->useControllerAttributes ?? true) === true) {
914913
$this->benchmark->start('route_attributes_before');
915914
$attributeResponse = $this->router->executeBeforeAttributes($this->request);
916915
$this->benchmark->stop('route_attributes_before');
@@ -1082,7 +1081,7 @@ public function storePreviousURL($uri)
10821081
return;
10831082
}
10841083
// Ignore AJAX requests
1085-
if (method_exists($this->request, 'isAJAX') && $this->request->isAJAX()) {
1084+
if ($this->request instanceof IncomingRequest && $this->request->isAJAX()) {
10861085
return;
10871086
}
10881087

tests/system/CodeIgniterTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,4 +1309,37 @@ public function testResetForWorkerMode(): void
13091309
$this->assertSame($csp->getStyleNonce(), RichRenderer::$css_nonce);
13101310
$this->assertTrue(RichRenderer::$needs_pre_render);
13111311
}
1312+
1313+
public function testGatherOutputCalledOnceWhenControllerReturnsResponse(): void
1314+
{
1315+
$this->resetServices();
1316+
1317+
$superglobals = service('superglobals');
1318+
$superglobals->setServer('argv', ['index.php', 'pages/test']);
1319+
$superglobals->setServer('argc', 2);
1320+
$superglobals->setServer('REQUEST_URI', '/pages/test');
1321+
$superglobals->setServer('SCRIPT_NAME', '/index.php');
1322+
1323+
$routes = service('routes');
1324+
$routes->add('pages/test', static fn () => service('response')->setBody('Test Body'));
1325+
1326+
$config = new App();
1327+
$codeigniter = new class ($config) extends MockCodeIgniter {
1328+
public int $gatherOutputCalls = 0;
1329+
1330+
protected function gatherOutput(?Cache $cacheConfig = null, $returned = null): void
1331+
{
1332+
$this->gatherOutputCalls++;
1333+
parent::gatherOutput($cacheConfig, $returned);
1334+
}
1335+
};
1336+
1337+
ob_start();
1338+
$codeigniter->run($routes);
1339+
ob_end_clean();
1340+
1341+
// When startController() returns a ResponseInterface (e.g. from a closure route),
1342+
// gatherOutput() must be called exactly once — not twice as in the original bug.
1343+
$this->assertSame(1, $codeigniter->gatherOutputCalls);
1344+
}
13121345
}

0 commit comments

Comments
 (0)