Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new Required Filters #8053

Merged
merged 32 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dfbe9a1
feat: add PageCache filter
kenjis Oct 17, 2023
22f9c1d
feat: add PerformanceMetrics filter
kenjis Oct 17, 2023
27b176c
feat: add "Required Filters" and use PageCache and PerformanceMetrics
kenjis Oct 17, 2023
fe28807
test: update existing tests
kenjis Oct 17, 2023
e0d80e8
fix: add check if filters are empty
kenjis Oct 17, 2023
7b9805e
fix: move event point post_system before sending Response
kenjis Oct 18, 2023
a010895
refactor: extract methods
kenjis Oct 18, 2023
7cd2797
refactor: use private methods
kenjis Oct 18, 2023
43c3a01
refactor: rename variable name
kenjis Oct 18, 2023
005b95f
refactor: fix PHPStan errors
kenjis Oct 18, 2023
a4dd669
refactor: remove meaningless code
kenjis Oct 18, 2023
c0d7751
fix: forceSecureAccess() position
kenjis Oct 19, 2023
df9a5f8
docs: add @return
kenjis Nov 3, 2023
2a4e587
docs: fix by proofreading
kenjis Nov 5, 2023
db582a2
refactor: use single iteration
kenjis Nov 5, 2023
a4998c2
refactor: move forceSecureAccess() to ForceHTTPS filter
kenjis Nov 3, 2023
2c497d9
docs: add comments to Config\Filters
kenjis Nov 5, 2023
10b6cad
docs: add about Required Filters
kenjis Nov 5, 2023
91994fa
docs: update Filter Execution Order
kenjis Nov 5, 2023
7646a44
docs: add about ForceHTTPS and PerformanceMetrics
kenjis Nov 5, 2023
0cc0654
docs: change TOC depth to 3
kenjis Nov 9, 2023
57fb443
docs: update filter property name
kenjis Nov 9, 2023
af366ad
docs: add changelog
kenjis Nov 9, 2023
cb2516a
docs: add upgrade
kenjis Nov 9, 2023
8aa9169
docs: fix by proofreading
kenjis Nov 19, 2023
523e414
docs: add @internal
kenjis Nov 19, 2023
71cc57a
config: add system/Config/Filters.php
kenjis Nov 19, 2023
baeea0f
refactor: extract setToolbarToLast()
kenjis Nov 19, 2023
64d6965
feat: to work even if Config\Filters is not updated
kenjis Nov 19, 2023
1217561
fix: toolbar does not work in globals filters
kenjis Nov 19, 2023
3ed4329
docs: app/Config/Filters.php changes are not mandatory
kenjis Nov 19, 2023
709b9f2
docs: add @phpstan-ignore-line
kenjis Nov 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Config/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class App extends BaseConfig
* If true, this will force every request made to this application to be
* made via a secure connection (HTTPS). If the incoming request is not
* secure, the user will be redirected to a secure version of the page
* and the HTTP Strict Transport Security header will be set.
* and the HTTP Strict Transport Security (HSTS) header will be set.
*/
public bool $forceGlobalSecureRequests = false;

Expand Down
42 changes: 37 additions & 5 deletions app/Config/Filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,61 @@

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Config\Filters as BaseFilters;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
use CodeIgniter\Filters\ForceHTTPS;
use CodeIgniter\Filters\Honeypot;
use CodeIgniter\Filters\InvalidChars;
use CodeIgniter\Filters\PageCache;
use CodeIgniter\Filters\PerformanceMetrics;
use CodeIgniter\Filters\SecureHeaders;

class Filters extends BaseConfig
class Filters extends BaseFilters
{
/**
* Configures aliases for Filter classes to
* make reading things nicer and simpler.
*
* @var array<string, class-string|list<class-string>> [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
* @var array<string, class-string|list<class-string>>
*
* [filter_name => classname]
* or [filter_name => [classname1, classname2, ...]]
*/
public array $aliases = [
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,
'honeypot' => Honeypot::class,
'invalidchars' => InvalidChars::class,
'secureheaders' => SecureHeaders::class,
'forcehttps' => ForceHTTPS::class,
'pagecache' => PageCache::class,
'performance' => PerformanceMetrics::class,
];

/**
* List of special required filters.
*
* The filters listed here are special. They are applied before and after
* other kinds of filters, and always applied even if a route does not exist.
*
* Filters set by default provide framework functionality. If removed,
* those functions will no longer work.
*
* @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters
*
* @var array{before: list<string>, after: list<string>}
*/
public array $required = [
'before' => [
'forcehttps', // Force Global Secure Requests
'pagecache', // Web Page Caching
],
'after' => [
'pagecache', // Web Page Caching
'performance', // Performance Metrics
'toolbar', // Debug Toolbar
],
];

/**
Expand All @@ -39,7 +72,6 @@ class Filters extends BaseConfig
// 'invalidchars',
],
'after' => [
'toolbar',
// 'honeypot',
// 'secureheaders',
],
Expand Down
2 changes: 1 addition & 1 deletion phpstan-baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -1923,7 +1923,7 @@
];
$ignoreErrors[] = [
'message' => '#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#',
'count' => 2,
'count' => 1,
'path' => __DIR__ . '/system/Filters/Filters.php',
];
$ignoreErrors[] = [
Expand Down
120 changes: 76 additions & 44 deletions system/CodeIgniter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use CodeIgniter\Events\Events;
use CodeIgniter\Exceptions\FrameworkException;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\Filters\Filters;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\DownloadResponse;
use CodeIgniter\HTTP\Exceptions\RedirectException;
Expand Down Expand Up @@ -339,32 +340,83 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon
$this->getRequestObject();
$this->getResponseObject();

try {
$this->forceSecureAccess();
Events::trigger('pre_system');

$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}
$this->benchmark->stop('bootstrap');

$this->benchmark->start('required_before_filters');
// Start up the filters
$filters = Services::filters();
// Run required before filters
$possibleResponse = $this->runRequiredBeforeFilters($filters);

$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
$this->response = $this->display404errors($e);
} catch (Throwable $e) {
$this->outputBufferingEnd();
// If a ResponseInterface instance is returned then send it back to the client and stop
if ($possibleResponse instanceof ResponseInterface) {
$this->response = $possibleResponse;
} else {
try {
$this->response = $this->handleRequest($routes, config(Cache::class), $returnResponse);
} catch (ResponsableInterface|DeprecatedRedirectException $e) {
$this->outputBufferingEnd();
if ($e instanceof DeprecatedRedirectException) {
$e = new RedirectException($e->getMessage(), $e->getCode(), $e);
}

throw $e;
$this->response = $e->getResponse();
} catch (PageNotFoundException $e) {
$this->response = $this->display404errors($e);
} catch (Throwable $e) {
$this->outputBufferingEnd();

throw $e;
}
}

$this->runRequiredAfterFilters($filters);

// Is there a post-system event?
Events::trigger('post_system');

if ($returnResponse) {
return $this->response;
}

$this->sendResponse();
}

/**
* Run required before filters.
*/
private function runRequiredBeforeFilters(Filters $filters): ?ResponseInterface
{
$possibleResponse = $filters->runRequired('before');
$this->benchmark->stop('required_before_filters');

// If a ResponseInterface instance is returned then send it back to the client and stop
if ($possibleResponse instanceof ResponseInterface) {
return $possibleResponse;
}

return null;
}

/**
* Run required after filters.
*/
private function runRequiredAfterFilters(Filters $filters): void
{
$filters->setResponse($this->response);

// Run required after filters
$this->benchmark->start('required_after_filters');
$response = $filters->runRequired('after');
$this->benchmark->stop('required_after_filters');

if ($response instanceof ResponseInterface) {
$this->response = $response;
}
}

/**
* Invoked via php-cli command?
*/
Expand Down Expand Up @@ -405,20 +457,11 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
return $this->response->setStatusCode(405)->setBody('Method Not Allowed');
}

Events::trigger('pre_system');

// Check for a cached page. Execution will stop
// if the page has been cached.
if (($response = $this->displayCache($cacheConfig)) instanceof ResponseInterface) {
return $response;
}

$routeFilters = $this->tryToRouteIt($routes);

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

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

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

// After filter debug toolbar requires 'total_execution'.
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');

// Run "after" filters
$this->benchmark->start('after_filters');
$response = $filters->run($uri, 'after');
Expand All @@ -496,21 +536,13 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
! $this->response instanceof DownloadResponse
&& ! $this->response instanceof RedirectResponse
) {
// Cache it without the performance metrics replaced
// so that we can have live speed updates along the way.
// Must be run after filters to preserve the Response headers.
$this->pageCache->make($this->request, $this->response);

// Save our current URI as the previous URI in the session
// for safer, more accurate use with `previous_url()` helper function.
$this->storePreviousURL(current_url(true));
}

unset($uri);

// Is there a post-system event?
Events::trigger('post_system');

return $this->response;
}

Expand Down Expand Up @@ -651,6 +683,8 @@ protected function getResponseObject()
* should be enforced for this URL.
*
* @return void
*
* @deprecated 4.5.0 No longer used. Moved to ForceHTTPS filter.
*/
protected function forceSecureAccess($duration = 31_536_000)
{
Expand All @@ -668,6 +702,7 @@ protected function forceSecureAccess($duration = 31_536_000)
*
* @throws Exception
*
* @deprecated 4.5.0 PageCache required filter is used. No longer used.
* @deprecated 4.4.2 The parameter $config is deprecated. No longer used.
*/
public function displayCache(Cache $config)
Expand Down Expand Up @@ -722,6 +757,9 @@ public function cachePage(Cache $config)
*/
public function getPerformanceStats(): array
{
// After filter debug toolbar requires 'total_execution'.
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');

return [
'startTime' => $this->startTime,
'totalTime' => $this->totalTime,
Expand Down Expand Up @@ -750,6 +788,8 @@ protected function generateCacheName(Cache $config): string

/**
* Replaces the elapsed_time and memory_usage tag.
*
* @deprecated 4.5.0 PerformanceMetrics required filter is used. No longer used.
*/
public function displayPerformanceMetrics(string $output): string
{
Expand All @@ -774,6 +814,8 @@ public function displayPerformanceMetrics(string $output): string
*/
protected function tryToRouteIt(?RouteCollectionInterface $routes = null)
{
$this->benchmark->start('routing');
MGatner marked this conversation as resolved.
Show resolved Hide resolved

if ($routes === null) {
$routes = Services::routes()->loadRoutes();
}
Expand All @@ -783,9 +825,6 @@ protected function tryToRouteIt(?RouteCollectionInterface $routes = null)

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

$this->benchmark->stop('bootstrap');
$this->benchmark->start('routing');

$this->outputBufferingStart();

$this->controller = $this->router->handle($uri);
Expand Down Expand Up @@ -1052,13 +1091,6 @@ public function spoofRequestMethod()
*/
protected function sendResponse()
{
// Update the performance metrics
$body = $this->response->getBody();
if ($body !== null) {
$output = $this->displayPerformanceMetrics($body);
$this->response->setBody($output);
}

$this->response->send();
}

Expand Down
Loading
Loading