diff --git a/system/CodeIgniter.php b/system/CodeIgniter.php index 79bc192c094e..0c955556685e 100644 --- a/system/CodeIgniter.php +++ b/system/CodeIgniter.php @@ -138,6 +138,8 @@ class CodeIgniter * to keep from setting headers/cookies/etc * * @var bool + * + * @deprecated No longer used. */ protected $useSafeOutput = false; @@ -340,7 +342,7 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon return $response; } - $this->response->pretend($this->useSafeOutput)->send(); + $this->response->send(); $this->callExit(EXIT_SUCCESS); return; @@ -371,6 +373,8 @@ public function run(?RouteCollectionInterface $routes = null, bool $returnRespon * not complain when ini_set() function is used. * * @return $this + * + * @deprecated No longer used. */ public function useSafeOutput(bool $safe = true) { @@ -433,7 +437,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache // If a ResponseInterface instance is returned then send it back to the client and stop if ($possibleResponse instanceof ResponseInterface) { - return $returnResponse ? $possibleResponse : $possibleResponse->pretend($this->useSafeOutput)->send(); + return $returnResponse ? $possibleResponse : $possibleResponse->send(); } if ($possibleResponse instanceof Request) { @@ -1057,7 +1061,7 @@ public function spoofRequestMethod() */ protected function sendResponse() { - $this->response->pretend($this->useSafeOutput)->send(); + $this->response->send(); } /** diff --git a/system/HTTP/MessageInterface.php b/system/HTTP/MessageInterface.php index c9a65d4db372..284b876babde 100644 --- a/system/HTTP/MessageInterface.php +++ b/system/HTTP/MessageInterface.php @@ -27,6 +27,15 @@ interface MessageInterface */ public function setBody($data); + /** + * Gets the body of the message. + * + * @return string|null + * + * @TODO Incompatible return type with PSR-7 + */ + public function getBody(); + /** * Appends data to the body of the current message. * @@ -48,6 +57,17 @@ public function populateHeaders(): void; */ public function headers(): array; + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $name Case-insensitive header field name. + * + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader(string $name): bool; + /** * Returns a single Header object. If multiple headers with the same * name exist, then will return an array of header objects. @@ -58,6 +78,19 @@ public function headers(): array; */ public function header($name); + /** + * Retrieves a comma-separated string of the values for a single header. + * + * This method returns all of the header values of the given + * case-insensitive header name as a string concatenated together using + * a comma. + * + * NOTE: Not all header values may be appropriately represented using + * comma concatenation. For such headers, use getHeader() instead + * and supply your own delimiter when concatenating. + */ + public function getHeaderLine(string $name): string; + /** * Sets a header and it's value. * diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index c0f3a2e8120a..56ccce087e67 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -188,10 +188,13 @@ public function __construct($config) /** * Turns "pretend" mode on or off to aid in testing. + * * Note that this is not a part of the interface so * should not be relied on outside of internal testing. * * @return $this + * + * @testTag only available to test code */ public function pretend(bool $pretend = true) { diff --git a/system/HTTP/ResponseInterface.php b/system/HTTP/ResponseInterface.php index e8f9fd3fcf3b..f6b00a5044db 100644 --- a/system/HTTP/ResponseInterface.php +++ b/system/HTTP/ResponseInterface.php @@ -28,10 +28,8 @@ * - Status code and reason phrase * - Headers * - Message body - * - * @mixin RedirectResponse */ -interface ResponseInterface +interface ResponseInterface extends MessageInterface { /** * Constants for status codes. @@ -130,7 +128,7 @@ public function getStatusCode(): int; * provided status code; if none is provided, will * default to the IANA name. * - * @return self + * @return $this * * @throws InvalidArgumentException For invalid status code arguments. */ @@ -153,7 +151,7 @@ public function getReason(): string; /** * Sets the date header * - * @return ResponseInterface + * @return $this */ public function setDate(DateTime $date); @@ -164,6 +162,8 @@ public function setDate(DateTime $date); * preferably, an instance of DateTime. * * @param DateTime|string $date + * + * @return $this */ public function setLastModified($date); @@ -172,7 +172,7 @@ public function setLastModified($date); * * @see http://tools.ietf.org/html/rfc5988 * - * @return Response + * @return $this * * @todo Recommend moving to Pager */ @@ -182,7 +182,7 @@ public function setLink(PagerInterface $pager); * Sets the Content Type header for this response with the mime type * and, optionally, the charset. * - * @return ResponseInterface + * @return $this */ public function setContentType(string $mime, string $charset = 'UTF-8'); @@ -202,7 +202,7 @@ public function setJSON($body, bool $unencoded = false); /** * Returns the current body, converted to JSON is it isn't already. * - * @return mixed|string + * @return bool|string|null * * @throws InvalidArgumentException If the body property is not array. */ @@ -220,7 +220,7 @@ public function setXML($body); /** * Retrieves the current body into XML and returns it. * - * @return mixed|string + * @return bool|string|null * * @throws InvalidArgumentException If the body property is not array. */ @@ -235,6 +235,8 @@ public function getXML(); /** * Sets the appropriate headers to ensure this response * is not cached by the browsers. + * + * @return $this */ public function noCache(); @@ -262,7 +264,7 @@ public function noCache(); * - proxy-revalidate * - no-transform * - * @return ResponseInterface + * @return $this */ public function setCache(array $options = []); @@ -273,21 +275,21 @@ public function setCache(array $options = []); /** * Sends the output to the browser. * - * @return ResponseInterface + * @return $this */ public function send(); /** * Sends the headers of this HTTP request to the browser. * - * @return Response + * @return $this */ public function sendHeaders(); /** * Sends the Body of the message to the browser. * - * @return Response + * @return $this */ public function sendBody(); diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 10a10f817bb4..b58fcdae8e53 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -252,7 +252,7 @@ public function setJSON($body, bool $unencoded = false) /** * Returns the current body, converted to JSON is it isn't already. * - * @return mixed|string + * @return bool|string|null * * @throws InvalidArgumentException If the body property is not array. */ @@ -284,7 +284,7 @@ public function setXML($body) /** * Retrieves the current body into XML and returns it. * - * @return mixed|string + * @return bool|string|null * * @throws InvalidArgumentException If the body property is not array. */ @@ -430,7 +430,7 @@ public function setLastModified($date) /** * Sends the output to the browser. * - * @return Response + * @return $this */ public function send() { diff --git a/tests/system/CodeIgniterTest.php b/tests/system/CodeIgniterTest.php index 4b1938aa0293..eb685eeedb16 100644 --- a/tests/system/CodeIgniterTest.php +++ b/tests/system/CodeIgniterTest.php @@ -41,6 +41,9 @@ protected function setUp(): void $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1'; $this->codeigniter = new MockCodeIgniter(new App()); + + $response = Services::response(); + $response->pretend(); } protected function tearDown(): void @@ -60,7 +63,7 @@ public function testRunEmptyDefaultRoute() $_SERVER['argc'] = 1; ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('Welcome to CodeIgniter', $output); @@ -82,7 +85,7 @@ public function testRunClosureRoute() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('You want to see "about" page.', $output); @@ -101,7 +104,7 @@ public function testRun404Override() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($routes); + $this->codeigniter->run($routes); $output = ob_get_clean(); $this->assertStringContainsString('Hello', $output); @@ -120,7 +123,7 @@ public function testRun404OverrideControllerReturnsResponse() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($routes); + $this->codeigniter->run($routes); $output = ob_get_clean(); $this->assertStringContainsString('Oops', $output); @@ -141,7 +144,7 @@ public function testRun404OverrideByClosure() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($routes); + $this->codeigniter->run($routes); $output = ob_get_clean(); $this->assertStringContainsString('404 Override by Closure.', $output); @@ -161,7 +164,7 @@ public function testControllersCanReturnString() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('You want to see "about" page.', $output); @@ -186,7 +189,7 @@ public function testControllersCanReturnResponseObject() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString("You want to see 'about' page.", $output); @@ -213,7 +216,7 @@ public function testControllersCanReturnDownloadResponseObject() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertSame('some text', $output); @@ -234,7 +237,7 @@ public function testControllersRunFilterByClassName() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('http://hellowworld.com', $output); @@ -262,7 +265,7 @@ public function testRoutesIsEmpty() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('Welcome to CodeIgniter', $output); @@ -276,7 +279,7 @@ public function testTransfersCorrectHTTPVersion() $_SERVER['SERVER_PROTOCOL'] = 'HTTP/2.0'; ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $response = $this->getPrivateProperty($this->codeigniter, 'response'); @@ -291,7 +294,7 @@ public function testIgnoringErrorSuppressedByAt() ob_start(); @unlink('inexistent-file'); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('Welcome to CodeIgniter', $output); @@ -316,7 +319,7 @@ public function testRunForceSecure() $this->assertNull($response->header('Location')); ob_start(); - $codeigniter->useSafeOutput(true)->run(); + $codeigniter->run(); ob_get_clean(); $this->assertSame('https://example.com/', $response->header('Location')->getValue()); @@ -339,7 +342,7 @@ public function testRunRedirectionWithNamed() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $response = $this->getPrivateProperty($this->codeigniter, 'response'); $this->assertSame('http://example.com/pages/named', $response->header('Location')->getValue()); @@ -362,7 +365,7 @@ public function testRunRedirectionWithURI() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $response = $this->getPrivateProperty($this->codeigniter, 'response'); $this->assertSame('http://example.com/pages/uri', $response->header('Location')->getValue()); @@ -386,7 +389,7 @@ public function testRunRedirectionWithURINotSet() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $response = $this->getPrivateProperty($this->codeigniter, 'response'); $this->assertSame('http://example.com/pages/notset', $response->header('Location')->getValue()); @@ -409,7 +412,7 @@ public function testRunRedirectionWithHTTPCode303() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $response = $this->getPrivateProperty($this->codeigniter, 'response'); @@ -426,7 +429,7 @@ public function testStoresPreviousURL() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $this->assertArrayHasKey('_ci_previous_url', $_SESSION); @@ -450,7 +453,7 @@ public function testNotStoresPreviousURL() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $this->assertArrayNotHasKey('_ci_previous_url', $_SESSION); @@ -474,7 +477,7 @@ public function testNotStoresPreviousURLByCheckingContentType() Services::injectMock('router', $router); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $this->assertArrayNotHasKey('_ci_previous_url', $_SESSION); @@ -491,7 +494,7 @@ public function testRunDefaultRoute() $_SERVER['argc'] = 2; ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('Welcome to CodeIgniter', $output); @@ -510,7 +513,7 @@ public function testRunCLIRoute() $routes->cli('cli', '\Tests\Support\Controllers\Popcorn::index'); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('Method Not Allowed', $output); @@ -534,7 +537,7 @@ public function testSpoofRequestMethodCanUsePUT() $routes->put('/', 'Home::index'); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $this->assertSame('put', Services::request()->getMethod()); @@ -558,7 +561,7 @@ public function testSpoofRequestMethodCannotUseGET() $routes->get('/', 'Home::index'); ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); ob_get_clean(); $this->assertSame('post', Services::request()->getMethod()); @@ -598,7 +601,7 @@ public function testPageCacheSendSecureHeaders() // The first response to be cached. ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('This is a test page', $output); @@ -608,7 +611,7 @@ public function testPageCacheSendSecureHeaders() // The second response from the Page cache. ob_start(); - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $output = ob_get_clean(); $this->assertStringContainsString('This is a test page', $output); @@ -666,7 +669,7 @@ public function testPageCacheWithCacheQueryString($cacheQueryStringValue, int $e Services::injectMock('router', $router); // Cache the page output using default caching function and $cacheConfig with value from the data provider - $this->codeigniter->useSafeOutput(true)->run(); + $this->codeigniter->run(); $this->codeigniter->cachePage($cacheConfig); // Cache the page using our own $cacheConfig confugration } diff --git a/tests/system/RESTful/ResourceControllerTest.php b/tests/system/RESTful/ResourceControllerTest.php index b65a7ddf38c4..fe1d0ac6f781 100644 --- a/tests/system/RESTful/ResourceControllerTest.php +++ b/tests/system/RESTful/ResourceControllerTest.php @@ -71,6 +71,9 @@ private function createCodeigniter(): void $config = new App(); $this->codeigniter = new MockCodeIgniter($config); + + $response = Services::response(); + $response->pretend(); } protected function tearDown(): void @@ -96,7 +99,7 @@ public function testResourceGet() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -118,7 +121,7 @@ public function testResourceGetNew() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -141,7 +144,7 @@ public function testResourceGetEdit() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -163,7 +166,7 @@ public function testResourceGetOne() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -184,7 +187,7 @@ public function testResourcePost() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -206,7 +209,7 @@ public function testResourcePatch() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -228,7 +231,7 @@ public function testResourcePut() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; @@ -250,7 +253,7 @@ public function testResourceDelete() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $error = json_decode($output)->messages->error; diff --git a/tests/system/RESTful/ResourcePresenterTest.php b/tests/system/RESTful/ResourcePresenterTest.php index c36f6340c861..a430b527a580 100644 --- a/tests/system/RESTful/ResourcePresenterTest.php +++ b/tests/system/RESTful/ResourcePresenterTest.php @@ -90,7 +90,7 @@ public function testResourceGet() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertSame(lang('RESTful.notImplemented', ['index']), $output); @@ -112,7 +112,7 @@ public function testResourceShow() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['show']), $output); @@ -133,7 +133,7 @@ public function testResourceNew() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['new']), $output); @@ -154,7 +154,7 @@ public function testResourceCreate() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['create']), $output); @@ -176,7 +176,7 @@ public function testResourceRemove() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['remove']), $output); @@ -198,7 +198,7 @@ public function testResourceDelete() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['delete']), $output); @@ -221,7 +221,7 @@ public function testResourceEdit() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['edit']), $output); @@ -243,7 +243,7 @@ public function testResourceUpdate() $this->createCodeigniter(); ob_start(); - $this->codeigniter->useSafeOutput(true)->run($this->routes); + $this->codeigniter->run($this->routes); $output = ob_get_clean(); $this->assertStringContainsString(lang('RESTful.notImplemented', ['update']), $output); diff --git a/user_guide_src/source/changelogs/v4.3.0.rst b/user_guide_src/source/changelogs/v4.3.0.rst index fd00f93c25a9..6a794ec5b157 100644 --- a/user_guide_src/source/changelogs/v4.3.0.rst +++ b/user_guide_src/source/changelogs/v4.3.0.rst @@ -46,10 +46,18 @@ Others - ``CITestStreamFilter::$buffer = ''`` no longer causes the filter to be registered to listen for streams. Now there is a ``CITestStreamFilter::registration()`` method for this. See :ref:`upgrade-430-stream-filter` for details. +.. _v430-interface-changes: + +Interface Changes +================= + +- Added missing ``getBody()``, ``hasHeader()`` and ``getHeaderLine()`` method in ``MessageInterface``. +- Now ``ResponseInterface`` extends ``MessageInterface``. + Method Signature Changes ======================== -.. _v430_validation_changes: +.. _v430-validation-changes: Validation Changes ------------------ diff --git a/user_guide_src/source/installation/upgrade_430.rst b/user_guide_src/source/installation/upgrade_430.rst index a304001cf896..5d9aac504cdb 100644 --- a/user_guide_src/source/installation/upgrade_430.rst +++ b/user_guide_src/source/installation/upgrade_430.rst @@ -77,7 +77,7 @@ instead of the Validation object. Validation Changes ================== -- ``ValidationInterface`` has been changed. Implemented classes should likewise add the methods and the parameters so as not to break LSP. See :ref:`v430_validation_changes` for details. +- ``ValidationInterface`` has been changed. Implemented classes should likewise add the methods and the parameters so as not to break LSP. See :ref:`v430-validation-changes` for details. - The return value of ``Validation::loadRuleGroup()`` has been changed ``null`` to ``[]`` when the ``$group`` is empty. Update the code if you depend on the behavior. .. _upgrade-430-stream-filter: @@ -117,6 +117,11 @@ need to use:: Or use the trait ``CodeIgniter\Test\StreamFilterTrait``. See :ref:`testing-cli-output`. +Interface Changes +================= + +Some interfaces has been fixed. See :ref:`v430-interface-changes` for details. + Others ======