4
* This file is part of CodeIgniter 4 framework.
6
* (c) CodeIgniter Foundation <admin@codeigniter.com>
8
* For the full copyright and license information, please view
9
* the LICENSE file that was distributed with this source code.
15
use CodeIgniter\Cache\ResponseCache;
16
use CodeIgniter\Debug\Timer;
17
use CodeIgniter\Events\Events;
18
use CodeIgniter\Exceptions\FrameworkException;
19
use CodeIgniter\Exceptions\PageNotFoundException;
20
use CodeIgniter\Filters\Filters;
21
use CodeIgniter\HTTP\CLIRequest;
22
use CodeIgniter\HTTP\DownloadResponse;
23
use CodeIgniter\HTTP\Exceptions\RedirectException;
24
use CodeIgniter\HTTP\IncomingRequest;
25
use CodeIgniter\HTTP\Method;
26
use CodeIgniter\HTTP\RedirectResponse;
27
use CodeIgniter\HTTP\Request;
28
use CodeIgniter\HTTP\ResponsableInterface;
29
use CodeIgniter\HTTP\ResponseInterface;
30
use CodeIgniter\HTTP\URI;
31
use CodeIgniter\Router\Exceptions\RedirectException as DeprecatedRedirectException;
32
use CodeIgniter\Router\RouteCollectionInterface;
33
use CodeIgniter\Router\Router;
37
use Config\Kint as KintConfig;
41
use Kint\Renderer\CliRenderer;
42
use Kint\Renderer\RichRenderer;
48
* This class is the core of the framework, and will analyse the
49
* request, route it to a controller, and send back the response.
50
* Of course, there are variations to that flow, but this is the brains.
52
* @see \CodeIgniter\CodeIgniterTest
57
* The current version of CodeIgniter Framework
59
public const CI_VERSION = '4.5.5';
69
* Total app execution time
76
* Main application configuration
92
* @var CLIRequest|IncomingRequest|null
99
* @var ResponseInterface
113
* @var (Closure(mixed...): ResponseInterface|string)|string|null
115
protected $controller;
118
* Controller method to invoke.
125
* Output handler to use.
132
* Cache expiration time
136
* @deprecated 4.4.0 Moved to ResponseCache::$ttl. No longer used.
138
protected static $cacheTTL = 0;
142
* web: Invoked by HTTP request
143
* php-cli: Invoked by CLI via `php public/index.php`
145
* @phpstan-var 'php-cli'|'web'
147
protected ?string $context = null;
150
* Whether to enable Control Filters.
152
protected bool $enableFilters = true;
155
* Whether to return Response object or send response.
157
* @deprecated 4.4.0 No longer used.
159
protected bool $returnResponse = false;
162
* Application output buffering level
164
protected int $bufferLevel;
169
protected ResponseCache $pageCache;
174
public function __construct(App $config)
176
$this->startTime = microtime(true);
177
$this->config = $config;
179
$this->pageCache = Services::responsecache();
183
* Handles some basic app and environment setup.
187
public function initialize()
189
// Set default locale on the server
190
Locale::setDefault($this->config->defaultLocale ?? 'en');
192
// Set default timezone on the server
193
date_default_timezone_set($this->config->appTimezone ?? 'UTC');
197
* Checks system for missing required PHP extensions.
201
* @throws FrameworkException
203
* @codeCoverageIgnore
205
* @deprecated 4.5.0 Moved to system/bootstrap.php.
207
protected function resolvePlatformExtensions()
209
$requiredExtensions = [
215
$missingExtensions = [];
217
foreach ($requiredExtensions as $extension) {
218
if (! extension_loaded($extension)) {
219
$missingExtensions[] = $extension;
223
if ($missingExtensions !== []) {
224
throw FrameworkException::forMissingExtension(implode(', ', $missingExtensions));
233
* @deprecated 4.5.0 Moved to Autoloader.
235
protected function initializeKint()
238
$this->autoloadKint();
239
$this->configureKint();
240
} elseif (class_exists(Kint::class)) {
241
// In case that Kint is already loaded via Composer.
242
Kint::$enabled_mode = false;
243
// @codeCoverageIgnore
250
* @deprecated 4.5.0 Moved to Autoloader.
252
private function autoloadKint(): void
254
// If we have KINT_DIR it means it's already loaded via composer
255
if (! defined('KINT_DIR')) {
256
spl_autoload_register(function ($class): void {
257
$class = explode('\\', $class);
259
if (array_shift($class) !== 'Kint') {
263
$file = SYSTEMPATH . 'ThirdParty/Kint/' . implode('/', $class) . '.php';
265
if (is_file($file)) {
270
require_once SYSTEMPATH . 'ThirdParty/Kint/init.php';
275
* @deprecated 4.5.0 Moved to Autoloader.
277
private function configureKint(): void
279
$config = new KintConfig();
281
Kint::$depth_limit = $config->maxDepth;
282
Kint::$display_called_from = $config->displayCalledFrom;
283
Kint::$expanded = $config->expanded;
285
if (isset($config->plugins) && is_array($config->plugins)) {
286
Kint::$plugins = $config->plugins;
289
$csp = Services::csp();
290
if ($csp->enabled()) {
291
RichRenderer::$js_nonce = $csp->getScriptNonce();
292
RichRenderer::$css_nonce = $csp->getStyleNonce();
295
RichRenderer::$theme = $config->richTheme;
296
RichRenderer::$folder = $config->richFolder;
297
RichRenderer::$sort = $config->richSort;
298
if (isset($config->richObjectPlugins) && is_array($config->richObjectPlugins)) {
299
RichRenderer::$value_plugins = $config->richObjectPlugins;
301
if (isset($config->richTabPlugins) && is_array($config->richTabPlugins)) {
302
RichRenderer::$tab_plugins = $config->richTabPlugins;
305
CliRenderer::$cli_colors = $config->cliColors;
306
CliRenderer::$force_utf8 = $config->cliForceUTF8;
307
CliRenderer::$detect_width = $config->cliDetectWidth;
308
CliRenderer::$min_terminal_width = $config->cliMinWidth;
312
* Launch the application!
314
* This is "the loop" if you will. The main entry point into the script
315
* that gets the required class instances, fires off the filters,
316
* tries to route the response, loads the controller and generally
317
* makes all the pieces work together.
319
* @param bool $returnResponse Used for testing purposes only.
321
* @return ResponseInterface|void
323
public function run(?RouteCollectionInterface $routes = null, bool $returnResponse = false)
325
if ($this->context === null) {
326
throw new LogicException(
327
'Context must be set before run() is called. If you are upgrading from 4.1.x, '
328
. 'you need to merge `public/index.php` and `spark` file from `vendor/codeigniter4/framework`.'
332
$this->pageCache->setTtl(0);
333
$this->bufferLevel = ob_get_level();
335
$this->startBenchmark();
337
$this->getRequestObject();
338
$this->getResponseObject();
340
Events::trigger('pre_system');
342
$this->benchmark->stop('bootstrap');
344
$this->benchmark->start('required_before_filters');
345
// Start up the filters
346
$filters = Services::filters();
347
// Run required before filters
348
$possibleResponse = $this->runRequiredBeforeFilters($filters);
350
// If a ResponseInterface instance is returned then send it back to the client and stop
351
if ($possibleResponse instanceof ResponseInterface) {
352
$this->response = $possibleResponse;
355
$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
356
} catch (DeprecatedRedirectException|ResponsableInterface $e) {
357
$this->outputBufferingEnd();
358
if ($e instanceof DeprecatedRedirectException) {
359
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
362
$this->response = $e->getResponse();
363
} catch (PageNotFoundException $e) {
364
$this->response = $this->display404errors($e);
365
} catch (Throwable $e) {
366
$this->outputBufferingEnd();
372
$this->runRequiredAfterFilters($filters);
374
// Is there a post-system event?
375
Events::trigger('post_system');
377
if ($returnResponse) {
378
return $this->response;
381
$this->sendResponse();
385
* Run required before filters.
387
private function runRequiredBeforeFilters(Filters $filters): ?ResponseInterface
389
$possibleResponse = $filters->runRequired('before');
390
$this->benchmark->stop('required_before_filters');
392
// If a ResponseInterface instance is returned then send it back to the client and stop
393
if ($possibleResponse instanceof ResponseInterface) {
394
return $possibleResponse;
401
* Run required after filters.
403
private function runRequiredAfterFilters(Filters $filters): void
405
$filters->setResponse($this->response);
407
// Run required after filters
408
$this->benchmark->start('required_after_filters');
409
$response = $filters->runRequired('after');
410
$this->benchmark->stop('required_after_filters');
412
if ($response instanceof ResponseInterface) {
413
$this->response = $response;
418
* Invoked via php-cli command?
420
private function isPhpCli(): bool
422
return $this->context === 'php-cli';
428
private function isWeb(): bool
430
return $this->context === 'web';
434
* Disables Controller Filters.
436
public function disableFilters(): void
438
$this->enableFilters = false;
442
* Handles the main request logic and fires the controller.
444
* @return ResponseInterface
446
* @throws PageNotFoundException
447
* @throws RedirectException
449
* @deprecated $returnResponse is deprecated.
451
protected function handleRequest(?RouteCollectionInterface $routes, Cache $cacheConfig, bool $returnResponse = false)
453
if ($this->request instanceof IncomingRequest && $this->request->getMethod() === 'CLI') {
454
return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
457
$routeFilters = $this->tryToRouteIt($routes);
459
// $uri is URL-encoded.
460
$uri = $this->request->getPath();
462
if ($this->enableFilters) {
463
/** @var Filters $filters */
464
$filters = service('filters');
466
// If any filters were specified within the routes file,
467
// we need to ensure it's active for the current request
468
if ($routeFilters !== null) {
469
$filters->enableFilters($routeFilters, 'before');
471
$oldFilterOrder = config(Feature::class)->oldFilterOrder ?? false;
472
if (! $oldFilterOrder) {
473
$routeFilters = array_reverse($routeFilters);
476
$filters->enableFilters($routeFilters, 'after');
479
// Run "before" filters
480
$this->benchmark->start('before_filters');
481
$possibleResponse = $filters->run($uri, 'before');
482
$this->benchmark->stop('before_filters');
484
// If a ResponseInterface instance is returned then send it back to the client and stop
485
if ($possibleResponse instanceof ResponseInterface) {
486
$this->outputBufferingEnd();
488
return $possibleResponse;
491
if ($possibleResponse instanceof IncomingRequest || $possibleResponse instanceof CLIRequest) {
492
$this->request = $possibleResponse;
496
$returned = $this->startController();
498
// Closure controller has run in startController().
499
if (! is_callable($this->controller)) {
500
$controller = $this->createController();
502
if (! method_exists($controller, '_remap') && ! is_callable([$controller, $this->method], false)) {
503
throw PageNotFoundException::forMethodNotFound($this->method);
506
// Is there a "post_controller_constructor" event?
507
Events::trigger('post_controller_constructor');
509
$returned = $this->runController($controller);
511
$this->benchmark->stop('controller_constructor');
512
$this->benchmark->stop('controller');
515
// If $returned is a string, then the controller output something,
516
// probably a view, instead of echoing it directly. Send it along
517
// so it can be used with the output.
518
$this->gatherOutput($cacheConfig, $returned);
520
if ($this->enableFilters) {
521
/** @var Filters $filters */
522
$filters = service('filters');
523
$filters->setResponse($this->response);
525
// Run "after" filters
526
$this->benchmark->start('after_filters');
527
$response = $filters->run($uri, 'after');
528
$this->benchmark->stop('after_filters');
530
if ($response instanceof ResponseInterface) {
531
$this->response = $response;
535
// Skip unnecessary processing for special Responses.
537
! $this->response instanceof DownloadResponse
538
&& ! $this->response instanceof RedirectResponse
540
// Save our current URI as the previous URI in the session
541
// for safer, more accurate use with `previous_url()` helper function.
542
$this->storePreviousURL(current_url(true));
547
return $this->response;
551
* You can load different configurations depending on your
552
* current environment. Setting the environment also influences
553
* things like logging and error reporting.
555
* This can be set to anything, but default usage is:
561
* @codeCoverageIgnore
565
* @deprecated 4.4.0 No longer used. Moved to index.php and spark.
567
protected function detectEnvironment()
569
// Make sure ENVIRONMENT isn't already set by other means.
570
if (! defined('ENVIRONMENT')) {
571
define('ENVIRONMENT', env('CI_ENVIRONMENT', 'production'));
576
* Load any custom boot files based upon the current environment.
578
* If no boot file exists, we shouldn't continue because something
579
* is wrong. At the very least, they should have error reporting setup.
583
* @deprecated 4.5.0 Moved to system/bootstrap.php.
585
protected function bootstrapEnvironment()
587
if (is_file(APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php')) {
588
require_once APPPATH . 'Config/Boot/' . ENVIRONMENT . '.php';
590
// @codeCoverageIgnoreStart
591
header('HTTP/1.1 503 Service Unavailable.', true, 503);
592
echo 'The application environment is not set correctly.';
594
exit(EXIT_ERROR); // EXIT_ERROR
595
// @codeCoverageIgnoreEnd
600
* Start the Benchmark
602
* The timer is used to display total script execution both in the
603
* debug toolbar, and potentially on the displayed page.
607
protected function startBenchmark()
609
if ($this->startTime === null) {
610
$this->startTime = microtime(true);
613
$this->benchmark = Services::timer();
614
$this->benchmark->start('total_execution', $this->startTime);
615
$this->benchmark->start('bootstrap');
619
* Sets a Request object to be used for this request.
620
* Used when running certain tests.
622
* @param CLIRequest|IncomingRequest $request
626
* @internal Used for testing purposes only.
629
public function setRequest($request)
631
$this->request = $request;
637
* Get our Request object, (either IncomingRequest or CLIRequest).
641
protected function getRequestObject()
643
if ($this->request instanceof Request) {
644
$this->spoofRequestMethod();
649
if ($this->isPhpCli()) {
650
Services::createRequest($this->config, true);
652
Services::createRequest($this->config);
655
$this->request = service('request');
657
$this->spoofRequestMethod();
661
* Get our Response object, and set some default values, including
662
* the HTTP protocol version and a default successful response.
666
protected function getResponseObject()
668
$this->response = Services::response($this->config);
670
if ($this->isWeb()) {
671
$this->response->setProtocolVersion($this->request->getProtocolVersion());
674
// Assume success until proven otherwise.
675
$this->response->setStatusCode(200);
679
* Force Secure Site Access? If the config value 'forceGlobalSecureRequests'
680
* is true, will enforce that all requests to this site are made through
681
* HTTPS. Will redirect the user to the current page with HTTPS, as well
682
* as set the HTTP Strict Transport Security header for those browsers
685
* @param int $duration How long the Strict Transport Security
686
* should be enforced for this URL.
690
* @deprecated 4.5.0 No longer used. Moved to ForceHTTPS filter.
692
protected function forceSecureAccess($duration = 31_536_000)
694
if ($this->config->forceGlobalSecureRequests !== true) {
698
force_https($duration, $this->request, $this->response);
702
* Determines if a response has been cached for the given URI.
704
* @return false|ResponseInterface
708
* @deprecated 4.5.0 PageCache required filter is used. No longer used.
709
* @deprecated 4.4.2 The parameter $config is deprecated. No longer used.
711
public function displayCache(Cache $config)
713
$cachedResponse = $this->pageCache->get($this->request, $this->response);
714
if ($cachedResponse instanceof ResponseInterface) {
715
$this->response = $cachedResponse;
717
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
718
$output = $this->displayPerformanceMetrics($cachedResponse->getBody());
719
$this->response->setBody($output);
721
return $this->response;
728
* Tells the app that the final output should be cached.
730
* @deprecated 4.4.0 Moved to ResponseCache::setTtl(). No longer used.
734
public static function cache(int $time)
736
static::$cacheTTL = $time;
740
* Caches the full response from the current request. Used for
741
* full-page caching for very high performance.
745
* @deprecated 4.4.0 No longer used.
747
public function cachePage(Cache $config)
751
foreach ($this->response->headers() as $header) {
752
$headers[$header->getName()] = $header->getValueLine();
755
return cache()->save($this->generateCacheName($config), serialize(['headers' => $headers, 'output' => $this->output]), static::$cacheTTL);
759
* Returns an array with our basic performance stats collected.
761
public function getPerformanceStats(): array
763
// After filter debug toolbar requires 'total_execution'.
764
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
767
'startTime' => $this->startTime,
768
'totalTime' => $this->totalTime,
773
* Generates the cache name to use for our full-page caching.
775
* @deprecated 4.4.0 No longer used.
777
protected function generateCacheName(Cache $config): string
779
if ($this->request instanceof CLIRequest) {
780
return md5($this->request->getPath());
783
$uri = clone $this->request->getUri();
785
$query = $config->cacheQueryString
786
? $uri->getQuery(is_array($config->cacheQueryString) ? ['only' => $config->cacheQueryString] : [])
789
return md5((string) $uri->setFragment('')->setQuery($query));
793
* Replaces the elapsed_time and memory_usage tag.
795
* @deprecated 4.5.0 PerformanceMetrics required filter is used. No longer used.
797
public function displayPerformanceMetrics(string $output): string
800
['{elapsed_time}', '{memory_usage}'],
801
[(string) $this->totalTime, number_format(memory_get_peak_usage() / 1024 / 1024, 3)],
807
* Try to Route It - As it sounds like, works with the router to
808
* match a route against the current URI. If the route is a
809
* "redirect route", will also handle the redirect.
811
* @param RouteCollectionInterface|null $routes A collection interface to use in place
812
* of the config file.
814
* @return list<string>|string|null Route filters, that is, the filters specified in the routes file
816
* @throws RedirectException
818
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
820
$this->benchmark->start('routing');
822
if ($routes === null) {
823
$routes = service('routes')->loadRoutes();
826
// $routes is defined in Config/Routes.php
827
$this->router = Services::router($routes, $this->request);
829
// $uri is URL-encoded.
830
$uri = $this->request->getPath();
832
$this->outputBufferingStart();
834
$this->controller = $this->router->handle($uri);
835
$this->method = $this->router->methodName();
837
// If a {locale} segment was matched in the final route,
838
// then we need to set the correct locale on our Request.
839
if ($this->router->hasLocale()) {
840
$this->request->setLocale($this->router->getLocale());
843
$this->benchmark->stop('routing');
845
return $this->router->getFilters();
849
* Determines the path to use for us to try to route to, based
850
* on the CLI/IncomingRequest path.
854
* @deprecated 4.5.0 No longer used.
856
protected function determinePath()
858
return $this->request->getPath();
862
* Now that everything has been setup, this method attempts to run the
863
* controller method and make the script go. If it's not able to, will
864
* show the appropriate Page Not Found error.
866
* @return ResponseInterface|string|void
868
protected function startController()
870
$this->benchmark->start('controller');
871
$this->benchmark->start('controller_constructor');
873
// Is it routed to a Closure?
874
if (is_object($this->controller) && ($this->controller::class === 'Closure')) {
875
$controller = $this->controller;
877
return $controller(...$this->router->params());
880
// No controller specified - we don't know what to do now.
881
if (! isset($this->controller)) {
882
throw PageNotFoundException::forEmptyController();
885
// Try to autoload the class
887
! class_exists($this->controller, true)
888
|| ($this->method[0] === '_' && $this->method !== '__invoke')
890
throw PageNotFoundException::forControllerNotFound($this->controller, $this->method);
895
* Instantiates the controller class.
899
protected function createController()
901
assert(is_string($this->controller));
903
$class = new $this->controller();
904
$class->initController($this->request, $this->response, Services::logger());
906
$this->benchmark->stop('controller_constructor');
912
* Runs the controller, allowing for _remap methods to function.
914
* CI4 supports three types of requests:
915
* 1. Web: URI segments become parameters, sent to Controllers via Routes,
916
* output controlled by Headers to browser
917
* 2. PHP CLI: accessed by CLI via php public/index.php, arguments become URI segments,
918
* sent to Controllers via Routes, output varies
920
* @param Controller $class
922
* @return false|ResponseInterface|string|void
924
protected function runController($class)
926
// This is a Web request or PHP CLI request
927
$params = $this->router->params();
929
// The controller method param types may not be string.
930
// So cannot set `declare(strict_types=1)` in this file.
931
$output = method_exists($class, '_remap')
932
? $class->_remap($this->method, ...$params)
933
: $class->{$this->method}(...$params);
935
$this->benchmark->stop('controller');
941
* Displays a 404 Page Not Found error. If set, will try to
942
* call the 404Override controller/method that was set in routing config.
944
* @return ResponseInterface|void
946
protected function display404errors(PageNotFoundException $e)
948
$this->response->setStatusCode($e->getCode());
950
// Is there a 404 Override available?
951
if ($override = $this->router->get404Override()) {
954
if ($override instanceof Closure) {
955
echo $override($e->getMessage());
956
} elseif (is_array($override)) {
957
$this->benchmark->start('controller');
958
$this->benchmark->start('controller_constructor');
960
$this->controller = $override[0];
961
$this->method = $override[1];
963
$controller = $this->createController();
965
$returned = $controller->{$this->method}($e->getMessage());
967
$this->benchmark->stop('controller');
972
$cacheConfig = config(Cache::class);
973
$this->gatherOutput($cacheConfig, $returned);
975
return $this->response;
978
$this->outputBufferingEnd();
980
// Throws new PageNotFoundException and remove exception message on production.
981
throw PageNotFoundException::forPageNotFound(
982
(ENVIRONMENT !== 'production' || ! $this->isWeb()) ? $e->getMessage() : null
987
* Gathers the script output from the buffer, replaces some execution
988
* time tag in the output and displays the debug toolbar, if required.
990
* @param Cache|null $cacheConfig Deprecated. No longer used.
991
* @param ResponseInterface|string|null $returned
993
* @deprecated $cacheConfig is deprecated.
997
protected function gatherOutput(?Cache $cacheConfig = null, $returned = null)
999
$this->output = $this->outputBufferingEnd();
1001
if ($returned instanceof DownloadResponse) {
1002
$this->response = $returned;
1006
// If the controller returned a response object,
1007
// we need to grab the body from it so it can
1008
// be added to anything else that might have been
1010
// We also need to save the instance locally
1011
// so that any status code changes, etc, take place.
1012
if ($returned instanceof ResponseInterface) {
1013
$this->response = $returned;
1014
$returned = $returned->getBody();
1017
if (is_string($returned)) {
1018
$this->output .= $returned;
1021
$this->response->setBody($this->output);
1025
* If we have a session object to use, store the current URI
1026
* as the previous URI. This is called just prior to sending the
1027
* response to the client, and will make it available next request.
1029
* This helps provider safer, more reliable previous_url() detection.
1031
* @param string|URI $uri
1035
public function storePreviousURL($uri)
1037
// Ignore CLI requests
1038
if (! $this->isWeb()) {
1041
// Ignore AJAX requests
1042
if (method_exists($this->request, 'isAJAX') && $this->request->isAJAX()) {
1046
// Ignore unroutable responses
1047
if ($this->response instanceof DownloadResponse || $this->response instanceof RedirectResponse) {
1051
// Ignore non-HTML responses
1052
if (! str_contains($this->response->getHeaderLine('Content-Type'), 'text/html')) {
1056
// This is mainly needed during testing...
1057
if (is_string($uri)) {
1058
$uri = new URI($uri);
1061
if (isset($_SESSION)) {
1062
session()->set('_ci_previous_url', URI::createURIString(
1064
$uri->getAuthority(),
1073
* Modifies the Request Object to use a different method if a POST
1074
* variable called _method is found.
1078
public function spoofRequestMethod()
1080
// Only works with POSTED forms
1081
if ($this->request->getMethod() !== Method::POST) {
1085
$method = $this->request->getPost('_method');
1087
if ($method === null) {
1091
// Only allows PUT, PATCH, DELETE
1092
if (in_array($method, [Method::PUT, Method::PATCH, Method::DELETE], true)) {
1093
$this->request = $this->request->setMethod($method);
1098
* Sends the output of this request back to the client.
1099
* This is what they've been waiting for!
1103
protected function sendResponse()
1105
$this->response->send();
1109
* Exits the application, setting the exit code for CLI-based applications
1110
* that might be watching.
1112
* Made into a separate method so that it can be mocked during testing
1113
* without actually stopping script execution.
1117
* @deprecated 4.4.0 No longer Used. Moved to index.php.
1121
protected function callExit($code)
1123
exit($code); // @codeCoverageIgnore
1127
* Sets the app context.
1129
* @phpstan-param 'php-cli'|'web' $context
1133
public function setContext(string $context)
1135
$this->context = $context;
1140
protected function outputBufferingStart(): void
1142
$this->bufferLevel = ob_get_level();
1146
protected function outputBufferingEnd(): string
1150
while (ob_get_level() > $this->bufferLevel) {
1151
$buffer .= ob_get_contents();