Skip to content

Commit b5451cb

Browse files
authored
Merge pull request #8053 from kenjis/feat-required-filters
feat: new Required Filters
2 parents 4907a18 + 709b9f2 commit b5451cb

File tree

21 files changed

+841
-182
lines changed

21 files changed

+841
-182
lines changed

app/Config/App.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class App extends BaseConfig
130130
* If true, this will force every request made to this application to be
131131
* made via a secure connection (HTTPS). If the incoming request is not
132132
* secure, the user will be redirected to a secure version of the page
133-
* and the HTTP Strict Transport Security header will be set.
133+
* and the HTTP Strict Transport Security (HSTS) header will be set.
134134
*/
135135
public bool $forceGlobalSecureRequests = false;
136136

app/Config/Filters.php

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,61 @@
22

33
namespace Config;
44

5-
use CodeIgniter\Config\BaseConfig;
5+
use CodeIgniter\Config\Filters as BaseFilters;
66
use CodeIgniter\Filters\CSRF;
77
use CodeIgniter\Filters\DebugToolbar;
8+
use CodeIgniter\Filters\ForceHTTPS;
89
use CodeIgniter\Filters\Honeypot;
910
use CodeIgniter\Filters\InvalidChars;
11+
use CodeIgniter\Filters\PageCache;
12+
use CodeIgniter\Filters\PerformanceMetrics;
1013
use CodeIgniter\Filters\SecureHeaders;
1114

12-
class Filters extends BaseConfig
15+
class Filters extends BaseFilters
1316
{
1417
/**
1518
* Configures aliases for Filter classes to
1619
* make reading things nicer and simpler.
1720
*
18-
* @var array<string, class-string|list<class-string>> [filter_name => classname]
19-
* or [filter_name => [classname1, classname2, ...]]
21+
* @var array<string, class-string|list<class-string>>
22+
*
23+
* [filter_name => classname]
24+
* or [filter_name => [classname1, classname2, ...]]
2025
*/
2126
public array $aliases = [
2227
'csrf' => CSRF::class,
2328
'toolbar' => DebugToolbar::class,
2429
'honeypot' => Honeypot::class,
2530
'invalidchars' => InvalidChars::class,
2631
'secureheaders' => SecureHeaders::class,
32+
'forcehttps' => ForceHTTPS::class,
33+
'pagecache' => PageCache::class,
34+
'performance' => PerformanceMetrics::class,
35+
];
36+
37+
/**
38+
* List of special required filters.
39+
*
40+
* The filters listed here are special. They are applied before and after
41+
* other kinds of filters, and always applied even if a route does not exist.
42+
*
43+
* Filters set by default provide framework functionality. If removed,
44+
* those functions will no longer work.
45+
*
46+
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
47+
*
48+
* @var array{before: list<string>, after: list<string>}
49+
*/
50+
public array $required = [
51+
'before' => [
52+
'forcehttps', // Force Global Secure Requests
53+
'pagecache', // Web Page Caching
54+
],
55+
'after' => [
56+
'pagecache', // Web Page Caching
57+
'performance', // Performance Metrics
58+
'toolbar', // Debug Toolbar
59+
],
2760
];
2861

2962
/**
@@ -39,7 +72,6 @@ class Filters extends BaseConfig
3972
// 'invalidchars',
4073
],
4174
'after' => [
42-
'toolbar',
4375
// 'honeypot',
4476
// 'secureheaders',
4577
],

phpstan-baseline.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1923,7 +1923,7 @@
19231923
];
19241924
$ignoreErrors[] = [
19251925
'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#',
1926-
'count' => 2,
1926+
'count' => 1,
19271927
'path' => __DIR__ . '/system/Filters/Filters.php',
19281928
];
19291929
$ignoreErrors[] = [

system/CodeIgniter.php

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use CodeIgniter\Events\Events;
1818
use CodeIgniter\Exceptions\FrameworkException;
1919
use CodeIgniter\Exceptions\PageNotFoundException;
20+
use CodeIgniter\Filters\Filters;
2021
use CodeIgniter\HTTP\CLIRequest;
2122
use CodeIgniter\HTTP\DownloadResponse;
2223
use CodeIgniter\HTTP\Exceptions\RedirectException;
@@ -339,32 +340,83 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon
339340
$this->getRequestObject();
340341
$this->getResponseObject();
341342

342-
try {
343-
$this->forceSecureAccess();
343+
Events::trigger('pre_system');
344344

345-
$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
346-
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
347-
$this->outputBufferingEnd();
348-
if ($e instanceof DeprecatedRedirectException) {
349-
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
350-
}
345+
$this->benchmark->stop('bootstrap');
346+
347+
$this->benchmark->start('required_before_filters');
348+
// Start up the filters
349+
$filters = Services::filters();
350+
// Run required before filters
351+
$possibleResponse = $this->runRequiredBeforeFilters($filters);
351352

352-
$this->response = $e->getResponse();
353-
} catch (PageNotFoundException $e) {
354-
$this->response = $this->display404errors($e);
355-
} catch (Throwable $e) {
356-
$this->outputBufferingEnd();
353+
// If a ResponseInterface instance is returned then send it back to the client and stop
354+
if ($possibleResponse instanceof ResponseInterface) {
355+
$this->response = $possibleResponse;
356+
} else {
357+
try {
358+
$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
359+
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
360+
$this->outputBufferingEnd();
361+
if ($e instanceof DeprecatedRedirectException) {
362+
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
363+
}
357364

358-
throw $e;
365+
$this->response = $e->getResponse();
366+
} catch (PageNotFoundException $e) {
367+
$this->response = $this->display404errors($e);
368+
} catch (Throwable $e) {
369+
$this->outputBufferingEnd();
370+
371+
throw $e;
372+
}
359373
}
360374

375+
$this->runRequiredAfterFilters($filters);
376+
377+
// Is there a post-system event?
378+
Events::trigger('post_system');
379+
361380
if ($returnResponse) {
362381
return $this->response;
363382
}
364383

365384
$this->sendResponse();
366385
}
367386

387+
/**
388+
* Run required before filters.
389+
*/
390+
private function runRequiredBeforeFilters(Filters $filters): ?ResponseInterface
391+
{
392+
$possibleResponse = $filters->runRequired('before');
393+
$this->benchmark->stop('required_before_filters');
394+
395+
// If a ResponseInterface instance is returned then send it back to the client and stop
396+
if ($possibleResponse instanceof ResponseInterface) {
397+
return $possibleResponse;
398+
}
399+
400+
return null;
401+
}
402+
403+
/**
404+
* Run required after filters.
405+
*/
406+
private function runRequiredAfterFilters(Filters $filters): void
407+
{
408+
$filters->setResponse($this->response);
409+
410+
// Run required after filters
411+
$this->benchmark->start('required_after_filters');
412+
$response = $filters->runRequired('after');
413+
$this->benchmark->stop('required_after_filters');
414+
415+
if ($response instanceof ResponseInterface) {
416+
$this->response = $response;
417+
}
418+
}
419+
368420
/**
369421
* Invoked via php-cli command?
370422
*/
@@ -405,20 +457,11 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
405457
return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
406458
}
407459

408-
Events::trigger('pre_system');
409-
410-
// Check for a cached page. Execution will stop
411-
// if the page has been cached.
412-
if (($response = $this->displayCache($cacheConfig)) instanceof ResponseInterface) {
413-
return $response;
414-
}
415-
416460
$routeFilters = $this->tryToRouteIt($routes);
417461

418462
$uri = $this->request->getPath();
419463

420464
if ($this->enableFilters) {
421-
// Start up the filters
422465
$filters = Services::filters();
423466

424467
// If any filters were specified within the routes file,
@@ -478,9 +521,6 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
478521
$filters = Services::filters();
479522
$filters->setResponse($this->response);
480523

481-
// After filter debug toolbar requires 'total_execution'.
482-
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
483-
484524
// Run "after" filters
485525
$this->benchmark->start('after_filters');
486526
$response = $filters->run($uri, 'after');
@@ -496,21 +536,13 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
496536
! $this->response instanceof DownloadResponse
497537
&& ! $this->response instanceof RedirectResponse
498538
) {
499-
// Cache it without the performance metrics replaced
500-
// so that we can have live speed updates along the way.
501-
// Must be run after filters to preserve the Response headers.
502-
$this->pageCache->make($this->request, $this->response);
503-
504539
// Save our current URI as the previous URI in the session
505540
// for safer, more accurate use with `previous_url()` helper function.
506541
$this->storePreviousURL(current_url(true));
507542
}
508543

509544
unset($uri);
510545

511-
// Is there a post-system event?
512-
Events::trigger('post_system');
513-
514546
return $this->response;
515547
}
516548

@@ -651,6 +683,8 @@ protected function getResponseObject()
651683
* should be enforced for this URL.
652684
*
653685
* @return void
686+
*
687+
* @deprecated 4.5.0 No longer used. Moved to ForceHTTPS filter.
654688
*/
655689
protected function forceSecureAccess($duration = 31_536_000)
656690
{
@@ -668,6 +702,7 @@ protected function forceSecureAccess($duration = 31_536_000)
668702
*
669703
* @throws Exception
670704
*
705+
* @deprecated 4.5.0 PageCache required filter is used. No longer used.
671706
* @deprecated 4.4.2 The parameter $config is deprecated. No longer used.
672707
*/
673708
public function displayCache(Cache $config)
@@ -722,6 +757,9 @@ public function cachePage(Cache $config)
722757
*/
723758
public function getPerformanceStats(): array
724759
{
760+
// After filter debug toolbar requires 'total_execution'.
761+
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
762+
725763
return [
726764
'startTime' => $this->startTime,
727765
'totalTime' => $this->totalTime,
@@ -750,6 +788,8 @@ protected function generateCacheName(Cache $config): string
750788

751789
/**
752790
* Replaces the elapsed_time and memory_usage tag.
791+
*
792+
* @deprecated 4.5.0 PerformanceMetrics required filter is used. No longer used.
753793
*/
754794
public function displayPerformanceMetrics(string $output): string
755795
{
@@ -774,6 +814,8 @@ public function displayPerformanceMetrics(string $output): string
774814
*/
775815
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
776816
{
817+
$this->benchmark->start('routing');
818+
777819
if ($routes === null) {
778820
$routes = Services::routes()->loadRoutes();
779821
}
@@ -783,9 +825,6 @@ protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
783825

784826
$uri = $this->request->getPath();
785827

786-
$this->benchmark->stop('bootstrap');
787-
$this->benchmark->start('routing');
788-
789828
$this->outputBufferingStart();
790829

791830
$this->controller = $this->router->handle($uri);
@@ -1052,13 +1091,6 @@ public function spoofRequestMethod()
10521091
*/
10531092
protected function sendResponse()
10541093
{
1055-
// Update the performance metrics
1056-
$body = $this->response->getBody();
1057-
if ($body !== null) {
1058-
$output = $this->displayPerformanceMetrics($body);
1059-
$this->response->setBody($output);
1060-
}
1061-
10621094
$this->response->send();
10631095
}
10641096

0 commit comments

Comments
 (0)