diff --git a/deptrac.yaml b/deptrac.yaml index bf39d4f54734..d2db0605103b 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -222,6 +222,10 @@ parameters: - Cache skip_violations: # Individual class exemptions + CodeIgniter\Cache\ResponseCache: + - CodeIgniter\HTTP\CLIRequest + - CodeIgniter\HTTP\IncomingRequest + - CodeIgniter\HTTP\ResponseInterface CodeIgniter\Entity\Cast\URICast: - CodeIgniter\HTTP\URI CodeIgniter\Log\Handlers\ChromeLoggerHandler: diff --git a/phpstan-baseline.neon.dist b/phpstan-baseline.neon.dist index 2d60b2b599d2..e1ecece1f5f0 100644 --- a/phpstan-baseline.neon.dist +++ b/phpstan-baseline.neon.dist @@ -25,16 +25,6 @@ parameters: count: 1 path: system/Cache/Handlers/RedisHandler.php - - - message: "#^Call to an undefined method CodeIgniter\\\\HTTP\\\\Request\\:\\:getPost\\(\\)\\.$#" - count: 1 - path: system/CodeIgniter.php - - - - message: "#^Call to an undefined method CodeIgniter\\\\HTTP\\\\Request\\:\\:setLocale\\(\\)\\.$#" - count: 1 - path: system/CodeIgniter.php - - message: "#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$db \\(CodeIgniter\\\\Database\\\\BaseConnection\\) in empty\\(\\) is not falsy\\.$#" count: 1 diff --git a/system/Cache/ResponseCache.php b/system/Cache/ResponseCache.php new file mode 100644 index 000000000000..44da42947633 --- /dev/null +++ b/system/Cache/ResponseCache.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Cache; + +use CodeIgniter\HTTP\CLIRequest; +use CodeIgniter\HTTP\IncomingRequest; +use CodeIgniter\HTTP\ResponseInterface; +use Config\Cache as CacheConfig; +use Exception; + +/** + * Web Page Caching + */ +final class ResponseCache +{ + /** + * Whether to take the URL query string into consideration when generating + * output cache files. Valid options are: + * + * false = Disabled + * true = Enabled, take all query parameters into account. + * Please be aware that this may result in numerous cache + * files generated for the same page over and over again. + * array('q') = Enabled, but only take into account the specified list + * of query parameters. + * + * @var bool|string[] + */ + private $cacheQueryString = false; + + /** + * Cache time to live. + * + * @var int seconds + */ + private int $ttl = 0; + + private CacheInterface $cache; + + public function __construct(CacheConfig $config, CacheInterface $cache) + { + $this->cacheQueryString = $config->cacheQueryString; + $this->cache = $cache; + } + + /** + * @return $this + */ + public function setTtl(int $ttl) + { + $this->ttl = $ttl; + + return $this; + } + + /** + * Generates the cache key to use from the current request. + * + * @param CLIRequest|IncomingRequest $request + * + * @internal for testing purposes only + */ + public function generateCacheKey($request): string + { + if ($request instanceof CLIRequest) { + return md5($request->getPath()); + } + + $uri = clone $request->getUri(); + + $query = $this->cacheQueryString + ? $uri->getQuery(is_array($this->cacheQueryString) ? ['only' => $this->cacheQueryString] : []) + : ''; + + return md5($uri->setFragment('')->setQuery($query)); + } + + /** + * Caches the response. + * + * @param CLIRequest|IncomingRequest $request + */ + public function make($request, ResponseInterface $response): bool + { + if ($this->ttl === 0) { + return true; + } + + $headers = []; + + foreach ($response->headers() as $header) { + $headers[$header->getName()] = $header->getValueLine(); + } + + return $this->cache->save( + $this->generateCacheKey($request), + serialize(['headers' => $headers, 'output' => $response->getBody()]), + $this->ttl + ); + } + + /** + * Gets the cached response for the request. + * + * @param CLIRequest|IncomingRequest $request + */ + public function get($request, ResponseInterface $response): ?ResponseInterface + { + if ($cachedResponse = $this->cache->get($this->generateCacheKey($request))) { + $cachedResponse = unserialize($cachedResponse); + + if ( + ! is_array($cachedResponse) + || ! isset($cachedResponse['output']) + || ! isset($cachedResponse['headers']) + ) { + throw new Exception('Error unserializing page cache'); + } + + $headers = $cachedResponse['headers']; + $output = $cachedResponse['output']; + + // Clear all default headers + foreach (array_keys($response->headers()) as $key) { + $response->removeHeader($key); + } + + // Set cached headers + foreach ($headers as $name => $value) { + $response->setHeader($name, $value); + } + + $response->setBody($output); + + return $response; + } + + return null; + } +} diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 06b0c1f5c500..2ff8da02d456 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -12,6 +12,7 @@ namespace CodeIgniter; use Closure; +use CodeIgniter\Cache\ResponseCache; use CodeIgniter\Debug\Timer; use CodeIgniter\Events\Events; use CodeIgniter\Exceptions\FrameworkException; @@ -84,7 +85,7 @@ class CodeIgniter /** * Current request. * - * @var CLIRequest|IncomingRequest|Request|null + * @var CLIRequest|IncomingRequest|null */ protected $request; @@ -127,6 +128,8 @@ class CodeIgniter * Cache expiration time * * @var int seconds + * + * @deprecated 4.4.0 Moved to ResponseCache::$ttl. No longer used. */ protected static $cacheTTL = 0; @@ -175,6 +178,11 @@ class CodeIgniter */ protected int $bufferLevel; + /** + * Web Page Caching + */ + protected ResponseCache $pageCache; + /** * Constructor. */ @@ -182,6 +190,8 @@ public function __construct(App $config) { $this->startTime = microtime(true); $this->config = $config; + + $this->pageCache = Services::responsecache(); } /** @@ -330,7 +340,7 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon ); } - static::$cacheTTL = 0; + $this->pageCache->setTtl(0); $this->bufferLevel = ob_get_level(); $this->startBenchmark(); @@ -463,7 +473,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache return $possibleResponse; } - if ($possibleResponse instanceof Request) { + if ($possibleResponse instanceof IncomingRequest || $possibleResponse instanceof CLIRequest) { $this->request = $possibleResponse; } } @@ -517,9 +527,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // 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. - if (static::$cacheTTL > 0) { - $this->cachePage($cacheConfig); - } + $this->pageCache->make($this->request, $this->response); // Update the performance metrics $body = $this->response->getBody(); @@ -603,9 +611,11 @@ protected function startBenchmark() * Sets a Request object to be used for this request. * Used when running certain tests. * + * @param CLIRequest|IncomingRequest $request + * * @return $this */ - public function setRequest(Request $request) + public function setRequest($request) { $this->request = $request; @@ -674,27 +684,11 @@ protected function forceSecureAccess($duration = 31_536_000) */ public function displayCache(Cache $config) { - if ($cachedResponse = cache()->get($this->generateCacheName($config))) { - $cachedResponse = unserialize($cachedResponse); - if (! is_array($cachedResponse) || ! isset($cachedResponse['output']) || ! isset($cachedResponse['headers'])) { - throw new Exception('Error unserializing page cache'); - } - - $headers = $cachedResponse['headers']; - $output = $cachedResponse['output']; - - // Clear all default headers - foreach (array_keys($this->response->headers()) as $key) { - $this->response->removeHeader($key); - } - - // Set cached headers - foreach ($headers as $name => $value) { - $this->response->setHeader($name, $value); - } + if ($cachedResponse = $this->pageCache->get($this->request, $this->response)) { + $this->response = $cachedResponse; $this->totalTime = $this->benchmark->getElapsedTime('total_execution'); - $output = $this->displayPerformanceMetrics($output); + $output = $this->displayPerformanceMetrics($cachedResponse->getBody()); $this->response->setBody($output); return $this->response; @@ -705,6 +699,8 @@ public function displayCache(Cache $config) /** * Tells the app that the final output should be cached. + * + * @deprecated 4.4.0 Moved to ResponseCache::setTtl(). to No longer used. */ public static function cache(int $time) { @@ -716,6 +712,8 @@ public static function cache(int $time) * full-page caching for very high performance. * * @return bool + * + * @deprecated 4.4.0 No longer used. */ public function cachePage(Cache $config) { @@ -741,6 +739,8 @@ public function getPerformanceStats(): array /** * Generates the cache name to use for our full-page caching. + * + * @deprecated 4.4.0 No longer used. */ protected function generateCacheName(Cache $config): string { diff --git a/system/Config/BaseService.php b/system/Config/BaseService.php index d517691b0c1f..133805a6a585 100644 --- a/system/Config/BaseService.php +++ b/system/Config/BaseService.php @@ -14,6 +14,7 @@ use CodeIgniter\Autoloader\Autoloader; use CodeIgniter\Autoloader\FileLocator; use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Cache\ResponseCache; use CodeIgniter\CLI\Commands; use CodeIgniter\CodeIgniter; use CodeIgniter\Database\ConnectionInterface; @@ -117,6 +118,7 @@ * @method static View renderer($viewPath = null, ConfigView $config = null, $getShared = true) * @method static IncomingRequest|CLIRequest request(App $config = null, $getShared = true) * @method static ResponseInterface response(App $config = null, $getShared = true) + * @method static ResponseCache responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true) * @method static Router router(RouteCollectionInterface $routes = null, Request $request = null, $getShared = true) * @method static RouteCollection routes($getShared = true) * @method static Security security(App $config = null, $getShared = true) diff --git a/system/Config/Services.php b/system/Config/Services.php index 5e0c4e70b96e..c8ded8034257 100644 --- a/system/Config/Services.php +++ b/system/Config/Services.php @@ -13,6 +13,7 @@ use CodeIgniter\Cache\CacheFactory; use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Cache\ResponseCache; use CodeIgniter\CLI\Commands; use CodeIgniter\CodeIgniter; use CodeIgniter\Database\ConnectionInterface; @@ -438,6 +439,23 @@ public static function negotiator(?RequestInterface $request = null, bool $getSh return new Negotiate($request); } + /** + * Return the ResponseCache. + * + * @return ResponseCache + */ + public static function responsecache(?Cache $config = null, ?CacheInterface $cache = null, bool $getShared = true) + { + if ($getShared) { + return static::getSharedInstance('responsecache', $config, $cache); + } + + $config ??= config(Cache::class); + $cache ??= AppServices::cache(); + + return new ResponseCache($config, $cache); + } + /** * Return the appropriate pagination handler. * diff --git a/system/Controller.php b/system/Controller.php index 64de91ab2938..e808e0b8c20f 100644 --- a/system/Controller.php +++ b/system/Controller.php @@ -104,12 +104,13 @@ protected function forceHTTPS(int $duration = 31_536_000) } /** - * Provides a simple way to tie into the main CodeIgniter class and - * tell it how long to cache the current page for. + * How long to cache the current page for. + * + * @params int $time time to live in seconds. */ protected function cachePage(int $time) { - CodeIgniter::cache($time); + Services::responsecache()->setTtl($time); } /** diff --git a/system/Test/FeatureTestCase.php b/system/Test/FeatureTestCase.php index 7163d3e85717..371e302c59ae 100644 --- a/system/Test/FeatureTestCase.php +++ b/system/Test/FeatureTestCase.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Test; use CodeIgniter\Events\Events; +use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\Exceptions\RedirectException; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\Request; @@ -328,11 +329,13 @@ protected function setupHeaders(IncomingRequest $request) * * Always populate the GET vars based on the URI. * - * @return Request + * @param CLIRequest|IncomingRequest $request + * + * @return CLIRequest|IncomingRequest * * @throws ReflectionException */ - protected function populateGlobals(string $method, Request $request, ?array $params = null) + protected function populateGlobals(string $method, $request, ?array $params = null) { // $params should set the query vars if present, // otherwise set it from the URL. @@ -357,10 +360,13 @@ protected function populateGlobals(string $method, Request $request, ?array $par * This allows the body to be formatted in a way that the controller is going to * expect as in the case of testing a JSON or XML API. * - * @param array|null $params The parameters to be formatted and put in the body. If this is empty, it will get the - * what has been loaded into the request global of the request class. + * @param CLIRequest|IncomingRequest $request + * @param array|null $params The parameters to be formatted and put in the body. If this is empty, it will get the + * what has been loaded into the request global of the request class. + * + * @return CLIRequest|IncomingRequest */ - protected function setRequestBody(Request $request, ?array $params = null): Request + protected function setRequestBody($request, ?array $params = null) { if (isset($this->requestBody) && $this->requestBody !== '') { $request->setBody($this->requestBody); diff --git a/tests/system/Cache/ResponseCacheTest.php b/tests/system/Cache/ResponseCacheTest.php new file mode 100644 index 000000000000..a1079e63a463 --- /dev/null +++ b/tests/system/Cache/ResponseCacheTest.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Cache; + +use CodeIgniter\HTTP\CLIRequest; +use CodeIgniter\HTTP\IncomingRequest; +use CodeIgniter\HTTP\Response; +use CodeIgniter\HTTP\ResponseInterface; +use CodeIgniter\HTTP\URI; +use CodeIgniter\HTTP\UserAgent; +use CodeIgniter\Test\CIUnitTestCase; +use Config\App as AppConfig; +use Config\Cache as CacheConfig; +use ErrorException; +use Exception; + +/** + * @backupGlobals enabled + * + * @internal + * + * @group Others + */ +final class ResponseCacheTest extends CIUnitTestCase +{ + private AppConfig $appConfig; + + protected function setUp(): void + { + parent::setUp(); + + $this->appConfig = new AppConfig(); + } + + private function createIncomingRequest( + string $uri = '', + array $query = [], + ?AppConfig $appConfig = null + ): IncomingRequest { + $_POST = $_GET = $_SERVER = $_REQUEST = $_ENV = $_COOKIE = $_SESSION = []; + + $_SERVER['REQUEST_URI'] = '/' . $uri . ($query ? '?' . http_build_query($query) : ''); + $_SERVER['SCRIPT_NAME'] = '/index.php'; + + $appConfig ??= $this->appConfig; + + $siteUri = new URI($appConfig->baseURL . $uri); + if ($query !== []) { + $_GET = $_REQUEST = $query; + $siteUri->setQueryArray($query); + } + + return new IncomingRequest( + $appConfig, + $siteUri, + null, + new UserAgent() + ); + } + + /** + * @phpstan-param list $params + */ + private function createCLIRequest(array $params = [], ?AppConfig $appConfig = null): CLIRequest + { + $_POST = $_GET = $_SERVER = $_REQUEST = $_ENV = $_COOKIE = $_SESSION = []; + + $_SERVER['argv'] = ['public/index.php', ...$params]; + $_SERVER['SCRIPT_NAME'] = 'public/index.php'; + + $appConfig ??= $this->appConfig; + + return new CLIRequest($appConfig); + } + + private function createResponseCache(?CacheConfig $cacheConfig = null): ResponseCache + { + $cache = mock(CacheFactory::class); + + $cacheConfig ??= new CacheConfig(); + + return (new ResponseCache($cacheConfig, $cache))->setTtl(300); + } + + public function testCachePageIncomingRequest() + { + $pageCache = $this->createResponseCache(); + + $request = $this->createIncomingRequest('foo/bar'); + + $response = new Response($this->appConfig); + $response->setHeader('ETag', 'abcd1234'); + $response->setBody('The response body.'); + + $return = $pageCache->make($request, $response); + + $this->assertTrue($return); + + // Check cache with a request with the same URI path. + $request = $this->createIncomingRequest('foo/bar'); + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertInstanceOf(ResponseInterface::class, $cachedResponse); + $this->assertSame('The response body.', $cachedResponse->getBody()); + $this->assertSame('abcd1234', $cachedResponse->getHeaderLine('ETag')); + + // Check cache with a request with the same URI path and different query string. + $request = $this->createIncomingRequest('foo/bar', ['foo' => 'bar', 'bar' => 'baz']); + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertInstanceOf(ResponseInterface::class, $cachedResponse); + $this->assertSame('The response body.', $cachedResponse->getBody()); + $this->assertSame('abcd1234', $cachedResponse->getHeaderLine('ETag')); + + // Check cache with another request with the different URI path. + $request = $this->createIncomingRequest('another'); + + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertNull($cachedResponse); + } + + public function testCachePageIncomingRequestWithCacheQueryString() + { + $cacheConfig = new CacheConfig(); + $cacheConfig->cacheQueryString = true; + $pageCache = $this->createResponseCache($cacheConfig); + + $request = $this->createIncomingRequest('foo/bar', ['foo' => 'bar', 'bar' => 'baz']); + + $response = new Response($this->appConfig); + $response->setHeader('ETag', 'abcd1234'); + $response->setBody('The response body.'); + + $return = $pageCache->make($request, $response); + + $this->assertTrue($return); + + // Check cache with a request with the same URI path and same query string. + $this->createIncomingRequest('foo/bar', ['foo' => 'bar', 'bar' => 'baz']); + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertInstanceOf(ResponseInterface::class, $cachedResponse); + $this->assertSame('The response body.', $cachedResponse->getBody()); + $this->assertSame('abcd1234', $cachedResponse->getHeaderLine('ETag')); + + // Check cache with a request with the same URI path and different query string. + $request = $this->createIncomingRequest('foo/bar', ['xfoo' => 'bar', 'bar' => 'baz']); + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertNull($cachedResponse); + + // Check cache with another request with the different URI path. + $request = $this->createIncomingRequest('another'); + + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertNull($cachedResponse); + } + + public function testCachePageCLIRequest() + { + $pageCache = $this->createResponseCache(); + + $request = $this->createCLIRequest(['foo', 'bar']); + + $response = new Response($this->appConfig); + $response->setBody('The response body.'); + + $return = $pageCache->make($request, $response); + + $this->assertTrue($return); + + // Check cache with a request with the same params. + $request = $this->createCLIRequest(['foo', 'bar']); + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertInstanceOf(ResponseInterface::class, $cachedResponse); + $this->assertSame('The response body.', $cachedResponse->getBody()); + + // Check cache with another request with the different params. + $request = $this->createCLIRequest(['baz']); + + $cachedResponse = $pageCache->get($request, new Response($this->appConfig)); + + $this->assertNull($cachedResponse); + } + + public function testUnserializeError() + { + $this->expectException(ErrorException::class); + $this->expectExceptionMessage('unserialize(): Error at offset 0 of 12 bytes'); + + $cache = mock(CacheFactory::class); + $cacheConfig = new CacheConfig(); + $pageCache = new ResponseCache($cacheConfig, $cache); + + $request = $this->createIncomingRequest('foo/bar'); + + $response = new Response($this->appConfig); + $response->setHeader('ETag', 'abcd1234'); + $response->setBody('The response body.'); + + $pageCache->make($request, $response); + + $cacheKey = $pageCache->generateCacheKey($request); + + // Save invalid data. + $cache->save($cacheKey, 'Invalid data'); + + // Check cache with a request with the same URI path. + $pageCache->get($request, new Response($this->appConfig)); + } + + public function testInvalidCacheError() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Error unserializing page cache'); + + $cache = mock(CacheFactory::class); + $cacheConfig = new CacheConfig(); + $pageCache = new ResponseCache($cacheConfig, $cache); + + $request = $this->createIncomingRequest('foo/bar'); + + $response = new Response($this->appConfig); + $response->setHeader('ETag', 'abcd1234'); + $response->setBody('The response body.'); + + $pageCache->make($request, $response); + + $cacheKey = $pageCache->generateCacheKey($request); + + // Save invalid data. + $cache->save($cacheKey, serialize(['a' => '1'])); + + // Check cache with a request with the same URI path. + $pageCache->get($request, new Response($this->appConfig)); + } +} diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index b2430a26a7c8..584310ba08c6 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -96,6 +96,7 @@ public function testRunClosureRoute() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -193,6 +194,7 @@ public function testControllersCanReturnString() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -216,6 +218,7 @@ public function testControllersCanReturnResponseObject() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -244,6 +247,7 @@ public function testControllersCanReturnDownloadResponseObject() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -268,6 +272,7 @@ public function testRunExecuteFilterByClassName() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -298,6 +303,7 @@ public function testRegisterSameFilterTwiceWithDifferentArgument() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $routes = Services::routes(); $routes->add( @@ -331,6 +337,7 @@ public function testDisableControllerFilters() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/pages/about'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -452,6 +459,7 @@ public function testRunRedirectionWithNamed() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/example'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -475,6 +483,7 @@ public function testRunRedirectionWithURI() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/example'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -501,6 +510,7 @@ public function testRunRedirectionWithGET() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/example'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -527,6 +537,7 @@ public function testRunRedirectionWithGETAndHTTPCode301() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/example'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -551,6 +562,7 @@ public function testRunRedirectionWithPOSTAndHTTPCode301() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/example'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'POST'; @@ -615,6 +627,7 @@ public function testNotStoresPreviousURL() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/example'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'GET'; @@ -638,6 +651,7 @@ public function testNotStoresPreviousURLByCheckingContentType() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/image'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; // Inject mock router. $routes = Services::routes(); @@ -679,6 +693,7 @@ public function testRunCLIRoute() $_SERVER['argc'] = 2; $_SERVER['REQUEST_URI'] = '/cli'; + $_SERVER['SCRIPT_NAME'] = 'public/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'CLI'; @@ -698,6 +713,7 @@ public function testSpoofRequestMethodCanUsePUT() $_SERVER['argc'] = 1; $_SERVER['REQUEST_URI'] = '/'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'POST'; @@ -722,6 +738,7 @@ public function testSpoofRequestMethodCannotUseGET() $_SERVER['argc'] = 1; $_SERVER['REQUEST_URI'] = '/'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $_SERVER['REQUEST_METHOD'] = 'POST'; @@ -754,6 +771,7 @@ public function testPageCacheSendSecureHeaders() command('cache:clear'); $_SERVER['REQUEST_URI'] = '/test'; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $routes = Services::routes(); $routes->add('test', static function () { @@ -832,13 +850,14 @@ public function testPageCacheWithCacheQueryString( foreach ($testingUrls as $testingUrl) { $this->resetServices(); $_SERVER['REQUEST_URI'] = '/' . $testingUrl; + $_SERVER['SCRIPT_NAME'] = '/index.php'; $this->codeigniter = new MockCodeIgniter(new App()); $routes = Services::routes(true); $routePath = explode('?', $testingUrl)[0]; $string = 'This is a test page, to check cache configuration'; $routes->add($routePath, static function () use ($string) { - CodeIgniter::cache(60); + Services::responsecache()->setTtl(60); $response = Services::response(); return $response->setBody($string); diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index c686450ef864..8bf257532fb4 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -52,15 +52,24 @@ Parameter Type Changes - **Services:** The first parameter of ``Services::security()`` has been changed from ``Config\App`` to ``Config\Security``. -- **Session:** The second parameter of ``Session::__construct()`` has been - changed from ``Config\App`` to ``Config\Session``. -- **Session:** The first parameter of ``__construct()`` in ``BaseHandler``, - ``DatabaseHandler``, ``FileHandler``, ``MemcachedHandler``, and ``RedisHandler`` - has been changed from ``Config\App`` to ``Config\Session``. +- **Session:** + - The second parameter of ``Session::__construct()`` has been changed from + ``Config\App`` to ``Config\Session``. + - The first parameter of ``__construct()`` in the database's ``BaseHandler``, + ``DatabaseHandler``, ``FileHandler``, ``MemcachedHandler``, and ``RedisHandler`` + has been changed from ``Config\App`` to ``Config\Session``. - **Security:** The first parameter of ``Security::__construct()`` has been changed from ``Config\App`` to ``Config\Security``. - **Validation:** The method signature of ``Validation::check()`` has been changed. The ``string`` typehint on the ``$rule`` parameter was removed. +- **CodeIgniter:** The method signature of ``CodeIgniter::setRequest()`` has been + changed. The ``Request`` typehint on the ``$request`` parameter was removed. +- **FeatureTestCase:** + - The method signature of ``FeatureTestCase::populateGlobals()`` has been + changed. The ``Request`` typehint on the ``$request`` parameter was removed. + - The method signature of ``FeatureTestCase::setRequestBody()`` has been + changed. The ``Request`` typehint on the ``$request`` parameter and the + return type ``Request`` were removed. Added Parameters ---------------- @@ -179,7 +188,12 @@ Deprecations are deprecated. Because these methods have been moved to ``BaseExceptionHandler`` or ``ExceptionHandler``. - **Autoloader:** ``Autoloader::sanitizeFilename()`` is deprecated. -- **CodeIgniter:** ``CodeIgniter::$returnResponse`` property is deprecated. No longer used. +- **CodeIgniter:** + - ``CodeIgniter::$returnResponse`` property is deprecated. No longer used. + - ``CodeIgniter::$cacheTTL`` property is deprecated. No longer used. Use ``ResponseCache`` instead. + - ``CodeIgniter::cache()`` method is deprecated. No longer used. Use ``ResponseCache`` instead. + - ``CodeIgniter::cachePage()`` method is deprecated. No longer used. Use ``ResponseCache`` instead. + - ``CodeIgniter::generateCacheName()`` method is deprecated. No longer used. Use ``ResponseCache`` instead. - **RedirectException:** ``\CodeIgniter\Router\Exceptions\RedirectException`` is deprecated. Use ``\CodeIgniter\HTTP\Exceptions\RedirectException`` instead. - **Session:** The property ``$sessionDriverName``, ``$sessionCookieName``, ``$sessionExpiration``, ``$sessionSavePath``, ``$sessionMatchIP``,