Skip to content

Commit ae2abf6

Browse files
Feature/form request strict mode (laravel#59430)
* strict mode for validation * delete validation.php file * formated * formatting * formatting * more tests --------- Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 23b8cc8 commit ae2abf6

3 files changed

Lines changed: 446 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Illuminate\Foundation\Http\Attributes;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_CLASS)]
8+
class FailOnUnknownFields
9+
{
10+
public function __construct(public bool $value = true)
11+
{
12+
}
13+
}

src/Illuminate/Foundation/Http/FormRequest.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
use Illuminate\Contracts\Validation\ValidatesWhenResolved;
1010
use Illuminate\Contracts\Validation\Validator;
1111
use Illuminate\Foundation\Http\Attributes\ErrorBag;
12+
use Illuminate\Foundation\Http\Attributes\FailOnUnknownFields;
1213
use Illuminate\Foundation\Http\Attributes\RedirectTo;
1314
use Illuminate\Foundation\Http\Attributes\RedirectToRoute;
1415
use Illuminate\Foundation\Http\Attributes\StopOnFirstFailure;
1516
use Illuminate\Http\Request;
1617
use Illuminate\Routing\Redirector;
18+
use Illuminate\Support\Arr;
1719
use Illuminate\Validation\ValidatesWhenResolvedTrait;
1820
use ReflectionClass;
1921

@@ -77,6 +79,13 @@ class FormRequest extends Request implements ValidatesWhenResolved
7779
*/
7880
protected $validator;
7981

82+
/**
83+
* Indicates if unknown fields should be rejected for all form requests.
84+
*
85+
* @var bool
86+
*/
87+
protected static bool $globalFailOnUnknownFields = false;
88+
8089
/**
8190
* Get the validator instance for the request.
8291
*
@@ -109,6 +118,12 @@ protected function getValidatorInstance()
109118
));
110119
}
111120

121+
if ($this->shouldFailOnUnknownFields()) {
122+
$validator->after(function (Validator $validator) {
123+
$this->validateNoUnknownFields($validator);
124+
});
125+
}
126+
112127
$this->setValidator($validator);
113128

114129
return $this->validator;
@@ -144,6 +159,7 @@ protected function configureFromAttributes()
144159
if (count($errorBag) > 0) {
145160
$this->errorBag = $errorBag[0]->newInstance()->name;
146161
}
162+
147163
}
148164

149165
/**
@@ -192,6 +208,66 @@ protected function validationRules()
192208
return method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : [];
193209
}
194210

211+
/**
212+
* Determine if fields not present in rules should fail validation.
213+
*
214+
* @return bool
215+
*/
216+
protected function shouldFailOnUnknownFields(): bool
217+
{
218+
$failOnUnknownFields = (new ReflectionClass($this))->getAttributes(FailOnUnknownFields::class);
219+
220+
return $failOnUnknownFields !== []
221+
? $failOnUnknownFields[0]->newInstance()->value
222+
: static::$globalFailOnUnknownFields;
223+
}
224+
225+
/**
226+
* Validate that no unknown fields were sent as input.
227+
*
228+
* @param \Illuminate\Contracts\Validation\Validator $validator
229+
* @return void
230+
*/
231+
protected function validateNoUnknownFields(Validator $validator): void
232+
{
233+
$allowedKeys = array_keys($this->validationRules());
234+
235+
foreach (array_keys(Arr::dot($this->all())) as $inputKey) {
236+
if (! $this->isKnownField($inputKey, $allowedKeys)) {
237+
$validator->errors()->add($inputKey, trans('validation.prohibited', [
238+
'attribute' => str_replace('_', ' ', $inputKey),
239+
]));
240+
}
241+
}
242+
}
243+
244+
/**
245+
* Determine if the given input key is an allowed key based on the validation rules.
246+
*
247+
* @param string $inputKey
248+
* @param array $allowedKeys
249+
* @return bool
250+
*/
251+
protected function isKnownField(string $inputKey, array $allowedKeys): bool
252+
{
253+
foreach ($allowedKeys as $ruleKey) {
254+
if ($ruleKey === $inputKey) {
255+
return true;
256+
}
257+
258+
if (str_contains($ruleKey, '*')) {
259+
$pattern = '/^'.str_replace('\*', '[^.]+', preg_quote($ruleKey, '/')).'$/';
260+
261+
if (preg_match($pattern, $inputKey)) {
262+
return true;
263+
}
264+
}
265+
266+
}
267+
268+
return false;
269+
}
270+
195271
/**
196272
* Handle a failed validation attempt.
197273
*
@@ -304,6 +380,17 @@ public function attributes()
304380
return [];
305381
}
306382

383+
/**
384+
* Enable or disable unknown-field rejection globally for all form requests.
385+
*
386+
* @param bool $value
387+
* @return void
388+
*/
389+
public static function failOnUnknownFields(bool $value = true): void
390+
{
391+
static::$globalFailOnUnknownFields = $value;
392+
}
393+
307394
/**
308395
* Set the Validator instance.
309396
*

0 commit comments

Comments
 (0)