Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions deptrac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ deptrac:
- Cookie
- Files
- I18n
- Input
- Security
- URI
Images:
Expand Down
2 changes: 1 addition & 1 deletion structarmed.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
'Files' => ['I18n'],
'Filters' => ['HTTP'],
'Honeypot' => ['Filters', 'HTTP'],
'HTTP' => ['Cookie', 'Files', 'I18n', 'Security', 'URI'],
'HTTP' => ['Cookie', 'Files', 'I18n', 'Input', 'Security', 'URI'],
'Images' => ['Files', 'I18n'],
'Lock' => ['Cache'],
'Model' => ['Database', 'DataCaster', 'DataConverter', 'Entity', 'I18n', 'Pager', 'Validation'],
Expand Down
8 changes: 8 additions & 0 deletions system/HTTP/IncomingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,14 @@ public function getGet($index = null, $filter = null, $flags = null)
return $this->fetchGlobal('get', $index, $filter, $flags);
}

/**
* Returns a typed input data selector.
*/
public function input(): RequestInput
{
return new RequestInput($this, service('inputdatafactory'));
}

/**
* Fetch an item from POST.
*
Expand Down
74 changes: 74 additions & 0 deletions system/HTTP/RequestInput.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\HTTP;

use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\Input\InputData;
use CodeIgniter\Input\InputDataFactory;

/**
* Provides typed input access for request data sources.
*
* @see \CodeIgniter\HTTP\RequestInputTest
*/
final readonly class RequestInput
{
public function __construct(
private IncomingRequest $request,
private InputDataFactory $factory,
) {
}

/**
* Returns GET parameters as a typed input object.
*/
public function get(): InputData
{
$data = $this->request->getGet();

return $this->factory->create(is_array($data) ? $data : []);
}

/**
* Returns POST body parameters as a typed input object.
*/
public function post(): InputData
{
$data = $this->request->getPost();

return $this->factory->create(is_array($data) ? $data : []);
}

/**
* Returns JSON body parameters as a typed input object.
*/
public function json(): InputData
{
$data = $this->request->getJSON(true) ?? [];

if (! is_array($data)) {
throw HTTPException::forUnsupportedJSONFormat();
}

return $this->factory->create($data);
}

/**
* Returns raw input parameters as a typed input object.
*/
public function raw(): InputData
{
return $this->factory->create($this->request->getRawInput());
}
}
134 changes: 134 additions & 0 deletions tests/system/HTTP/RequestInputTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

declare(strict_types=1);

/**
* This file is part of CodeIgniter 4 framework.
*
* (c) CodeIgniter Foundation <admin@codeigniter.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace CodeIgniter\HTTP;

use CodeIgniter\Config\Services;
use CodeIgniter\HTTP\Exceptions\HTTPException;
use CodeIgniter\Input\InputData;
use CodeIgniter\Superglobals;
use CodeIgniter\Test\CIUnitTestCase;
use Config\App;
use PHPUnit\Framework\Attributes\BackupGlobals;
use PHPUnit\Framework\Attributes\Group;

/**
* @internal
*/
#[BackupGlobals(true)]
#[Group('SeparateProcess')]
final class RequestInputTest extends CIUnitTestCase
{
protected function setUp(): void
{
parent::setUp();

Services::injectMock('superglobals', new Superglobals([], [], [], [], [], []));
}

private function createRequest(?App $config = null, ?string $body = null): IncomingRequest
{
$config ??= new App();

return new IncomingRequest(
$config,
new SiteURI($config, ''),
$body,
new UserAgent(),
);
}

public function testInputReturnsRequestInput(): void
{
$request = $this->createRequest();
$input = $request->input();

$this->assertInstanceOf(RequestInput::class, $input);
}

public function testGetReadsGetData(): void
{
service('superglobals')->setGet('page', '3');
service('superglobals')->setGet('filters', ['active' => 'true']);
service('superglobals')->setPost('page', '10');

$input = $this->createRequest()->input()->get();

$this->assertInstanceOf(InputData::class, $input);
$this->assertSame(3, $input->integer('page'));
$this->assertTrue($input->boolean('filters.active'));
$this->assertSame(1, $input->integer('missing', 1));
}

public function testPostReadsPostData(): void
{
service('superglobals')->setGet('remember', '0');
service('superglobals')->setPost('remember', '1');
service('superglobals')->setPost('tags', ['php', 'ci4']);

$input = $this->createRequest()->input()->post();

$this->assertInstanceOf(InputData::class, $input);
$this->assertTrue($input->boolean('remember'));
$this->assertSame(['php', 'ci4'], $input->array('tags'));
}

public function testJsonReadsJsonBody(): void
{
$json = json_encode([
'page' => '4',
'filters' => ['active' => 'true'],
'nullable' => null,
]);

$input = $this->createRequest(new App(), $json)->input()->json();

$this->assertInstanceOf(InputData::class, $input);
$this->assertSame(4, $input->integer('page'));
$this->assertTrue($input->boolean('filters.active'));
$this->assertTrue($input->has('nullable'));
}

public function testJsonReturnsEmptyInputForEmptyJsonBody(): void
{
$input = $this->createRequest(new App())->input()->json();

$this->assertInstanceOf(InputData::class, $input);
$this->assertFalse($input->has('name'));
}

public function testJsonRejectsScalarJsonBody(): void
{
$this->expectException(HTTPException::class);
$this->expectExceptionMessage('The provided JSON format is not supported.');

$this->createRequest(new App(), '"hello"')->input()->json();
}

public function testJsonKeepsInvalidJsonError(): void
{
$this->expectException(HTTPException::class);
$this->expectExceptionMessage('Failed to parse JSON string. Error: Syntax error');

$this->createRequest(new App(), 'Invalid JSON string')->input()->json();
}

public function testRawReadsRawInputData(): void
{
$input = $this->createRequest(new App(), 'title=Hello&published=1')->input()->raw();

$this->assertInstanceOf(InputData::class, $input);
$this->assertSame('Hello', $input->string('title'));
$this->assertTrue($input->boolean('published'));
}
}
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.8.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ HTTP

- 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 <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``.
- Added ``SSEResponse`` class for streaming Server-Sent Events (SSE) over HTTP. See :ref:`server-sent-events`.
- ``Response`` and its child classes no longer require ``Config\App`` passed to their constructors.
Consequently, ``CURLRequest``'s ``$config`` parameter is unused and will be removed in a future release.
Expand Down
30 changes: 29 additions & 1 deletion user_guide_src/source/incoming/incomingrequest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,30 @@ The ``getVar()`` method will pull from ``$_REQUEST``, so will return any data fr
.. note:: If the incoming request has a ``Content-Type`` header set to ``application/json``,
the ``getVar()`` method returns the JSON data instead of ``$_REQUEST`` data.

.. _incomingrequest-typed-request-input:

Typed Request Input
===================

.. versionadded:: 4.8.0

``input()`` returns a ``CodeIgniter\HTTP\RequestInput`` object. Use it to read
values from a specific part of the request with typed fallback helpers:

.. literalinclude:: incomingrequest/046.php
:lines: 2-

``input()->get()`` reads query-string parameters. ``input()->post()`` reads
POST body parameters. ``input()->json()`` reads JSON request body parameters.
``input()->raw()`` reads raw input parameters, like ``getRawInput()``.

These methods keep GET, POST, JSON, and raw data separate. They do not combine
multiple request sources for you.

These methods do not validate input. They are fallback-friendly helpers for
reading raw request data. Use Validation or :ref:`form-requests` when input
must satisfy application rules before it is consumed.

.. _incomingrequest-getting-json-data:

Getting JSON Data
Expand Down Expand Up @@ -406,6 +430,11 @@ The methods provided by the parent classes that are available are:

.. literalinclude:: incomingrequest/045.php

.. php:method:: input()

:returns: A typed input data selector.
:rtype: CodeIgniter\\HTTP\\RequestInput

.. php:method:: getPost([$index = null[, $filter = null[, $flags = null]]])

:param string $index: The name of the variable/key to look for.
Expand Down Expand Up @@ -519,4 +548,3 @@ The methods provided by the parent classes that are available are:
.. note:: Prior to v4.4.0, this was the safest method to determine the
"current URI", since ``IncomingRequest::$uri`` might not be aware of
the complete App configuration for base URLs.

6 changes: 6 additions & 0 deletions user_guide_src/source/incoming/incomingrequest/046.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

$page = $request->input()->get()->integer('page', 1);
$remember = $request->input()->post()->boolean('remember', false);
$name = $request->input()->json()->string('name');
$published = $request->input()->raw()->boolean('published', false);
Loading