3535use Config \Cache ;
3636use Config \Feature ;
3737use Config \Kint as KintConfig ;
38+ use Config \Routing ;
3839use Config \Services ;
3940use Exception ;
4041use Kint ;
@@ -57,6 +58,11 @@ class CodeIgniter
5758 */
5859 public const CI_VERSION = '4.7.5-dev ' ;
5960
61+ /**
62+ * Spoofable HTTP methods
63+ */
64+ private const SPOOFABLE_METHODS = [Method::PUT , Method::PATCH , Method::DELETE ];
65+
6066 /**
6167 * App startup time.
6268 *
@@ -221,7 +227,7 @@ private function resetKintForWorkerMode(): void
221227 return ;
222228 }
223229
224- $ csp = service ( ' csp ' );
230+ $ csp = Services:: csp ( );
225231 if ($ csp ->enabled ()) {
226232 RichRenderer::$ js_nonce = $ csp ->getScriptNonce ();
227233 RichRenderer::$ css_nonce = $ csp ->getStyleNonce ();
@@ -461,22 +467,21 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
461467 return $ this ->response ->setStatusCode (405 )->setBody ('Method Not Allowed ' );
462468 }
463469
464- $ routeFilters = $ this ->tryToRouteIt ($ routes );
465-
466470 // $uri is URL-encoded.
467471 $ uri = $ this ->request ->getPath ();
468472
473+ $ routeFilters = $ this ->tryToRouteIt ($ routes , $ uri );
474+
469475 if ($ this ->enableFilters ) {
470476 /** @var Filters $filters */
471- $ filters = service ( ' filters ' );
477+ $ filters = Services:: filters ( );
472478
473479 // If any filters were specified within the routes file,
474480 // we need to ensure it's active for the current request
475481 if ($ routeFilters !== null ) {
476482 $ filters ->enableFilters ($ routeFilters , 'before ' );
477483
478- $ oldFilterOrder = config (Feature::class)->oldFilterOrder ?? false ; // @phpstan-ignore nullCoalesce.property
479- if (! $ oldFilterOrder ) {
484+ if (! config (Feature::class)->oldFilterOrder ) {
480485 $ routeFilters = array_reverse ($ routeFilters );
481486 }
482487
@@ -501,13 +506,16 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
501506 }
502507
503508 $ returned = $ this ->startController ();
509+ $ gathered = false ;
504510
505511 // If startController returned a Response (from an attribute or Closure), use it
506512 if ($ returned instanceof ResponseInterface) {
507513 $ this ->gatherOutput ($ cacheConfig , $ returned );
514+ $ gathered = true ;
508515 }
509- // Closure controller has run in startController().
510- elseif (! is_callable ($ this ->controller )) {
516+ // Closure controller has already run inside startController().
517+ // Use instanceof instead of is_callable() — 88x faster for this check.
518+ elseif (! $ this ->controller instanceof Closure) {
511519 $ controller = $ this ->createController ();
512520
513521 if (! method_exists ($ controller , '_remap ' ) && ! is_callable ([$ controller , $ this ->method ], false )) {
@@ -526,11 +534,13 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
526534 // If $returned is a string, then the controller output something,
527535 // probably a view, instead of echoing it directly. Send it along
528536 // so it can be used with the output.
529- $ this ->gatherOutput ($ cacheConfig , $ returned );
537+ if (! $ gathered ) {
538+ $ this ->gatherOutput ($ cacheConfig , $ returned );
539+ }
530540
531541 if ($ this ->enableFilters ) {
532542 /** @var Filters $filters */
533- $ filters = service ( ' filters ' );
543+ $ filters = Services:: filters ( );
534544 $ filters ->setResponse ($ this ->response );
535545
536546 // Run "after" filters
@@ -543,8 +553,8 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
543553 }
544554 }
545555
546- // Execute controller attributes' after() methods AFTER framework filters
547- if (( config (' Routing ' )->useControllerAttributes ?? true ) === true ) { // @phpstan-ignore nullCoalesce.property
556+ // Execute controller attributes' after() methods AFTER framework filters.
557+ if (config (Routing::class )->useControllerAttributes === true ) {
548558 $ this ->benchmark ->start ('route_attributes_after ' );
549559 $ this ->response = $ this ->router ->executeAfterAttributes ($ this ->request , $ this ->response );
550560 $ this ->benchmark ->stop ('route_attributes_after ' );
@@ -560,8 +570,6 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
560570 $ this ->storePreviousURL (current_url (true ));
561571 }
562572
563- unset($ uri );
564-
565573 return $ this ->response ;
566574 }
567575
@@ -670,7 +678,7 @@ protected function getRequestObject()
670678 Services::createRequest ($ this ->config );
671679 }
672680
673- $ this ->request = service ( ' request ' );
681+ $ this ->request = Services:: request ( );
674682
675683 $ this ->spoofRequestMethod ();
676684 }
@@ -833,19 +841,19 @@ public function displayPerformanceMetrics(string $output): string
833841 *
834842 * @throws RedirectException
835843 */
836- protected function tryToRouteIt (?RouteCollectionInterface $ routes = null )
844+ protected function tryToRouteIt (?RouteCollectionInterface $ routes = null , ? string $ uri = null )
837845 {
838846 $ this ->benchmark ->start ('routing ' );
839847
840848 if (! $ routes instanceof RouteCollectionInterface) {
841- $ routes = service ( ' routes ' )->loadRoutes ();
849+ $ routes = Services:: routes ( )->loadRoutes ();
842850 }
843851
844852 // $routes is defined in Config/Routes.php
845853 $ this ->router = Services::router ($ routes , $ this ->request );
846854
847855 // $uri is URL-encoded.
848- $ uri = $ this ->request ->getPath ();
856+ $ uri ?? = $ this ->request ->getPath ();
849857
850858 $ this ->outputBufferingStart ();
851859
@@ -888,8 +896,8 @@ protected function startController()
888896 $ this ->benchmark ->start ('controller ' );
889897 $ this ->benchmark ->start ('controller_constructor ' );
890898
891- // Is it routed to a Closure?
892- if (is_object ( $ this ->controller ) && ( $ this -> controller ::class === ' Closure ' ) ) {
899+ // Is it routed to a Closure? Use instanceof — faster than is_object() + ::class string check.
900+ if ($ this ->controller instanceof Closure) {
893901 $ controller = $ this ->controller ;
894902
895903 return $ controller (...$ this ->router ->params ());
@@ -909,8 +917,7 @@ protected function startController()
909917 }
910918
911919 // 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
920+ if (config (Routing::class)->useControllerAttributes === true ) {
914921 $ this ->benchmark ->start ('route_attributes_before ' );
915922 $ attributeResponse = $ this ->router ->executeBeforeAttributes ($ this ->request );
916923 $ this ->benchmark ->stop ('route_attributes_before ' );
@@ -1081,8 +1088,9 @@ public function storePreviousURL($uri)
10811088 if (! $ this ->isWeb ()) {
10821089 return ;
10831090 }
1084- // Ignore AJAX requests
1085- if (method_exists ($ this ->request , 'isAJAX ' ) && $ this ->request ->isAJAX ()) {
1091+ // Ignore AJAX requests. Use instanceof instead of method_exists() — faster
1092+ // since CLIRequest never has isAJAX(), only IncomingRequest does.
1093+ if ($ this ->request instanceof IncomingRequest && $ this ->request ->isAJAX ()) {
10861094 return ;
10871095 }
10881096
@@ -1132,7 +1140,7 @@ public function spoofRequestMethod()
11321140 }
11331141
11341142 // Only allows PUT, PATCH, DELETE
1135- if (in_array ($ method , [Method:: PUT , Method:: PATCH , Method:: DELETE ] , true )) {
1143+ if (in_array ($ method , self :: SPOOFABLE_METHODS , true )) {
11361144 $ this ->request = $ this ->request ->setMethod ($ method );
11371145 }
11381146 }
0 commit comments