Skip to content

Commit c41d0d3

Browse files
Class based after validation rules (#46757)
* Class based after validation rules * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 1c2eef8 commit c41d0d3

File tree

4 files changed

+153
-1
lines changed

4 files changed

+153
-1
lines changed

src/Illuminate/Foundation/Http/FormRequest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ protected function getValidatorInstance()
9696
$this->withValidator($validator);
9797
}
9898

99+
if (method_exists($this, 'after')) {
100+
$validator->after($this->container->call(
101+
$this->after(...),
102+
['validator' => $validator]
103+
));
104+
}
105+
99106
$this->setValidator($validator);
100107

101108
return $this->validator;

src/Illuminate/Validation/Validator.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,11 +383,19 @@ protected function replacePlaceholderInString(string $value)
383383
/**
384384
* Add an after validation callback.
385385
*
386-
* @param callable|string $callback
386+
* @param callable|array|string $callback
387387
* @return $this
388388
*/
389389
public function after($callback)
390390
{
391+
if (is_array($callback) && ! is_callable($callback)) {
392+
foreach ($callback as $rule) {
393+
$this->after(method_exists($rule, 'after') ? $rule->after(...) : $rule);
394+
}
395+
396+
return $this;
397+
}
398+
391399
$this->after[] = fn () => $callback($this);
392400

393401
return $this;

tests/Foundation/FoundationFormRequestTest.php

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,60 @@ public function testValidatedMethodReturnsOnlyRequestedNestedValidatedData()
149149
$this->assertSame('bar', $request->validated('nested.foo'));
150150
}
151151

152+
public function testAfterMethod()
153+
{
154+
$request = new class extends FormRequest {
155+
public $value = 'value-from-request';
156+
157+
public function rules()
158+
{
159+
return [];
160+
}
161+
162+
protected function failedValidation(Validator $validator)
163+
{
164+
throw new class ($validator) extends Exception {
165+
public function __construct(public $validator)
166+
{
167+
//
168+
}
169+
};
170+
}
171+
172+
public function after(InjectedDependency $dep)
173+
{
174+
return [
175+
new AfterValidationRule($dep->value),
176+
new InvokableAfterValidationRule($this->value),
177+
fn ($validator) => $validator->errors()->add('closure', 'true'),
178+
];
179+
}
180+
};
181+
$request->setContainer($container = new Container);
182+
$container->instance(\Illuminate\Contracts\Validation\Factory::class, (new \Illuminate\Validation\Factory(
183+
new \Illuminate\Translation\Translator(new \Illuminate\Translation\ArrayLoader(), 'en')
184+
))->setContainer($container));
185+
$container->instance(InjectedDependency::class, new InjectedDependency('value-from-dependency'));
186+
187+
$messages = [];
188+
189+
try {
190+
$request->validateResolved();
191+
$this->fail();
192+
} catch (Exception $e) {
193+
if (property_exists($e, 'validator')) {
194+
$messages = $e->validator->messages()->messages();
195+
}
196+
}
197+
198+
$this->assertSame([
199+
'after' => ['value-from-dependency'],
200+
'invokable' => ['value-from-request'],
201+
'closure' => ['true'],
202+
], $messages);
203+
}
204+
205+
152206
/**
153207
* Catch the given exception thrown from the executor, and return it.
154208
*
@@ -377,3 +431,36 @@ public function authorize()
377431
return Response::allow('baz');
378432
}
379433
}
434+
435+
class InvokableAfterValidationRule
436+
{
437+
public function __construct(private $value)
438+
{
439+
440+
}
441+
public function __invoke($validator)
442+
{
443+
$validator->errors()->add('invokable', $this->value);
444+
}
445+
}
446+
447+
class AfterValidationRule
448+
{
449+
public function __construct(private $value)
450+
{
451+
//
452+
}
453+
454+
public function after($validator)
455+
{
456+
$validator->errors()->add('after', $this->value);
457+
}
458+
}
459+
460+
class InjectedDependency
461+
{
462+
public function __construct(public $value)
463+
{
464+
//
465+
}
466+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Validation;
4+
5+
use Illuminate\Container\Container;
6+
use Illuminate\Translation\ArrayLoader;
7+
use Illuminate\Translation\Translator;
8+
use Illuminate\Validation\Validator;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class ValidatorAfterRuleTest extends TestCase
12+
{
13+
public function testAfterAcceptsArrayOfRules()
14+
{
15+
$validator = new Validator(new Translator(new ArrayLoader, 'en'), [], []);
16+
17+
$validator->after([
18+
fn ($validator) => $validator->errors()->add('closure', 'true'),
19+
new InvokableAfterRule,
20+
new AfterMethodRule,
21+
])->messages()->messages();
22+
23+
$this->assertSame($validator->messages()->messages(), [
24+
'closure' => ['true'],
25+
'invokableAfterRule' => ['true'],
26+
'afterMethodRule' => ['true'],
27+
]);
28+
}
29+
}
30+
31+
class InvokableAfterRule
32+
{
33+
public function __invoke($validator)
34+
{
35+
$validator->errors()->add('invokableAfterRule', 'true');
36+
}
37+
}
38+
39+
class AfterMethodRule
40+
{
41+
public function __invoke()
42+
{
43+
//
44+
}
45+
46+
public function after($validator)
47+
{
48+
$validator->errors()->add('afterMethodRule', 'true');
49+
}
50+
}

0 commit comments

Comments
 (0)