feat: add typed FormRequest accessors#10158
feat: add typed FormRequest accessors#10158memleakd wants to merge 7 commits intocodeigniter4:4.8from
Conversation
- Add typed helpers for reading validated integer, boolean, date, and enum values - Keep accessors scoped to validated FormRequest data only - Document expected validation/accessor responsibilities - Cover defaults, null values, invalid values, dot syntax, and enum variants Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
|
Thanks, I like the idea. Typed access to validated data is useful and would make controller code nicer. However, I don't think these accessors should be added directly to I would prefer a design like this: $validation->getValidated(); // array, unchanged
$formRequest->validated(); // array, unchanged
$input = $validation->getValidatedInput();
$input = $formRequest->validatedInput();
$page = $input->integer('page', 1);
$active = $input->boolean('active', false);
$status = $input->enum('status', Status::class);This keeps BC, makes the feature available outside So I'm positive about the concept, but I think we should be careful with the design and not rush the implementation. I would like to hear what others think before we decide whether this should be reshaped into a dedicated |
|
Thanks, this makes sense to me. I originally kept the helpers directly on I’ll convert the PR to draft for now and wait for more feedback before reshaping it. I agree the dedicated Happy to rework it in that direction if everyone feel the same. |
|
I went ahead and reworked this in the
I also updated the docs, tests, and changelog/interface-change note around this design. I’ll mark the PR ready for review again.
|
- add ValidatedInput for typed access to validated data - expose ValidatedInput from Validation and FormRequest - keep FormRequest focused on request validation flow - update docs and tests for the new typed input API Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
71b130d to
d7067f3
Compare
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
I would add |
However, I would be against adding methods directly to the request object: $this->request->integer('page', 1);That leaves too much ambiguity about the input source, creating the same problem we have with $this->request->getQueryInput()->integer('page', 1);
$this->request->getPostInput()->boolean('active', false);
$this->request->getPayloadInput()->enum('status', Status::class);To make this reusable, maybe the current namespace CodeIgniter\Input;
class InputData
{
public function get(string $key, mixed $default = null): mixed {}
public function has(string $key): bool {}
public function integer(string $key, ?int $default = null): ?int {}
public function boolean(string $key, ?bool $default = null): ?bool {}
public function date(string $key, ...): ?Time {}
public function enum(string $key, string $enumClass, ?UnitEnum $default = null): ?UnitEnum {}
public function string(string $key, ?string $default = null): ?string {}
public function array(string $key, ?array $default = null): ?array {}
}Then The important part is agreeing on the behavior for missing values, One more important thing - I don't think this PR needs to implement request-level input methods, but the class design should not make that reuse difficult. Thoughts? |
|
Sorry. I was thinking about the And to shorten the code, $input = $this->request->getPostInput();
$email = $input->string('email', '');
$status = $input->integer('status', 0);Typed default values should not return another scalar type. There should be no other types for |
|
#9482 If we go back, I was talking about a similar behavior - separate methods for types. Here we see why it was necessary. Yes, the PR is imperfect, but we just rejected this idea. |
|
I agree that raw request input should probably support only simple types, for example Then, as you said, we can still have a So the structure could be: CodeIgniter\Input\InputData
CodeIgniter\Validation\ValidatedInput extends InputData
I would also move the current As for extensibility, we could add services for both I think DTO/value object mapping is too large for this PR. For now, I'd keep this PR focused on typed access to existing values. |
|
The DTO is given as an example that the validator works with any data, it does not need to be limited in types. I wrote about it too #9262 |
|
Why is the namespace on |
I suggested
|
- Add generic InputData for typed access to keyed input data - Move validation-specific accessors to ValidatedInput - Return ValidatedInput from Validation and FormRequest APIs - Add non-shared inputdata and validatedinput services - Update docs, tests, changelog, and architecture rules Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
|
Thanks everyone for the discussion and feedback! I reworked this around the latest architecture direction: CodeIgniter\Input\InputData
CodeIgniter\Validation\ValidatedInput extends InputData
This keeps the PR focused on validated data, while leaving room for future request-source APIs like Docs, tests, changelog, and Deptrac rules were updated too. |
- Add InputData::float() for numeric input values - Cover float strings, integers, defaults, nulls, and invalid values - Document float access with matching decimal validation examples Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
|
@memleakd Thank you for the update. I was thinking about this more, and have some additional thoughts regarding the behavior of The idea is to split typed input access by trust level:
$request->getQueryInput()->integer('page', 1);If the value is missing or cannot be read as an integer, it can return the default. This is useful for pagination, filters, UI toggles, etc.
$validation->getValidatedInput()->integer('page', 1);Here, missing values can still use the default, and null can stay null, but an invalid value should throw. So: raw input access is forgiving convenience, validated input access is a strict contract. I was thinking about something like this: public function integer(string $key, ?int $default = null): ?int
{
if (! $this->has($key)) {
return $default;
}
$value = $this->get($key);
if ($value === null || is_int($value)) {
return $value;
}
if (is_string($value)) {
$integer = filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
if ($integer !== null) {
return $integer;
}
}
return $this->invalidValue($key, 'integer', $default);
}Then |
- Return defaults for invalid raw InputData values - Keep ValidatedInput strict through an override - Cover fallback and strict behavior in tests - Document the raw versus validated input trust levels Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
|
Thanks again for thinking this through and explaining the direction so clearly. I updated the PR to follow that model. The split feels much cleaner now: I also added tests around both behaviors and a short docs note so the distinction is clear. |
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
Description
This PR proposes typed access to validated data through a dedicated
ValidatedInputobject.It builds on the new FormRequest feature while keeping FormRequest focused on its core responsibilities: validation rules, authorization, input preparation, and failure handling.
The same API is available after using the Validation service directly:
Design
Following the review discussion, typed access is split by responsibility:
InputDatais a generic typed container for keyed input-like data. It provides simple accessors such asget(),has(),string(),integer(),float(),boolean(), andarray().InputDatais intentionally fallback-friendly for raw input: if a present value cannot be read as the requested type, the default value is returned.ValidatedInputextendsInputDatawith validation-specific accessors such asdate()andenum().ValidatedInputis intentionally strict: missing values may still use defaults and explicitnullstaysnull, but invalid present values throw anInvalidArgumentExceptionbecause the data has already passed validation.This keeps the current PR focused on validated data, while leaving room for future explicit request-source APIs such as
getPostInput()orgetQueryInput()to reuse the genericInputDataobject without making Validation depend on HTTP.Behavior
The existing array-based APIs remain unchanged:
The new typed APIs are optional:
Typed methods read from validated data only. They do not replace validation rules.
Missing optional fields return the provided default value, or
nullwhen no default is provided. Fields present with anullvalue returnnull.Services
This PR adds non-shared services for both typed input objects:
They are used internally by
Validation::getValidatedInput()andFormRequest::validatedInput(). Since these objects wrap a specific data array, they are non-shared by default.Notes
This adds
ValidationInterface::getValidatedInput(), so custom implementations of the interface will need to add the new method. The changelog lists this under interface changes.Docs were updated so FormRequest shows concise controller usage, while the Validation docs provide the canonical behavior reference for typed validated input.
Checklist: