Skip to content

Commit 7997dc5

Browse files
committed
refactor(input): split raw and validated input behavior
- 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>
1 parent c8dc6f1 commit 7997dc5

5 files changed

Lines changed: 79 additions & 48 deletions

File tree

system/Input/InputData.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414
namespace CodeIgniter\Input;
1515

16-
use CodeIgniter\Exceptions\InvalidArgumentException;
17-
1816
/**
1917
* @see \CodeIgniter\Input\InputDataTest
2018
*/
@@ -69,7 +67,7 @@ public function string(string $key, ?string $default = null): ?string
6967
return $value;
7068
}
7169

72-
throw $this->invalidType($key, 'string');
70+
return $this->invalidValue($key, 'string', $default);
7371
}
7472

7573
/**
@@ -93,7 +91,7 @@ public function integer(string $key, ?int $default = null): ?int
9391
}
9492
}
9593

96-
throw $this->invalidType($key, 'integer');
94+
return $this->invalidValue($key, 'integer', $default);
9795
}
9896

9997
/**
@@ -121,7 +119,7 @@ public function float(string $key, ?float $default = null): ?float
121119
}
122120
}
123121

124-
throw $this->invalidType($key, 'float');
122+
return $this->invalidValue($key, 'float', $default);
125123
}
126124

127125
/**
@@ -145,7 +143,7 @@ public function boolean(string $key, ?bool $default = null): ?bool
145143
}
146144
}
147145

148-
throw $this->invalidType($key, 'boolean');
146+
return $this->invalidValue($key, 'boolean', $default);
149147
}
150148

151149
/**
@@ -165,13 +163,11 @@ public function array(string $key, ?array $default = null): ?array
165163
return $value;
166164
}
167165

168-
throw $this->invalidType($key, 'array');
166+
return $this->invalidValue($key, 'array', $default);
169167
}
170168

171-
protected function invalidType(string $key, string $type): InvalidArgumentException
169+
protected function invalidValue(string $key, string $type, mixed $default): mixed
172170
{
173-
return new InvalidArgumentException(
174-
sprintf('The input "%s" value cannot be read as %s.', $key, $type),
175-
);
171+
return $default;
176172
}
177173
}

system/Validation/ValidatedInput.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function date(
4343
}
4444

4545
if (! is_string($value) || $value === '') {
46-
throw $this->invalidType($key, 'date');
46+
return $this->invalidValue($key, 'date', null);
4747
}
4848

4949
try {
@@ -53,7 +53,7 @@ public function date(
5353

5454
return Time::createFromFormat($format, $value, $timezone);
5555
} catch (Exception) {
56-
throw $this->invalidType($key, 'date');
56+
return $this->invalidValue($key, 'date', null);
5757
}
5858
}
5959

@@ -76,7 +76,7 @@ public function enum(string $key, string $enumClass, ?UnitEnum $default = null):
7676
}
7777

7878
if ($default instanceof UnitEnum && ! $default instanceof $enumClass) {
79-
throw $this->invalidType($key, $enumClass);
79+
return $this->invalidValue($key, $enumClass, $default);
8080
}
8181

8282
$value = $this->get($key, $default);
@@ -90,7 +90,7 @@ public function enum(string $key, string $enumClass, ?UnitEnum $default = null):
9090
return $value;
9191
}
9292

93-
throw $this->invalidType($key, $enumClass);
93+
return $this->invalidValue($key, $enumClass, $default);
9494
}
9595

9696
$reflection = new ReflectionEnum($enumClass);
@@ -107,7 +107,7 @@ public function enum(string $key, string $enumClass, ?UnitEnum $default = null):
107107
}
108108
}
109109

110-
throw $this->invalidType($key, $enumClass);
110+
return $this->invalidValue($key, $enumClass, $default);
111111
}
112112

113113
private function backedEnum(string $key, string $enumClass, ReflectionEnum $reflection, mixed $value): UnitEnum
@@ -120,10 +120,10 @@ private function backedEnum(string $key, string $enumClass, ReflectionEnum $refl
120120
}
121121

122122
if (! is_int($value)) {
123-
throw $this->invalidType($key, $enumClass);
123+
return $this->invalidValue($key, $enumClass, null);
124124
}
125125
} elseif (! is_int($value) && ! is_string($value)) {
126-
throw $this->invalidType($key, $enumClass);
126+
return $this->invalidValue($key, $enumClass, null);
127127
}
128128

129129
if ($backingType === 'string') {
@@ -133,15 +133,15 @@ private function backedEnum(string $key, string $enumClass, ReflectionEnum $refl
133133
$enum = $enumClass::tryFrom($value);
134134

135135
if ($enum === null) {
136-
throw $this->invalidType($key, $enumClass);
136+
return $this->invalidValue($key, $enumClass, null);
137137
}
138138

139139
return $enum;
140140
}
141141

142-
protected function invalidType(string $key, string $type): InvalidArgumentException
142+
protected function invalidValue(string $key, string $type, mixed $default): never
143143
{
144-
return new InvalidArgumentException(
144+
throw new InvalidArgumentException(
145145
sprintf('The validated "%s" value cannot be read as %s.', $key, $type),
146146
);
147147
}

tests/system/Input/InputDataTest.php

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
namespace CodeIgniter\Input;
1515

16-
use CodeIgniter\Exceptions\InvalidArgumentException;
1716
use CodeIgniter\Test\CIUnitTestCase;
1817
use PHPUnit\Framework\Attributes\Group;
1918

@@ -73,14 +72,11 @@ public function testStringReturnsDefaultForMissingInputField(): void
7372
$this->assertSame('Untitled', $input->string('title', 'Untitled'));
7473
}
7574

76-
public function testStringThrowsForInvalidInputValue(): void
75+
public function testStringReturnsDefaultForInvalidInputValue(): void
7776
{
7877
$input = new InputData(['title' => 123]);
7978

80-
$this->expectException(InvalidArgumentException::class);
81-
$this->expectExceptionMessage('The input "title" value cannot be read as string.');
82-
83-
$input->string('title');
79+
$this->assertSame('Untitled', $input->string('title', 'Untitled'));
8480
}
8581

8682
public function testIntegerReturnsInputInteger(): void
@@ -104,14 +100,11 @@ public function testIntegerSupportsDotSyntax(): void
104100
$this->assertSame(2, $input->integer('filters.page'));
105101
}
106102

107-
public function testIntegerThrowsForInvalidInputValue(): void
103+
public function testIntegerReturnsDefaultForInvalidInputValue(): void
108104
{
109105
$input = new InputData(['page' => '1.5']);
110106

111-
$this->expectException(InvalidArgumentException::class);
112-
$this->expectExceptionMessage('The input "page" value cannot be read as integer.');
113-
114-
$input->integer('page');
107+
$this->assertSame(1, $input->integer('page', 1));
115108
}
116109

117110
public function testFloatReturnsInputFloat(): void
@@ -135,14 +128,11 @@ public function testFloatReturnsDefaultForMissingInputField(): void
135128
$this->assertEqualsWithDelta(1.5, $input->float('price', 1.5), PHP_FLOAT_EPSILON);
136129
}
137130

138-
public function testFloatThrowsForInvalidInputValue(): void
131+
public function testFloatReturnsDefaultForInvalidInputValue(): void
139132
{
140133
$input = new InputData(['price' => 'free']);
141134

142-
$this->expectException(InvalidArgumentException::class);
143-
$this->expectExceptionMessage('The input "price" value cannot be read as float.');
144-
145-
$input->float('price');
135+
$this->assertEqualsWithDelta(1.5, $input->float('price', 1.5), PHP_FLOAT_EPSILON);
146136
}
147137

148138
public function testBooleanReturnsInputBoolean(): void
@@ -166,14 +156,11 @@ public function testBooleanReturnsDefaultForMissingInputField(): void
166156
$this->assertFalse($input->boolean('active', false));
167157
}
168158

169-
public function testBooleanThrowsForInvalidInputValue(): void
159+
public function testBooleanReturnsDefaultForInvalidInputValue(): void
170160
{
171161
$input = new InputData(['active' => 'sometimes']);
172162

173-
$this->expectException(InvalidArgumentException::class);
174-
$this->expectExceptionMessage('The input "active" value cannot be read as boolean.');
175-
176-
$input->boolean('active');
163+
$this->assertFalse($input->boolean('active', false));
177164
}
178165

179166
public function testArrayReturnsInputArray(): void
@@ -190,14 +177,11 @@ public function testArrayReturnsDefaultForMissingInputField(): void
190177
$this->assertSame(['draft'], $input->array('tags', ['draft']));
191178
}
192179

193-
public function testArrayThrowsForInvalidInputValue(): void
180+
public function testArrayReturnsDefaultForInvalidInputValue(): void
194181
{
195182
$input = new InputData(['tags' => 'php']);
196183

197-
$this->expectException(InvalidArgumentException::class);
198-
$this->expectExceptionMessage('The input "tags" value cannot be read as array.');
199-
200-
$input->array('tags');
184+
$this->assertSame(['draft'], $input->array('tags', ['draft']));
201185
}
202186

203187
public function testTypedAccessorsReturnNullForNullInputFields(): void

tests/system/Validation/ValidatedInputTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,56 @@ public function testDateThrowsForInvalidValidatedValue(): void
6161
$input->date('published_at');
6262
}
6363

64+
public function testStringThrowsForInvalidValidatedValue(): void
65+
{
66+
$input = new ValidatedInput(['title' => 123]);
67+
68+
$this->expectException(InvalidArgumentException::class);
69+
$this->expectExceptionMessage('The validated "title" value cannot be read as string.');
70+
71+
$input->string('title', 'Untitled');
72+
}
73+
74+
public function testIntegerThrowsForInvalidValidatedValue(): void
75+
{
76+
$input = new ValidatedInput(['page' => '1.5']);
77+
78+
$this->expectException(InvalidArgumentException::class);
79+
$this->expectExceptionMessage('The validated "page" value cannot be read as integer.');
80+
81+
$input->integer('page', 1);
82+
}
83+
84+
public function testFloatThrowsForInvalidValidatedValue(): void
85+
{
86+
$input = new ValidatedInput(['price' => 'free']);
87+
88+
$this->expectException(InvalidArgumentException::class);
89+
$this->expectExceptionMessage('The validated "price" value cannot be read as float.');
90+
91+
$input->float('price', 1.5);
92+
}
93+
94+
public function testBooleanThrowsForInvalidValidatedValue(): void
95+
{
96+
$input = new ValidatedInput(['active' => 'sometimes']);
97+
98+
$this->expectException(InvalidArgumentException::class);
99+
$this->expectExceptionMessage('The validated "active" value cannot be read as boolean.');
100+
101+
$input->boolean('active', false);
102+
}
103+
104+
public function testArrayThrowsForInvalidValidatedValue(): void
105+
{
106+
$input = new ValidatedInput(['tags' => 'php']);
107+
108+
$this->expectException(InvalidArgumentException::class);
109+
$this->expectExceptionMessage('The validated "tags" value cannot be read as array.');
110+
111+
$input->array('tags', ['draft']);
112+
}
113+
64114
public function testEnumReturnsValidatedStringBackedEnum(): void
65115
{
66116
$input = new ValidatedInput(['status' => 'active']);

user_guide_src/source/libraries/validation.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,8 @@ This usually means the validation rules and the expected type do not match.
500500

501501
``ValidatedInput`` extends ``CodeIgniter\Input\InputData``. This keeps generic
502502
typed input access reusable while adding validation-specific readers for dates
503-
and enums.
503+
and enums. ``InputData`` is fallback-friendly for raw input; ``ValidatedInput``
504+
is strict because it represents data that has already passed validation.
504505

505506
The typed input object has the following methods:
506507

0 commit comments

Comments
 (0)