diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 3c199fb9a309..0180c832860e 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -292,6 +292,16 @@ public function put(string $url, array $options = []): ResponseInterface return $this->request(Method::PUT, $url, $options); } + /** + * Convenience method for sending a QUERY request. + * + * @param array $options + */ + public function query(string $url, array $options = []): ResponseInterface + { + return $this->request(Method::QUERY, $url, $options); + } + /** * Set the HTTP Authentication. * diff --git a/system/HTTP/FormRequest.php b/system/HTTP/FormRequest.php index 881824e18acf..1a38eeae00f4 100644 --- a/system/HTTP/FormRequest.php +++ b/system/HTTP/FormRequest.php @@ -223,10 +223,10 @@ public function getValidatedInput(): ValidatedInput * multiple sources. By default, data is sourced from the appropriate * part of the request based on HTTP method and Content-Type: * - * - JSON (any method) - decoded JSON body - * - PUT / PATCH / DELETE - raw body (unless multipart/form-data) - * - GET / HEAD - query-string parameters - * - Everything else (POST) - POST body + * - JSON (any method) - decoded JSON body + * - PUT / PATCH / DELETE / QUERY - raw body (unless multipart/form-data) + * - GET / HEAD - query-string parameters + * - Everything else (POST) - POST body * * @return array */ @@ -239,7 +239,7 @@ protected function validationData(): array } if ( - in_array($this->request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE], true) + in_array($this->request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE, Method::QUERY], true) && ! str_contains($contentType, 'multipart/form-data') ) { return $this->request->getRawInput() ?? []; diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 3139af042608..8994c75e0a8d 100644 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -499,7 +499,7 @@ public function getJsonVar($index = null, bool $assoc = false, ?int $filter = nu } /** - * A convenience method that grabs the raw input stream(send method in PUT, PATCH, DELETE) and decodes + * A convenience method that grabs the raw input stream (send method in PUT, PATCH, DELETE, QUERY) and decodes * the String into an array. * * @return array @@ -512,7 +512,7 @@ public function getRawInput() } /** - * Gets a specific variable from raw input stream (send method in PUT, PATCH, DELETE). + * Gets a specific variable from raw input stream (send method in PUT, PATCH, DELETE, QUERY). * * @param array|string|null $index The variable that you want which can use dot syntax for getting specific values. * @param int|null $filter Filter Constant diff --git a/system/HTTP/Method.php b/system/HTTP/Method.php index 09e1dbc95ecb..e86bcaed9b76 100644 --- a/system/HTTP/Method.php +++ b/system/HTTP/Method.php @@ -90,6 +90,15 @@ class Method */ public const PUT = 'PUT'; + /** + * Safe: Yes + * Idempotent: Yes + * Cacheable: Yes + * + * @see https://www.rfc-editor.org/rfc/rfc10008.html + */ + public const QUERY = 'QUERY'; + /** * Safe: Yes * Idempotent: Yes @@ -115,6 +124,7 @@ public static function all(): array self::PATCH, self::POST, self::PUT, + self::QUERY, self::TRACE, ]; } diff --git a/system/Router/RouteCollection.php b/system/Router/RouteCollection.php index 0710c6902836..2c2700046d00 100644 --- a/system/Router/RouteCollection.php +++ b/system/Router/RouteCollection.php @@ -143,6 +143,7 @@ class RouteCollection implements RouteCollectionInterface Method::OPTIONS => [], Method::GET => [], Method::HEAD => [], + Method::QUERY => [], Method::POST => [], Method::PATCH => [], Method::PUT => [], @@ -168,6 +169,7 @@ class RouteCollection implements RouteCollectionInterface Method::OPTIONS => [], Method::GET => [], Method::HEAD => [], + Method::QUERY => [], Method::POST => [], Method::PATCH => [], Method::PUT => [], @@ -1102,6 +1104,19 @@ public function head(string $from, $to, ?array $options = null): RouteCollection return $this; } + /** + * Specifies a route that is only available to QUERY requests. + * + * @param array|(Closure(mixed...): (ResponseInterface|string|void))|string $to + * @param array|null $options + */ + public function query(string $from, $to, ?array $options = null): RouteCollectionInterface + { + $this->create(Method::QUERY, $from, $to, $options); + + return $this; + } + /** * Specifies a route that is only available to PATCH requests. * diff --git a/system/Router/Router.php b/system/Router/Router.php index 924a1ae7ea5f..5755c0cf23fe 100644 --- a/system/Router/Router.php +++ b/system/Router/Router.php @@ -43,6 +43,7 @@ class Router implements RouterInterface public const HTTP_METHODS = [ Method::GET, Method::HEAD, + Method::QUERY, Method::POST, Method::PATCH, Method::PUT, diff --git a/system/Test/FeatureTestTrait.php b/system/Test/FeatureTestTrait.php index c45ed882699f..37d44b6d8ac1 100644 --- a/system/Test/FeatureTestTrait.php +++ b/system/Test/FeatureTestTrait.php @@ -296,6 +296,21 @@ public function options(string $path, ?array $params = null) return $this->call(Method::OPTIONS, $path, $params); } + /** + * Performs a QUERY request. + * + * @param array|null $params + * + * @return TestResponse + * + * @throws RedirectException + * @throws Exception + */ + public function query(string $path, ?array $params = null) + { + return $this->call(Method::QUERY, $path, $params); + } + /** * Setup a Request object to use so that CodeIgniter * won't try to auto-populate some of the items. diff --git a/system/Validation/Validation.php b/system/Validation/Validation.php index fa8522ac7799..7f53bb58c1f3 100644 --- a/system/Validation/Validation.php +++ b/system/Validation/Validation.php @@ -535,7 +535,7 @@ public function withRequest(RequestInterface $request): ValidationInterface return $this; } - if (in_array($request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE], true) + if (in_array($request->getMethod(), [Method::PUT, Method::PATCH, Method::DELETE, Method::QUERY], true) && ! str_contains($request->getHeaderLine('Content-Type'), 'multipart/form-data') ) { $this->data = $request->getRawInput(); diff --git a/tests/system/Commands/Utilities/RoutesTest.php b/tests/system/Commands/Utilities/RoutesTest.php index df54ece28a6c..20bceadc947a 100644 --- a/tests/system/Commands/Utilities/RoutesTest.php +++ b/tests/system/Commands/Utilities/RoutesTest.php @@ -32,6 +32,10 @@ protected function setUp(): void { $this->resetServices(); parent::setUp(); + + service('superglobals') + ->setServer('HTTP_HOST', 'example.com') + ->setServer('SERVER_NAME', 'example.com'); } protected function tearDown(): void @@ -68,6 +72,7 @@ public function testRoutesCommand(): void | GET | closure | » | (Closure) | | | | GET | testing | testing-index | \App\Controllers\TestController::index | | | | HEAD | testing | testing-index | \App\Controllers\TestController::index | | | + | QUERY | testing | testing-index | \App\Controllers\TestController::index | | | | POST | testing | testing-index | \App\Controllers\TestController::index | | | | PATCH | testing | testing-index | \App\Controllers\TestController::index | | | | PUT | testing | testing-index | \App\Controllers\TestController::index | | | @@ -104,6 +109,7 @@ public function testRoutesCommandSortByHandler(): void | GET | / | » | \App\Controllers\Home::index | | | | GET | testing | testing-index | \App\Controllers\TestController::index | | | | HEAD | testing | testing-index | \App\Controllers\TestController::index | | | + | QUERY | testing | testing-index | \App\Controllers\TestController::index | | | | POST | testing | testing-index | \App\Controllers\TestController::index | | | | PATCH | testing | testing-index | \App\Controllers\TestController::index | | | | PUT | testing | testing-index | \App\Controllers\TestController::index | | | @@ -133,6 +139,7 @@ public function testRoutesCommandHostHostname(): void | GET | all | » | \App\Controllers\AllDomain::index | | | | GET | testing | testing-index | \App\Controllers\TestController::index | | | | HEAD | testing | testing-index | \App\Controllers\TestController::index | | | + | QUERY | testing | testing-index | \App\Controllers\TestController::index | | | | POST | testing | testing-index | \App\Controllers\TestController::index | | | | PATCH | testing | testing-index | \App\Controllers\TestController::index | | | | PUT | testing | testing-index | \App\Controllers\TestController::index | | | @@ -162,6 +169,7 @@ public function testRoutesCommandHostSubdomain(): void | GET | all | » | \App\Controllers\AllDomain::index | | | | GET | testing | testing-index | \App\Controllers\TestController::index | | | | HEAD | testing | testing-index | \App\Controllers\TestController::index | | | + | QUERY | testing | testing-index | \App\Controllers\TestController::index | | | | POST | testing | testing-index | \App\Controllers\TestController::index | | | | PATCH | testing | testing-index | \App\Controllers\TestController::index | | | | PUT | testing | testing-index | \App\Controllers\TestController::index | | | @@ -193,6 +201,7 @@ public function testRoutesCommandAutoRouteImproved(): void | GET | closure | » | (Closure) | | | | GET | testing | testing-index | \App\Controllers\TestController::index | | | | HEAD | testing | testing-index | \App\Controllers\TestController::index | | | + | QUERY | testing | testing-index | \App\Controllers\TestController::index | | | | POST | testing | testing-index | \App\Controllers\TestController::index | | | | PATCH | testing | testing-index | \App\Controllers\TestController::index | | | | PUT | testing | testing-index | \App\Controllers\TestController::index | | | @@ -228,6 +237,7 @@ public function testRoutesCommandRouteLegacy(): void | GET | closure | » | (Closure) | | | | GET | testing | testing-index | \App\Controllers\TestController::index | | | | HEAD | testing | testing-index | \App\Controllers\TestController::index | | | + | QUERY | testing | testing-index | \App\Controllers\TestController::index | | | | POST | testing | testing-index | \App\Controllers\TestController::index | | | | PATCH | testing | testing-index | \App\Controllers\TestController::index | | | | PUT | testing | testing-index | \App\Controllers\TestController::index | | | diff --git a/tests/system/HTTP/CURLRequestTest.php b/tests/system/HTTP/CURLRequestTest.php index aece2bf26354..ff85437af1b9 100644 --- a/tests/system/HTTP/CURLRequestTest.php +++ b/tests/system/HTTP/CURLRequestTest.php @@ -175,6 +175,35 @@ public function testOptionsSetsCorrectMethod(): void $this->assertSame('OPTIONS', $options[CURLOPT_CUSTOMREQUEST]); } + public function testQuerySetsCorrectMethod(): void + { + $this->request->query('http://example.com'); + + $this->assertSame('QUERY', $this->request->getMethod()); + + $options = $this->request->curl_options; + + $this->assertArrayHasKey(CURLOPT_CUSTOMREQUEST, $options); + $this->assertSame('QUERY', $options[CURLOPT_CUSTOMREQUEST]); + } + + public function testQueryWithJsonSetsBodyAndContentType(): void + { + $params = [ + 'foo' => 'bar', + ]; + $this->request->query('http://example.com', [ + 'json' => $params, + ]); + + $options = $this->request->curl_options; + + $this->assertSame('QUERY', $this->request->getMethod()); + $this->assertSame('QUERY', $options[CURLOPT_CUSTOMREQUEST]); + $this->assertSame(json_encode($params), $options[CURLOPT_POSTFIELDS]); + $this->assertContains('Content-Type: application/json', $options[CURLOPT_HTTPHEADER]); + } + public function testOptionsBaseURIOption(): void { $options = ['baseURI' => 'http://www.foo.com/api/v1/']; diff --git a/tests/system/HTTP/FormRequestTest.php b/tests/system/HTTP/FormRequestTest.php index e01c6ebb3509..d10c2433d663 100644 --- a/tests/system/HTTP/FormRequestTest.php +++ b/tests/system/HTTP/FormRequestTest.php @@ -40,7 +40,7 @@ protected function setUp(): void parent::setUp(); $this->resetServices(); - Services::injectMock('superglobals', new Superglobals()); + Services::injectMock('superglobals', new Superglobals(get: [], post: [])); service('superglobals')->setServer('REQUEST_METHOD', 'POST'); service('superglobals')->setServer('SERVER_PROTOCOL', 'HTTP/1.1'); service('superglobals')->setServer('SERVER_NAME', 'example.com'); @@ -346,6 +346,23 @@ public function testResolveRequestReturns422ForJsonRequest(): void $this->assertSame(422, $response->getStatusCode()); } + public function testResolveRequestUsesQueryRawBody(): void + { + service('superglobals') + ->setServer('REQUEST_METHOD', Method::QUERY) + ->setServer('CONTENT_TYPE', 'application/x-www-form-urlencoded'); + + $formRequest = $this->makeFormRequest($this->makeRequest('title=Hello&body=World')); + + $response = $formRequest->resolveRequest(); + + $this->assertNotInstanceOf(ResponseInterface::class, $response); + $this->assertSame([ + 'title' => 'Hello', + 'body' => 'World', + ], $formRequest->getValidated()); + } + public function testResolveRequestReturns422WhenJsonIsPreferred(): void { service('superglobals')->setServer('HTTP_ACCEPT', 'application/json'); diff --git a/tests/system/HTTP/IncomingRequestTest.php b/tests/system/HTTP/IncomingRequestTest.php index c16c706a9b8d..fc014db42ee3 100644 --- a/tests/system/HTTP/IncomingRequestTest.php +++ b/tests/system/HTTP/IncomingRequestTest.php @@ -700,6 +700,7 @@ public static function provideIsHTTPMethods(): iterable ['HEAD'], ['PATCH'], ['OPTIONS'], + ['QUERY'], ]; } diff --git a/tests/system/Router/RouteCollectionTest.php b/tests/system/Router/RouteCollectionTest.php index 6346697d206a..905affd1c827 100644 --- a/tests/system/Router/RouteCollectionTest.php +++ b/tests/system/Router/RouteCollectionTest.php @@ -839,12 +839,17 @@ public function testMatchSupportsMultipleMethods(): void $expected = ['here' => '\there']; - $routes->match(['GET', 'POST'], 'here', 'there'); + $routes->match(['GET', 'POST', 'QUERY'], 'here', 'there'); $this->assertSame($expected, $routes->getRoutes()); service('request')->setMethod(Method::POST); $routes = $this->getCollector(); - $routes->match(['GET', 'POST'], 'here', 'there'); + $routes->match(['GET', 'POST', 'QUERY'], 'here', 'there'); + $this->assertSame($expected, $routes->getRoutes()); + + service('request')->setMethod(Method::QUERY); + $routes = $this->getCollector(); + $routes->match(['GET', 'POST', 'QUERY'], 'here', 'there'); $this->assertSame($expected, $routes->getRoutes()); } @@ -915,6 +920,17 @@ public function testHead(): void $this->assertSame($expected, $routes->getRoutes()); } + public function testQuery(): void + { + $routes = $this->getCollector(); + $routes->setHTTPVerb(Method::QUERY); + + $expected = ['here' => '\there']; + + $routes->query('here', 'there'); + $this->assertSame($expected, $routes->getRoutes()); + } + public function testPatch(): void { $routes = $this->getCollector(); diff --git a/tests/system/Security/SecurityTest.php b/tests/system/Security/SecurityTest.php index 1785341531c1..a33ec2fef045 100644 --- a/tests/system/Security/SecurityTest.php +++ b/tests/system/Security/SecurityTest.php @@ -16,6 +16,7 @@ use CodeIgniter\Config\Factories; use CodeIgniter\Config\Services; use CodeIgniter\HTTP\IncomingRequest; +use CodeIgniter\HTTP\Method; use CodeIgniter\HTTP\SiteURI; use CodeIgniter\HTTP\UserAgent; use CodeIgniter\Security\Exceptions\SecurityException; @@ -104,6 +105,16 @@ public function testGetHashReturnsCsrfCookieWhenGetWithCsrfCookie(): void $this->assertSame(self::CORRECT_CSRF_HASH, $security->getHash()); } + public function testCsrfVerifyQueryReturnsSelf(): void + { + service('superglobals')->setServer('REQUEST_METHOD', Method::QUERY); + + $security = $this->createMockSecurity(); + $request = $this->createIncomingRequest(); + + $this->assertSame($security, $security->verify($request)); + } + public function testCsrfVerifyPostThrowsExceptionOnNoMatch(): void { service('superglobals') diff --git a/tests/system/Test/FeatureTestTraitTest.php b/tests/system/Test/FeatureTestTraitTest.php index f36767f7f971..b5d2f3546d28 100644 --- a/tests/system/Test/FeatureTestTraitTest.php +++ b/tests/system/Test/FeatureTestTraitTest.php @@ -538,6 +538,30 @@ public function testCallPutWithJsonRequest(): void $response->assertJSONExact($data); } + public function testCallQueryWithJsonRequest(): void + { + $this->withRoutes([ + [ + 'QUERY', + 'home', + '\Tests\Support\Controllers\Popcorn::echoJson', + ], + ]); + $data = [ + 'true' => true, + 'false' => false, + 'int' => 2, + 'null' => null, + 'float' => 1.23, + 'string' => 'foo', + ]; + $response = $this->withBodyFormat('json') + ->query('home', $data); + + $response->assertOK(); + $response->assertJSONExact($data); + } + public function testCallPutWithJsonRequestAndREQUEST(): void { $this->withRoutes([ diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index d0483a890ed8..6773f27d7274 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -974,7 +974,8 @@ public function testInvalidRule(): void $this->validation->run($data); } - public function testRawInput(): void + #[DataProvider('provideRawInput')] + public function testRawInput(string $method): void { $rawstring = 'username=admin001&role=administrator&usepass=0'; $config = new App(); @@ -984,13 +985,23 @@ public function testRawInput(): void $rules = [ 'role' => 'required|min_length[5]', ]; - $result = $this->validation->withRequest($request->withMethod('PATCH'))->setRules($rules)->run(); + $result = $this->validation->withRequest($request->withMethod($method))->setRules($rules)->run(); $this->assertTrue($result); $this->assertSame([], $this->validation->getErrors()); $this->assertSame(['role' => 'administrator'], $this->validation->getValidated()); } + /** + * @return iterable> + */ + public static function provideRawInput(): iterable + { + yield 'PATCH' => ['PATCH']; + + yield 'QUERY' => ['QUERY']; + } + public function testJsonInput(): void { service('superglobals')->setServer('CONTENT_TYPE', 'application/json'); diff --git a/user_guide_src/source/changelogs/v4.8.0.rst b/user_guide_src/source/changelogs/v4.8.0.rst index 569daf8f5c43..33eddd4cd39c 100644 --- a/user_guide_src/source/changelogs/v4.8.0.rst +++ b/user_guide_src/source/changelogs/v4.8.0.rst @@ -304,6 +304,7 @@ Helpers and Functions HTTP ==== +- Added support for the HTTP ``QUERY`` method in routing, feature tests, ``CURLRequest``, and ``FormRequest`` input source selection. - Added the ``retry`` option to ``CURLRequest`` for retrying failed responses with configurable delays, retryable status codes, optional transient cURL error retries, and ``Retry-After`` support. See :ref:`curlrequest-request-options-retry`. - Added :ref:`Form Requests ` - a new ``FormRequest`` base class that encapsulates validation rules, custom error messages, and authorization logic for a single HTTP request. - Added ``IncomingRequest::input()`` to read GET, POST, JSON, and raw request data through ``InputData``. diff --git a/user_guide_src/source/incoming/form_requests.rst b/user_guide_src/source/incoming/form_requests.rst index 278703cd237e..6528ca33a8a0 100644 --- a/user_guide_src/source/incoming/form_requests.rst +++ b/user_guide_src/source/incoming/form_requests.rst @@ -185,7 +185,7 @@ By default ``validationData()`` selects the appropriate data source automatically based on the HTTP method and ``Content-Type`` header: * **JSON request** (``Content-Type: application/json``) -> decoded JSON body -* **PUT / PATCH / DELETE** (non-multipart) -> raw body via ``getRawInput()`` +* **PUT / PATCH / DELETE / QUERY** (non-multipart) -> raw body via ``getRawInput()`` * **GET / HEAD** -> query-string parameters via ``getGet()`` * **Everything else** (POST, multipart) -> POST body via ``getPost()`` diff --git a/user_guide_src/source/incoming/incomingrequest.rst b/user_guide_src/source/incoming/incomingrequest.rst index 680f0b807dce..a3f814b3f902 100644 --- a/user_guide_src/source/incoming/incomingrequest.rst +++ b/user_guide_src/source/incoming/incomingrequest.rst @@ -220,8 +220,8 @@ pass true in the second parameter: .. _incomingrequest-retrieving-raw-data: -Retrieving Raw Data (PUT, PATCH, DELETE) -======================================== +Retrieving Raw Data (PUT, PATCH, DELETE, QUERY) +=============================================== Finally, you can grab the contents of ``php://input`` as a raw stream with ``getRawInput()``: diff --git a/user_guide_src/source/incoming/routing.rst b/user_guide_src/source/incoming/routing.rst index 0aad82af632c..416121c8e7dc 100644 --- a/user_guide_src/source/incoming/routing.rst +++ b/user_guide_src/source/incoming/routing.rst @@ -78,6 +78,19 @@ You can use any standard HTTP verb (GET, POST, PUT, DELETE, OPTIONS, etc): .. literalinclude:: routing/003.php +``QUERY`` routes are for the HTTP QUERY method defined by RFC 10008. +``QUERY`` requests are safe and idempotent like GET, but send query content in the +request body. Read that body explicitly with request input methods such as +``$this->request->input()->json()`` or ``$this->request->input()->raw()``. Your +controller should reject missing or unsupported ``Content-Type`` values for the +query content it expects. Do not use ``QUERY`` for operations that create, +update, delete, or otherwise change server state. + +.. note:: Your web server, proxy, or CORS configuration may need to allow the + ``QUERY`` method before these requests can reach CodeIgniter. For + browser-based cross-origin requests, ensure ``QUERY`` is included in your + allowed CORS methods so preflight requests can succeed. + You can supply multiple verbs that a route should match by passing them in as an array to the ``match()`` method: .. literalinclude:: routing/004.php diff --git a/user_guide_src/source/incoming/routing/003.php b/user_guide_src/source/incoming/routing/003.php index 7351142dea77..50ac7be75c64 100644 --- a/user_guide_src/source/incoming/routing/003.php +++ b/user_guide_src/source/incoming/routing/003.php @@ -3,3 +3,4 @@ $routes->post('products', 'Product::feature'); $routes->put('products/1', 'Product::feature'); $routes->delete('products/1', 'Product::feature'); +$routes->query('products/search', 'Product::search'); diff --git a/user_guide_src/source/libraries/curlrequest.rst b/user_guide_src/source/libraries/curlrequest.rst index 61e73e702796..dc96cc19f0a5 100644 --- a/user_guide_src/source/libraries/curlrequest.rst +++ b/user_guide_src/source/libraries/curlrequest.rst @@ -116,6 +116,10 @@ each take the URL as the first parameter and an array of options as the second: .. literalinclude:: curlrequest/007.php +The ``query()`` shortcut sends an HTTP QUERY request. QUERY requests usually +include query content, so use options such as ``json``, ``form_params``, or +``body`` to send the expected body. + Base URI -------- diff --git a/user_guide_src/source/libraries/curlrequest/007.php b/user_guide_src/source/libraries/curlrequest/007.php index d509f3e3fa29..ca6514e2b970 100644 --- a/user_guide_src/source/libraries/curlrequest/007.php +++ b/user_guide_src/source/libraries/curlrequest/007.php @@ -7,3 +7,4 @@ $client->patch('http://example.com'); $client->put('http://example.com'); $client->post('http://example.com'); +$client->query('http://example.com'); diff --git a/user_guide_src/source/libraries/validation.rst b/user_guide_src/source/libraries/validation.rst index c7d76d1c0409..17da5936c1f2 100644 --- a/user_guide_src/source/libraries/validation.rst +++ b/user_guide_src/source/libraries/validation.rst @@ -399,7 +399,7 @@ data to be validated: when the request is a JSON request (``Content-Type: application/json``), or gets Raw data from :ref:`$request->getRawInput() ` - when the request is a PUT, PATCH, DELETE request and + when the request is a PUT, PATCH, DELETE, or QUERY request and is not HTML form post (``Content-Type: multipart/form-data``), or gets data from :ref:`$request->getVar() `, and an attacker could change what data is validated. diff --git a/user_guide_src/source/testing/feature.rst b/user_guide_src/source/testing/feature.rst index bccefdf3b0df..84f8fc18effc 100644 --- a/user_guide_src/source/testing/feature.rst +++ b/user_guide_src/source/testing/feature.rst @@ -105,6 +105,9 @@ This is useful when testing JSON or XML APIs so that you can set the request in This will take the parameters passed into ``call()``, ``post()``, ``get()``... and assign them to the body of the request in the given format. +For example, use ``withBodyFormat('json')->query()`` to test a QUERY request +with a JSON body. + This will also set the `Content-Type` header for your request accordingly. .. literalinclude:: feature/008.php diff --git a/user_guide_src/source/testing/feature/003.php b/user_guide_src/source/testing/feature/003.php index 1905a2dcf110..859c9e348c9d 100644 --- a/user_guide_src/source/testing/feature/003.php +++ b/user_guide_src/source/testing/feature/003.php @@ -6,3 +6,4 @@ $this->patch($path, $params); $this->delete($path, $params); $this->options($path, $params); +$this->query($path, $params);