refactor: simplify and optimize handleRequest method in CodeIgniter#10369
refactor: simplify and optimize handleRequest method in CodeIgniter#10369gr8man wants to merge 1 commit into
Conversation
36c2a86 to
76b6aac
Compare
michalsn
left a comment
There was a problem hiding this comment.
I agree there is one real issue here: when startController() returns a ResponseInterface, the old flow can call gatherOutput() twice. Fixing that redundant call is reasonable.
However, I don't think this refactor should be merged as-is.
- Lazy URI Resolution: The
$this->request->getPath()call has been moved insideapplyFilters(), meaning URI resolution is only performed when filters are enabled ($this->enableFilters === true), avoiding unnecessary string calculations.
I think this is false as an optimization. Routing still calls getPath() unconditionally, and IncomingRequest::getPath() is only a property read. This does not justify the refactor.
- Early Response Return: Added an early
return;insideserveResponse()ifstartController()returns aResponseInterfaceinstance (e.g. from filter attributes or closure routes). This avoids redundant calls tohandleCache()/gatherOutput()which ended output buffering and processed the response body twice in the original implementation.
This should be extracted as the only kept change.
- No Redundant Wrappers: We call the existing
tryToRouteIt()method directly insidehandleRequest()instead of creating a redundantresolveRoute()wrapper.
The supposed avoided wrapper did not exist in the original code, while this PR adds a new redundant wrapper: handleCache().
Performance Comparison (2,000 Iterations) A realistic benchmark simulating a full request lifecycle (routing to
Home::indexcontroller, rendering thewelcome_messageview, and executinginvalidcharsbefore filter plussecureheadersandtoolbarafter filters) was run to compare the two branches.
...
The refactoring reduces the average request execution time by ~6.8% while maintaining identical peak memory usage.
I ran the benchmark script locally and couldn't reproduce the ~6.8% improvement. Across 10 runs per branch, the refactored branch was actually a bit slower, both by average and by median.
That matches my earlier doubts about this benchmark. There are also problems with the script itself: as far as I can tell it doesn't actually enable the filters it claims to, and it never triggers the ResponseInterface short-circuit, which is the one code path this PR really changes. The per-iteration reset also doesn't match what the FrankenPHP worker loop does between requests, so it's not measuring the scenario it claims to.
fddc099 to
6f9842e
Compare
6f9842e to
efcbfae
Compare
michalsn
left a comment
There was a problem hiding this comment.
Please update the PR description so it's not misleading relative to the code changes.
| /** | ||
| * Cache for Routing configuration | ||
| */ | ||
| private ?Routing $routingConfig = null; | ||
|
|
||
| /** | ||
| * Cache for Feature configuration | ||
| */ | ||
| private ?Feature $featureConfig = null; | ||
|
|
||
| /** | ||
| * Cache for Cache configuration | ||
| */ | ||
| private ?Cache $cacheConfig = null; | ||
|
|
There was a problem hiding this comment.
Please remove. It gives us nothing here and only causes problems for worker mode.
| $csp = service('csp'); | ||
| $csp = Services::csp(); |
There was a problem hiding this comment.
Please revert everywhere. We prefer the use of service() as it's optimized.
| protected function tryToRouteIt(?RouteCollectionInterface $routes = null) | ||
| protected function tryToRouteIt(?RouteCollectionInterface $routes = null, ?string $uri = null) |
There was a problem hiding this comment.
This is a BC break, without real gain. This is just one getPath() call. Please revert.
| // Is it routed to a Closure? | ||
| if (is_object($this->controller) && ($this->controller::class === 'Closure')) { | ||
| // Is it routed to a Closure? Use instanceof — faster than is_object() + ::class string check. | ||
| if ($this->controller instanceof Closure) { |
There was a problem hiding this comment.
It's likely true, but please remove the added part of the comment. I see no reason why we should have this in the code.
| if (method_exists($this->request, 'isAJAX') && $this->request->isAJAX()) { | ||
| // Ignore AJAX requests. Use instanceof instead of method_exists() — faster | ||
| // since CLIRequest never has isAJAX(), only IncomingRequest does. | ||
| if ($this->request instanceof IncomingRequest && $this->request->isAJAX()) { |
There was a problem hiding this comment.
This narrows the previous behavior from "any request with isAJAX()" to only IncomingRequest.
I think I'm okay with this, but I would like to hear what others think.
If this stays, please remove the added comment, as it's not relevant to include.
| if (in_array($method, [Method::PUT, Method::PATCH, Method::DELETE], true)) { | ||
| if (in_array($method, self::SPOOFABLE_METHODS, true)) { |
There was a problem hiding this comment.
I'm not sure if this gives us any benefits here. This constant will be used only once.
Description
This PR refactors the
handleRequest()method insystem/CodeIgniter.php.Originally,
handleRequest()was a 109-line method with high complexity, managing routing, filters, controller dispatching, output buffering, and caching within a single block.This change simplifies the method by delegating these concerns to three smaller, focused helper methods:
applyFilters(string $position, $routeFilters = null): ?ResponseInterface— Runs global and route-specific filters.serveResponse(Cache $cacheConfig, $returned): void— Creates/runs the controller and gathers the output.handleCache(Cache $cacheConfig, $returned): void— A wrapper around output gathering and caching.Optimizations Implemented
$this->request->getPath()call has been moved insideapplyFilters(), meaning URI resolution is only performed when filters are enabled ($this->enableFilters === true), avoiding unnecessary string calculations.return;insideserveResponse()ifstartController()returns aResponseInterfaceinstance (e.g. from filter attributes or closure routes). This avoids redundant calls tohandleCache()/gatherOutput()which ended output buffering and processed the response body twice in the original implementation.tryToRouteIt()method directly insidehandleRequest()instead of creating a redundantresolveRoute()wrapper.Performance Comparison (2,000 Iterations)
A realistic benchmark simulating a full request lifecycle (routing to
Home::indexcontroller, rendering thewelcome_messageview, and executinginvalidcharsbefore filter plussecureheadersandtoolbarafter filters) was run to compare the two branches.Original
developbranch:Refactored branch (
refactor/codeigniter-handle-request):The refactoring reduces the average request execution time by ~6.8% while maintaining identical peak memory usage.
Benchmark Script Code
Checklist: