Skip to content

Commit 0d15487

Browse files
authored
Merge pull request #7644 from kenjis/refactor-PageCache
refactor: extract ResponseCache class for Web Page Caching
2 parents 8d9b176 + 7890156 commit 0d15487

File tree

11 files changed

+503
-51
lines changed

11 files changed

+503
-51
lines changed

deptrac.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ parameters:
222222
- Cache
223223
skip_violations:
224224
# Individual class exemptions
225+
CodeIgniter\Cache\ResponseCache:
226+
- CodeIgniter\HTTP\CLIRequest
227+
- CodeIgniter\HTTP\IncomingRequest
228+
- CodeIgniter\HTTP\ResponseInterface
225229
CodeIgniter\Entity\Cast\URICast:
226230
- CodeIgniter\HTTP\URI
227231
CodeIgniter\Log\Handlers\ChromeLoggerHandler:

phpstan-baseline.neon.dist

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,6 @@ parameters:
2525
count: 1
2626
path: system/Cache/Handlers/RedisHandler.php
2727

28-
-
29-
message: "#^Call to an undefined method CodeIgniter\\\\HTTP\\\\Request\\:\\:getPost\\(\\)\\.$#"
30-
count: 1
31-
path: system/CodeIgniter.php
32-
33-
-
34-
message: "#^Call to an undefined method CodeIgniter\\\\HTTP\\\\Request\\:\\:setLocale\\(\\)\\.$#"
35-
count: 1
36-
path: system/CodeIgniter.php
37-
3828
-
3929
message: "#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$db \\(CodeIgniter\\\\Database\\\\BaseConnection\\) in empty\\(\\) is not falsy\\.$#"
4030
count: 1

system/Cache/ResponseCache.php

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
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\Cache;
13+
14+
use CodeIgniter\HTTP\CLIRequest;
15+
use CodeIgniter\HTTP\IncomingRequest;
16+
use CodeIgniter\HTTP\ResponseInterface;
17+
use Config\Cache as CacheConfig;
18+
use Exception;
19+
20+
/**
21+
* Web Page Caching
22+
*/
23+
final class ResponseCache
24+
{
25+
/**
26+
* Whether to take the URL query string into consideration when generating
27+
* output cache files. Valid options are:
28+
*
29+
* false = Disabled
30+
* true = Enabled, take all query parameters into account.
31+
* Please be aware that this may result in numerous cache
32+
* files generated for the same page over and over again.
33+
* array('q') = Enabled, but only take into account the specified list
34+
* of query parameters.
35+
*
36+
* @var bool|string[]
37+
*/
38+
private $cacheQueryString = false;
39+
40+
/**
41+
* Cache time to live.
42+
*
43+
* @var int seconds
44+
*/
45+
private int $ttl = 0;
46+
47+
private CacheInterface $cache;
48+
49+
public function __construct(CacheConfig $config, CacheInterface $cache)
50+
{
51+
$this->cacheQueryString = $config->cacheQueryString;
52+
$this->cache = $cache;
53+
}
54+
55+
/**
56+
* @return $this
57+
*/
58+
public function setTtl(int $ttl)
59+
{
60+
$this->ttl = $ttl;
61+
62+
return $this;
63+
}
64+
65+
/**
66+
* Generates the cache key to use from the current request.
67+
*
68+
* @param CLIRequest|IncomingRequest $request
69+
*
70+
* @internal for testing purposes only
71+
*/
72+
public function generateCacheKey($request): string
73+
{
74+
if ($request instanceof CLIRequest) {
75+
return md5($request->getPath());
76+
}
77+
78+
$uri = clone $request->getUri();
79+
80+
$query = $this->cacheQueryString
81+
? $uri->getQuery(is_array($this->cacheQueryString) ? ['only' => $this->cacheQueryString] : [])
82+
: '';
83+
84+
return md5($uri->setFragment('')->setQuery($query));
85+
}
86+
87+
/**
88+
* Caches the response.
89+
*
90+
* @param CLIRequest|IncomingRequest $request
91+
*/
92+
public function make($request, ResponseInterface $response): bool
93+
{
94+
if ($this->ttl === 0) {
95+
return true;
96+
}
97+
98+
$headers = [];
99+
100+
foreach ($response->headers() as $header) {
101+
$headers[$header->getName()] = $header->getValueLine();
102+
}
103+
104+
return $this->cache->save(
105+
$this->generateCacheKey($request),
106+
serialize(['headers' => $headers, 'output' => $response->getBody()]),
107+
$this->ttl
108+
);
109+
}
110+
111+
/**
112+
* Gets the cached response for the request.
113+
*
114+
* @param CLIRequest|IncomingRequest $request
115+
*/
116+
public function get($request, ResponseInterface $response): ?ResponseInterface
117+
{
118+
if ($cachedResponse = $this->cache->get($this->generateCacheKey($request))) {
119+
$cachedResponse = unserialize($cachedResponse);
120+
121+
if (
122+
! is_array($cachedResponse)
123+
|| ! isset($cachedResponse['output'])
124+
|| ! isset($cachedResponse['headers'])
125+
) {
126+
throw new Exception('Error unserializing page cache');
127+
}
128+
129+
$headers = $cachedResponse['headers'];
130+
$output = $cachedResponse['output'];
131+
132+
// Clear all default headers
133+
foreach (array_keys($response->headers()) as $key) {
134+
$response->removeHeader($key);
135+
}
136+
137+
// Set cached headers
138+
foreach ($headers as $name => $value) {
139+
$response->setHeader($name, $value);
140+
}
141+
142+
$response->setBody($output);
143+
144+
return $response;
145+
}
146+
147+
return null;
148+
}
149+
}

system/CodeIgniter.php

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace CodeIgniter;
1313

1414
use Closure;
15+
use CodeIgniter\Cache\ResponseCache;
1516
use CodeIgniter\Debug\Timer;
1617
use CodeIgniter\Events\Events;
1718
use CodeIgniter\Exceptions\FrameworkException;
@@ -84,7 +85,7 @@ class CodeIgniter
8485
/**
8586
* Current request.
8687
*
87-
* @var CLIRequest|IncomingRequest|Request|null
88+
* @var CLIRequest|IncomingRequest|null
8889
*/
8990
protected $request;
9091

@@ -127,6 +128,8 @@ class CodeIgniter
127128
* Cache expiration time
128129
*
129130
* @var int seconds
131+
*
132+
* @deprecated 4.4.0 Moved to ResponseCache::$ttl. No longer used.
130133
*/
131134
protected static $cacheTTL = 0;
132135

@@ -175,13 +178,20 @@ class CodeIgniter
175178
*/
176179
protected int $bufferLevel;
177180

181+
/**
182+
* Web Page Caching
183+
*/
184+
protected ResponseCache $pageCache;
185+
178186
/**
179187
* Constructor.
180188
*/
181189
public function __construct(App $config)
182190
{
183191
$this->startTime = microtime(true);
184192
$this->config = $config;
193+
194+
$this->pageCache = Services::responsecache();
185195
}
186196

187197
/**
@@ -330,7 +340,7 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon
330340
);
331341
}
332342

333-
static::$cacheTTL = 0;
343+
$this->pageCache->setTtl(0);
334344
$this->bufferLevel = ob_get_level();
335345

336346
$this->startBenchmark();
@@ -463,7 +473,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
463473
return $possibleResponse;
464474
}
465475

466-
if ($possibleResponse instanceof Request) {
476+
if ($possibleResponse instanceof IncomingRequest || $possibleResponse instanceof CLIRequest) {
467477
$this->request = $possibleResponse;
468478
}
469479
}
@@ -517,9 +527,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
517527
// Cache it without the performance metrics replaced
518528
// so that we can have live speed updates along the way.
519529
// Must be run after filters to preserve the Response headers.
520-
if (static::$cacheTTL > 0) {
521-
$this->cachePage($cacheConfig);
522-
}
530+
$this->pageCache->make($this->request, $this->response);
523531

524532
// Update the performance metrics
525533
$body = $this->response->getBody();
@@ -603,9 +611,11 @@ protected function startBenchmark()
603611
* Sets a Request object to be used for this request.
604612
* Used when running certain tests.
605613
*
614+
* @param CLIRequest|IncomingRequest $request
615+
*
606616
* @return $this
607617
*/
608-
public function setRequest(Request $request)
618+
public function setRequest($request)
609619
{
610620
$this->request = $request;
611621

@@ -674,27 +684,11 @@ protected function forceSecureAccess($duration = 31_536_000)
674684
*/
675685
public function displayCache(Cache $config)
676686
{
677-
if ($cachedResponse = cache()->get($this->generateCacheName($config))) {
678-
$cachedResponse = unserialize($cachedResponse);
679-
if (! is_array($cachedResponse) || ! isset($cachedResponse['output']) || ! isset($cachedResponse['headers'])) {
680-
throw new Exception('Error unserializing page cache');
681-
}
682-
683-
$headers = $cachedResponse['headers'];
684-
$output = $cachedResponse['output'];
685-
686-
// Clear all default headers
687-
foreach (array_keys($this->response->headers()) as $key) {
688-
$this->response->removeHeader($key);
689-
}
690-
691-
// Set cached headers
692-
foreach ($headers as $name => $value) {
693-
$this->response->setHeader($name, $value);
694-
}
687+
if ($cachedResponse = $this->pageCache->get($this->request, $this->response)) {
688+
$this->response = $cachedResponse;
695689

696690
$this->totalTime = $this->benchmark->getElapsedTime('total_execution');
697-
$output = $this->displayPerformanceMetrics($output);
691+
$output = $this->displayPerformanceMetrics($cachedResponse->getBody());
698692
$this->response->setBody($output);
699693

700694
return $this->response;
@@ -705,6 +699,8 @@ public function displayCache(Cache $config)
705699

706700
/**
707701
* Tells the app that the final output should be cached.
702+
*
703+
* @deprecated 4.4.0 Moved to ResponseCache::setTtl(). to No longer used.
708704
*/
709705
public static function cache(int $time)
710706
{
@@ -716,6 +712,8 @@ public static function cache(int $time)
716712
* full-page caching for very high performance.
717713
*
718714
* @return bool
715+
*
716+
* @deprecated 4.4.0 No longer used.
719717
*/
720718
public function cachePage(Cache $config)
721719
{
@@ -741,6 +739,8 @@ public function getPerformanceStats(): array
741739

742740
/**
743741
* Generates the cache name to use for our full-page caching.
742+
*
743+
* @deprecated 4.4.0 No longer used.
744744
*/
745745
protected function generateCacheName(Cache $config): string
746746
{

system/Config/BaseService.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use CodeIgniter\Autoloader\Autoloader;
1515
use CodeIgniter\Autoloader\FileLocator;
1616
use CodeIgniter\Cache\CacheInterface;
17+
use CodeIgniter\Cache\ResponseCache;
1718
use CodeIgniter\CLI\Commands;
1819
use CodeIgniter\CodeIgniter;
1920
use CodeIgniter\Database\ConnectionInterface;
@@ -117,6 +118,7 @@
117118
* @method static View renderer($viewPath = null, ConfigView $config = null, $getShared = true)
118119
* @method static IncomingRequest|CLIRequest request(App $config = null, $getShared = true)
119120
* @method static ResponseInterface response(App $config = null, $getShared = true)
121+
* @method static ResponseCache responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
120122
* @method static Router router(RouteCollectionInterface $routes = null, Request $request = null, $getShared = true)
121123
* @method static RouteCollection routes($getShared = true)
122124
* @method static Security security(App $config = null, $getShared = true)

system/Config/Services.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use CodeIgniter\Cache\CacheFactory;
1515
use CodeIgniter\Cache\CacheInterface;
16+
use CodeIgniter\Cache\ResponseCache;
1617
use CodeIgniter\CLI\Commands;
1718
use CodeIgniter\CodeIgniter;
1819
use CodeIgniter\Database\ConnectionInterface;
@@ -438,6 +439,23 @@ public static function negotiator(?RequestInterface $request = null, bool $getSh
438439
return new Negotiate($request);
439440
}
440441

442+
/**
443+
* Return the ResponseCache.
444+
*
445+
* @return ResponseCache
446+
*/
447+
public static function responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true)
448+
{
449+
if ($getShared) {
450+
return static::getSharedInstance('responsecache', $config, $cache);
451+
}
452+
453+
$config ??= config(Cache::class);
454+
$cache ??= AppServices::cache();
455+
456+
return new ResponseCache($config, $cache);
457+
}
458+
441459
/**
442460
* Return the appropriate pagination handler.
443461
*

system/Controller.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,13 @@ protected function forceHTTPS(int $duration = 31_536_000)
104104
}
105105

106106
/**
107-
* Provides a simple way to tie into the main CodeIgniter class and
108-
* tell it how long to cache the current page for.
107+
* How long to cache the current page for.
108+
*
109+
* @params int $time time to live in seconds.
109110
*/
110111
protected function cachePage(int $time)
111112
{
112-
CodeIgniter::cache($time);
113+
Services::responsecache()->setTtl($time);
113114
}
114115

115116
/**

0 commit comments

Comments
 (0)