• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 22274186362

22 Feb 2026 09:06AM UTC coverage: 86.561% (+0.6%) from 85.977%
22274186362

Pull #9962

github

web-flow
Merge 96a632b74 into cd3013ba3
Pull Request #9962: feat: Chunk array method in models

2 of 2 new or added lines in 1 file covered. (100.0%)

66 existing lines in 5 files now uncovered.

22254 of 25709 relevant lines covered (86.56%)

209.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.12
/system/CodeIgniter.php
1
<?php
2

3
/**
4
 * This file is part of CodeIgniter 4 framework.
5
 *
6
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11

12
namespace CodeIgniter;
13

14
use Closure;
15
use CodeIgniter\Cache\ResponseCache;
16
use CodeIgniter\Debug\Timer;
17
use CodeIgniter\Events\Events;
18
use CodeIgniter\Exceptions\LogicException;
19
use CodeIgniter\Exceptions\PageNotFoundException;
20
use CodeIgniter\Filters\Filters;
21
use CodeIgniter\HTTP\CLIRequest;
22
use CodeIgniter\HTTP\Exceptions\RedirectException;
23
use CodeIgniter\HTTP\IncomingRequest;
24
use CodeIgniter\HTTP\Method;
25
use CodeIgniter\HTTP\NonBufferedResponseInterface;
26
use CodeIgniter\HTTP\RedirectResponse;
27
use CodeIgniter\HTTP\Request;
28
use CodeIgniter\HTTP\RequestInterface;
29
use CodeIgniter\HTTP\ResponsableInterface;
30
use CodeIgniter\HTTP\ResponseInterface;
31
use CodeIgniter\HTTP\URI;
32
use CodeIgniter\Router\RouteCollectionInterface;
33
use CodeIgniter\Router\Router;
34
use Config\App;
35
use Config\Cache;
36
use Config\Feature;
37
use Config\Services;
38
use Locale;
39
use Throwable;
40

41
/**
42
 * This class is the core of the framework, and will analyse the
43
 * request, route it to a controller, and send back the response.
44
 * Of course, there are variations to that flow, but this is the brains.
45
 *
46
 * @see \CodeIgniter\CodeIgniterTest
47
 */
48
class CodeIgniter
49
{
50
    /**
51
     * The current version of CodeIgniter Framework
52
     */
53
    public const CI_VERSION = '4.8.0-dev';
54

55
    /**
56
     * App startup time.
57
     *
58
     * @var float|null
59
     */
60
    protected $startTime;
61

62
    /**
63
     * Total app execution time
64
     *
65
     * @var float
66
     */
67
    protected $totalTime;
68

69
    /**
70
     * Main application configuration
71
     *
72
     * @var App
73
     */
74
    protected $config;
75

76
    /**
77
     * Timer instance.
78
     *
79
     * @var Timer
80
     */
81
    protected $benchmark;
82

83
    /**
84
     * Current request.
85
     *
86
     * @var CLIRequest|IncomingRequest|null
87
     */
88
    protected $request;
89

90
    /**
91
     * Current response.
92
     *
93
     * @var ResponseInterface|null
94
     */
95
    protected $response;
96

97
    /**
98
     * Router to use.
99
     *
100
     * @var Router|null
101
     */
102
    protected $router;
103

104
    /**
105
     * Controller to use.
106
     *
107
     * @var (Closure(mixed...): ResponseInterface|string)|string|null
108
     */
109
    protected $controller;
110

111
    /**
112
     * Controller method to invoke.
113
     *
114
     * @var string|null
115
     */
116
    protected $method;
117

118
    /**
119
     * Output handler to use.
120
     *
121
     * @var string|null
122
     */
123
    protected $output;
124

125
    /**
126
     * Context
127
     *  web:     Invoked by HTTP request
128
     *  php-cli: Invoked by CLI via `php public/index.php`
129
     *
130
     * @var 'php-cli'|'web'|null
131
     */
132
    protected ?string $context = null;
133

134
    /**
135
     * Whether to enable Control Filters.
136
     */
137
    protected bool $enableFilters = true;
138

139
    /**
140
     * Application output buffering level
141
     */
142
    protected int $bufferLevel;
143

144
    /**
145
     * Web Page Caching
146
     */
147
    protected ResponseCache $pageCache;
148

149
    /**
150
     * Constructor.
151
     */
152
    public function __construct(App $config)
153
    {
154
        $this->startTime = microtime(true);
7,211✔
155
        $this->config    = $config;
7,211✔
156

157
        $this->pageCache = Services::responsecache();
7,211✔
158
    }
159

160
    /**
161
     * Handles some basic app and environment setup.
162
     *
163
     * @return void
164
     */
165
    public function initialize()
166
    {
167
        // Set default locale on the server
168
        Locale::setDefault($this->config->defaultLocale ?? 'en');
7,211✔
169

170
        // Set default timezone on the server
171
        date_default_timezone_set($this->config->appTimezone ?? 'UTC');
7,211✔
172
    }
173

174
    /**
175
     * Reset request-specific state for worker mode.
176
     * Clears all request/response data to prepare for the next request.
177
     */
178
    public function resetForWorkerMode(): void
179
    {
180
        $this->request    = null;
1✔
181
        $this->response   = null;
1✔
182
        $this->router     = null;
1✔
183
        $this->controller = null;
1✔
184
        $this->method     = null;
1✔
185
        $this->output     = null;
1✔
186

187
        // Reset timing
188
        $this->startTime = null;
1✔
189
        $this->totalTime = 0;
1✔
190
    }
191

192
    /**
193
     * Launch the application!
194
     *
195
     * This is "the loop" if you will. The main entry point into the script
196
     * that gets the required class instances, fires off the filters,
197
     * tries to route the response, loads the controller and generally
198
     * makes all the pieces work together.
199
     *
200
     * @param bool $returnResponse Used for testing purposes only.
201
     *
202
     * @return ResponseInterface|null
203
     */
204
    public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false)
205
    {
206
        if ($this->context === null) {
99✔
UNCOV
207
            throw new LogicException(
×
UNCOV
208
                'Context must be set before run() is called. If you are upgrading from 4.1.x, '
×
UNCOV
209
                . 'you need to merge `public/index.php` and `spark` file from `vendor/codeigniter4/framework`.',
×
UNCOV
210
            );
×
211
        }
212

213
        $this->pageCache->setTtl(0);
99✔
214
        $this->bufferLevel = ob_get_level();
99✔
215

216
        $this->startBenchmark();
99✔
217

218
        $this->getRequestObject();
99✔
219
        $this->getResponseObject();
99✔
220

221
        Events::trigger('pre_system');
99✔
222

223
        $this->benchmark->stop('bootstrap');
99✔
224

225
        $this->benchmark->start('required_before_filters');
99✔
226
        // Start up the filters
227
        $filters = Services::filters();
99✔
228
        // Run required before filters
229
        $possibleResponse = $this->runRequiredBeforeFilters($filters);
99✔
230

231
        // If a ResponseInterface instance is returned then send it back to the client and stop
232
        if ($possibleResponse instanceof ResponseInterface) {
99✔
233
            $this->response = $possibleResponse;
4✔
234
        } else {
235
            try {
236
                $this->response = $this->handleRequest($routes);
98✔
237
            } catch (ResponsableInterface $e) {
18✔
238
                $this->outputBufferingEnd();
6✔
239

240
                $this->response = $e->getResponse();
6✔
241
            } catch (PageNotFoundException $e) {
12✔
242
                $this->response = $this->display404errors($e);
12✔
UNCOV
243
            } catch (Throwable $e) {
×
244
                $this->outputBufferingEnd();
×
245

UNCOV
246
                throw $e;
×
247
            }
248
        }
249

250
        $this->runRequiredAfterFilters($filters);
91✔
251

252
        // Is there a post-system event?
253
        Events::trigger('post_system');
91✔
254

255
        if ($returnResponse) {
91✔
256
            return $this->response;
36✔
257
        }
258

259
        $this->sendResponse();
55✔
260

261
        return null;
55✔
262
    }
263

264
    private function runRequiredBeforeFilters(Filters $filters): ?ResponseInterface
265
    {
266
        $possibleResponse = $filters->runRequired('before');
99✔
267
        $this->benchmark->stop('required_before_filters');
99✔
268

269
        // If a ResponseInterface instance is returned then send it back to the client and stop
270
        if ($possibleResponse instanceof ResponseInterface) {
99✔
271
            return $possibleResponse;
4✔
272
        }
273

274
        return null;
98✔
275
    }
276

277
    private function runRequiredAfterFilters(Filters $filters): void
278
    {
279
        $filters->setResponse($this->response);
91✔
280

281
        $this->benchmark->start('required_after_filters');
91✔
282
        $response = $filters->runRequired('after');
91✔
283
        $this->benchmark->stop('required_after_filters');
91✔
284

285
        if ($response instanceof ResponseInterface) {
91✔
286
            $this->response = $response;
91✔
287
        }
288
    }
289

290
    /**
291
     * Invoked via php-cli command?
292
     */
293
    private function isPhpCli(): bool
294
    {
295
        return $this->context === 'php-cli';
61✔
296
    }
297

298
    /**
299
     * Web access?
300
     */
301
    private function isWeb(): bool
302
    {
303
        return $this->context === 'web';
99✔
304
    }
305

306
    /**
307
     * Disables Controller Filters.
308
     */
309
    public function disableFilters(): void
310
    {
311
        $this->enableFilters = false;
1✔
312
    }
313

314
    /**
315
     * Handles the main request logic and fires the controller.
316
     *
317
     * @return ResponseInterface
318
     *
319
     * @throws PageNotFoundException
320
     * @throws RedirectException
321
     */
322
    protected function handleRequest(?RouteCollectionInterface $routes, ?Cache $cacheConfig = null)
323
    {
324
        if (func_num_args() > 1) {
98✔
325
            // @todo v4.8.0: Remove this check and the $cacheConfig parameter from the method signature.
UNCOV
326
            @trigger_error(sprintf('Since v4.8.0, the $cacheConfig parameter of %s is deprecated and no longer used.', __METHOD__), E_USER_DEPRECATED);
×
327
        }
328

329
        if ($this->request instanceof IncomingRequest && $this->request->getMethod() === 'CLI') {
98✔
330
            return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
1✔
331
        }
332

333
        $routeFilters = $this->tryToRouteIt($routes);
97✔
334

335
        // $uri is URL-encoded.
336
        $uri = $this->request->getPath();
80✔
337

338
        if ($this->enableFilters) {
80✔
339
            /** @var Filters $filters */
340
            $filters = service('filters');
79✔
341

342
            // If any filters were specified within the routes file,
343
            // we need to ensure it's active for the current request
344
            if ($routeFilters !== null) {
79✔
345
                $filters->enableFilters($routeFilters, 'before');
79✔
346

347
                $oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
79✔
348
                if (! $oldFilterOrder) {
79✔
349
                    $routeFilters = array_reverse($routeFilters);
79✔
350
                }
351

352
                $filters->enableFilters($routeFilters, 'after');
79✔
353
            }
354

355
            // Run "before" filters
356
            $this->benchmark->start('before_filters');
79✔
357
            $possibleResponse = $filters->run($uri, 'before');
79✔
358
            $this->benchmark->stop('before_filters');
79✔
359

360
            // If a ResponseInterface instance is returned then send it back to the client and stop
361
            if ($possibleResponse instanceof ResponseInterface) {
79✔
362
                $this->outputBufferingEnd();
1✔
363

364
                return $possibleResponse;
1✔
365
            }
366

367
            if ($possibleResponse instanceof IncomingRequest || $possibleResponse instanceof CLIRequest) {
78✔
368
                $this->request = $possibleResponse;
78✔
369
            }
370
        }
371

372
        $returned = $this->startController();
79✔
373

374
        // If startController returned a Response (from an attribute or Closure), use it
375
        if ($returned instanceof ResponseInterface) {
78✔
376
            $this->gatherOutput($returned);
8✔
377
        }
378
        // Closure controller has run in startController().
379
        elseif (! is_callable($this->controller)) {
71✔
380
            $controller = $this->createController();
49✔
381

382
            if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
49✔
UNCOV
383
                throw PageNotFoundException::forMethodNotFound($this->method);
×
384
            }
385

386
            // Is there a "post_controller_constructor" event?
387
            Events::trigger('post_controller_constructor');
49✔
388

389
            $returned = $this->runController($controller);
49✔
390
        } else {
391
            $this->benchmark->stop('controller_constructor');
22✔
392
            $this->benchmark->stop('controller');
22✔
393
        }
394

395
        // If $returned is a string, then the controller output something,
396
        // probably a view, instead of echoing it directly. Send it along
397
        // so it can be used with the output.
398
        $this->gatherOutput($returned);
78✔
399

400
        if ($this->enableFilters) {
78✔
401
            /** @var Filters $filters */
402
            $filters = service('filters');
77✔
403
            $filters->setResponse($this->response);
77✔
404

405
            // Run "after" filters
406
            $this->benchmark->start('after_filters');
77✔
407
            $response = $filters->run($uri, 'after');
77✔
408
            $this->benchmark->stop('after_filters');
77✔
409

410
            if ($response instanceof ResponseInterface) {
77✔
411
                $this->response = $response;
77✔
412
            }
413
        }
414

415
        // Execute controller attributes' after() methods AFTER framework filters
416
        if ((config('Routing')->useControllerAttributes ?? true) === true) {
78✔
417
            $this->benchmark->start('route_attributes_after');
77✔
418
            $this->response = $this->router->executeAfterAttributes($this->request, $this->response);
77✔
419
            $this->benchmark->stop('route_attributes_after');
77✔
420
        }
421

422
        // Skip unnecessary processing for special Responses.
423
        if (
424
            ! $this->response instanceof NonBufferedResponseInterface
78✔
425
            && ! $this->response instanceof RedirectResponse
78✔
426
        ) {
427
            // Save our current URI as the previous URI in the session
428
            // for safer, more accurate use with `previous_url()` helper function.
429
            $this->storePreviousURL(current_url(true));
76✔
430
        }
431

432
        unset($uri);
78✔
433

434
        return $this->response;
78✔
435
    }
436

437
    /**
438
     * Start the Benchmark
439
     *
440
     * The timer is used to display total script execution both in the
441
     * debug toolbar, and potentially on the displayed page.
442
     *
443
     * @return void
444
     */
445
    protected function startBenchmark()
446
    {
447
        if ($this->startTime === null) {
99✔
UNCOV
448
            $this->startTime = microtime(true);
×
449
        }
450

451
        $this->benchmark = Services::timer();
99✔
452
        $this->benchmark->start('total_execution', $this->startTime);
99✔
453
        $this->benchmark->start('bootstrap');
99✔
454
    }
455

456
    /**
457
     * Sets a Request object to be used for this request.
458
     * Used when running certain tests.
459
     *
460
     * @param CLIRequest|IncomingRequest $request
461
     *
462
     * @return $this
463
     *
464
     * @internal Used for testing purposes only.
465
     * @testTag
466
     */
467
    public function setRequest($request)
468
    {
469
        $this->request = $request;
38✔
470

471
        return $this;
38✔
472
    }
473

474
    /**
475
     * Get our Request object, (either IncomingRequest or CLIRequest).
476
     *
477
     * @return void
478
     */
479
    protected function getRequestObject()
480
    {
481
        if ($this->request instanceof Request) {
99✔
482
            $this->spoofRequestMethod();
40✔
483

484
            return;
40✔
485
        }
486

487
        if ($this->isPhpCli()) {
61✔
UNCOV
488
            Services::createRequest($this->config, true);
×
489
        } else {
490
            Services::createRequest($this->config);
61✔
491
        }
492

493
        $this->request = service('request');
61✔
494

495
        $this->spoofRequestMethod();
61✔
496
    }
497

498
    /**
499
     * Get our Response object, and set some default values, including
500
     * the HTTP protocol version and a default successful response.
501
     *
502
     * @return void
503
     */
504
    protected function getResponseObject()
505
    {
506
        $this->response = Services::response($this->config);
99✔
507

508
        if ($this->isWeb()) {
99✔
509
            $this->response->setProtocolVersion($this->request->getProtocolVersion());
99✔
510
        }
511

512
        // Assume success until proven otherwise.
513
        $this->response->setStatusCode(200);
99✔
514
    }
515

516
    /**
517
     * Returns an array with our basic performance stats collected.
518
     */
519
    public function getPerformanceStats(): array
520
    {
521
        // After filter debug toolbar requires 'total_execution'.
UNCOV
522
        $this->totalTime = $this->benchmark->getElapsedTime('total_execution');
×
523

UNCOV
524
        return [
×
UNCOV
525
            'startTime' => $this->startTime,
×
UNCOV
526
            'totalTime' => $this->totalTime,
×
UNCOV
527
        ];
×
528
    }
529

530
    /**
531
     * Try to Route It - As it sounds like, works with the router to
532
     * match a route against the current URI. If the route is a
533
     * "redirect route", will also handle the redirect.
534
     *
535
     * @param RouteCollectionInterface|null $routes A collection interface to use in place
536
     *                                              of the config file.
537
     *
538
     * @return list<string>|string|null Route filters, that is, the filters specified in the routes file
539
     *
540
     * @throws RedirectException
541
     */
542
    protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
543
    {
544
        $this->benchmark->start('routing');
97✔
545

546
        if (! $routes instanceof RouteCollectionInterface) {
97✔
547
            $routes = service('routes')->loadRoutes();
37✔
548
        }
549

550
        // $routes is defined in Config/Routes.php
551
        $this->router = Services::router($routes, $this->request);
97✔
552

553
        // $uri is URL-encoded.
554
        $uri = $this->request->getPath();
97✔
555

556
        $this->outputBufferingStart();
97✔
557

558
        $this->controller = $this->router->handle($uri);
97✔
559
        $this->method     = $this->router->methodName();
80✔
560

561
        // If a {locale} segment was matched in the final route,
562
        // then we need to set the correct locale on our Request.
563
        if ($this->router->hasLocale()) {
80✔
UNCOV
564
            $this->request->setLocale($this->router->getLocale());
×
565
        }
566

567
        $this->benchmark->stop('routing');
80✔
568

569
        return $this->router->getFilters();
80✔
570
    }
571

572
    /**
573
     * Now that everything has been setup, this method attempts to run the
574
     * controller method and make the script go. If it's not able to, will
575
     * show the appropriate Page Not Found error.
576
     *
577
     * @return ResponseInterface|string|null
578
     */
579
    protected function startController()
580
    {
581
        $this->benchmark->start('controller');
80✔
582
        $this->benchmark->start('controller_constructor');
80✔
583

584
        // Is it routed to a Closure?
585
        if (is_object($this->controller) && ($this->controller::class === 'Closure')) {
80✔
586
            $controller = $this->controller;
29✔
587

588
            return $controller(...$this->router->params());
29✔
589
        }
590

591
        // No controller specified - we don't know what to do now.
592
        if (! isset($this->controller)) {
51✔
UNCOV
593
            throw PageNotFoundException::forEmptyController();
×
594
        }
595

596
        // Try to autoload the class
597
        if (
598
            ! class_exists($this->controller, true)
51✔
599
            || ($this->method[0] === '_' && $this->method !== '__invoke')
51✔
600
        ) {
UNCOV
601
            throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
×
602
        }
603

604
        // Execute route attributes' before() methods
605
        // This runs after routing/validation but BEFORE expensive controller instantiation
606
        if ((config('Routing')->useControllerAttributes ?? true) === true) {
51✔
607
            $this->benchmark->start('route_attributes_before');
50✔
608
            $attributeResponse = $this->router->executeBeforeAttributes($this->request);
50✔
609
            $this->benchmark->stop('route_attributes_before');
49✔
610

611
            // If attribute returns a Response, short-circuit
612
            if ($attributeResponse instanceof ResponseInterface) {
49✔
613
                $this->benchmark->stop('controller_constructor');
1✔
614
                $this->benchmark->stop('controller');
1✔
615

616
                return $attributeResponse;
1✔
617
            }
618

619
            // If attribute returns a modified Request, use it
620
            if ($attributeResponse instanceof RequestInterface) {
49✔
621
                $this->request = $attributeResponse;
49✔
622
            }
623
        }
624

625
        return null;
50✔
626
    }
627

628
    /**
629
     * Instantiates the controller class.
630
     *
631
     * @return Controller
632
     */
633
    protected function createController()
634
    {
635
        assert(is_string($this->controller));
636

637
        $class = new $this->controller();
52✔
638
        $class->initController($this->request, $this->response, Services::logger());
52✔
639

640
        $this->benchmark->stop('controller_constructor');
52✔
641

642
        return $class;
52✔
643
    }
644

645
    /**
646
     * Runs the controller, allowing for _remap methods to function.
647
     *
648
     * CI4 supports three types of requests:
649
     *  1. Web: URI segments become parameters, sent to Controllers via Routes,
650
     *      output controlled by Headers to browser
651
     *  2. PHP CLI: accessed by CLI via php public/index.php, arguments become URI segments,
652
     *      sent to Controllers via Routes, output varies
653
     *
654
     * @param Controller $class
655
     *
656
     * @return false|ResponseInterface|string|void
657
     */
658
    protected function runController($class)
659
    {
660
        // This is a Web request or PHP CLI request
661
        $params = $this->router->params();
49✔
662

663
        // The controller method param types may not be string.
664
        // So cannot set `declare(strict_types=1)` in this file.
665
        $output = method_exists($class, '_remap')
49✔
UNCOV
666
            ? $class->_remap($this->method, ...$params)
×
667
            : $class->{$this->method}(...$params);
49✔
668

669
        $this->benchmark->stop('controller');
49✔
670

671
        return $output;
49✔
672
    }
673

674
    /**
675
     * Displays a 404 Page Not Found error. If set, will try to
676
     * call the 404Override controller/method that was set in routing config.
677
     *
678
     * @return ResponseInterface|void
679
     */
680
    protected function display404errors(PageNotFoundException $e)
681
    {
682
        $this->response->setStatusCode($e->getCode());
12✔
683

684
        // Is there a 404 Override available?
685
        $override = $this->router->get404Override();
12✔
686

687
        if ($override !== null) {
12✔
688
            $returned = null;
4✔
689

690
            if ($override instanceof Closure) {
4✔
691
                echo $override($e->getMessage());
1✔
692
            } elseif (is_array($override)) {
3✔
693
                $this->benchmark->start('controller');
3✔
694
                $this->benchmark->start('controller_constructor');
3✔
695

696
                $this->controller = $override[0];
3✔
697
                $this->method     = $override[1];
3✔
698

699
                $controller = $this->createController();
3✔
700

701
                $returned = $controller->{$this->method}($e->getMessage());
3✔
702

703
                $this->benchmark->stop('controller');
3✔
704
            }
705

706
            unset($override);
4✔
707

708
            $this->gatherOutput($returned);
4✔
709

710
            return $this->response;
4✔
711
        }
712

713
        $this->outputBufferingEnd();
8✔
714

715
        // Throws new PageNotFoundException and remove exception message on production.
716
        throw PageNotFoundException::forPageNotFound(
8✔
717
            (ENVIRONMENT !== 'production' || ! $this->isWeb()) ? $e->getMessage() : null,
8✔
718
        );
8✔
719
    }
720

721
    /**
722
     * Gathers the script output from the buffer, replaces some execution
723
     * time tag in the output and displays the debug toolbar, if required.
724
     *
725
     * @param ResponseInterface|string|null $returned
726
     *
727
     * @return void
728
     */
729
    protected function gatherOutput($returned = null)
730
    {
731
        $this->output = $this->outputBufferingEnd();
82✔
732

733
        if ($returned instanceof NonBufferedResponseInterface) {
82✔
734
            $this->response = $returned;
1✔
735

736
            return;
1✔
737
        }
738
        // If the controller returned a response object,
739
        // we need to grab the body from it so it can
740
        // be added to anything else that might have been
741
        // echoed already.
742
        // We also need to save the instance locally
743
        // so that any status code changes, etc, take place.
744
        if ($returned instanceof ResponseInterface) {
81✔
745
            $this->response = $returned;
28✔
746
            $returned       = $returned->getBody();
28✔
747
        }
748

749
        if (is_string($returned)) {
81✔
750
            $this->output .= $returned;
73✔
751
        }
752

753
        $this->response->setBody($this->output);
81✔
754
    }
755

756
    /**
757
     * If we have a session object to use, store the current URI
758
     * as the previous URI. This is called just prior to sending the
759
     * response to the client, and will make it available next request.
760
     *
761
     * This helps provider safer, more reliable previous_url() detection.
762
     *
763
     * @param string|URI $uri
764
     *
765
     * @return void
766
     */
767
    public function storePreviousURL($uri)
768
    {
769
        // Ignore CLI requests
770
        if (! $this->isWeb()) {
76✔
UNCOV
771
            return;
×
772
        }
773
        // Ignore AJAX requests
774
        if (method_exists($this->request, 'isAJAX') && $this->request->isAJAX()) {
76✔
775
            return;
×
776
        }
777

778
        // Ignore unroutable responses
779
        if ($this->response instanceof NonBufferedResponseInterface || $this->response instanceof RedirectResponse) {
76✔
780
            return;
×
781
        }
782

783
        // Ignore non-HTML responses
784
        if (! str_contains($this->response->getHeaderLine('Content-Type'), 'text/html')) {
76✔
785
            return;
12✔
786
        }
787

788
        // This is mainly needed during testing...
789
        if (is_string($uri)) {
64✔
UNCOV
790
            $uri = new URI($uri);
×
791
        }
792

793
        if (isset($_SESSION)) {
64✔
794
            session()->set('_ci_previous_url', URI::createURIString(
64✔
795
                $uri->getScheme(),
64✔
796
                $uri->getAuthority(),
64✔
797
                $uri->getPath(),
64✔
798
                $uri->getQuery(),
64✔
799
                $uri->getFragment(),
64✔
800
            ));
64✔
801
        }
802
    }
803

804
    /**
805
     * Modifies the Request Object to use a different method if a POST
806
     * variable called _method is found.
807
     *
808
     * @return void
809
     */
810
    public function spoofRequestMethod()
811
    {
812
        // Only works with POSTED forms
813
        if ($this->request->getMethod() !== Method::POST) {
99✔
814
            return;
84✔
815
        }
816

817
        $method = $this->request->getPost('_method');
16✔
818

819
        if ($method === null) {
16✔
820
            return;
14✔
821
        }
822

823
        // Only allows PUT, PATCH, DELETE
824
        if (in_array($method, [Method::PUT, Method::PATCH, Method::DELETE], true)) {
2✔
825
            $this->request = $this->request->setMethod($method);
1✔
826
        }
827
    }
828

829
    /**
830
     * Sends the output of this request back to the client.
831
     * This is what they've been waiting for!
832
     *
833
     * @return void
834
     */
835
    protected function sendResponse()
836
    {
837
        $this->response->send();
55✔
838
    }
839

840
    /**
841
     * Sets the app context.
842
     *
843
     * @param 'php-cli'|'web' $context
844
     *
845
     * @return $this
846
     */
847
    public function setContext(string $context)
848
    {
849
        $this->context = $context;
39✔
850

851
        return $this;
39✔
852
    }
853

854
    protected function outputBufferingStart(): void
855
    {
856
        $this->bufferLevel = ob_get_level();
97✔
857
        ob_start();
97✔
858
    }
859

860
    protected function outputBufferingEnd(): string
861
    {
862
        $buffer = '';
97✔
863

864
        while (ob_get_level() > $this->bufferLevel) {
97✔
865
            $buffer .= ob_get_contents();
97✔
866
            ob_end_clean();
97✔
867
        }
868

869
        return $buffer;
97✔
870
    }
871
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc