Skip to content

Commit e30bdf0

Browse files
[9.x] Introduce Invokable validation classes (#42689)
* Introduce Invokable validation classes * style ci * formatting Co-authored-by: Taylor Otwell <[email protected]>
1 parent 32b4b7c commit e30bdf0

File tree

5 files changed

+534
-0
lines changed

5 files changed

+534
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Illuminate\Contracts\Validation;
4+
5+
interface InvokableRule
6+
{
7+
/**
8+
* Run the validation rule.
9+
*
10+
* @param string $attribute
11+
* @param mixed $value
12+
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
13+
* @return void
14+
*/
15+
public function __invoke($attribute, $value, $fail);
16+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Illuminate\Translation;
4+
5+
use RuntimeException;
6+
use Stringable;
7+
8+
class PotentiallyTranslatedString implements Stringable
9+
{
10+
/**
11+
* The string that may be translated.
12+
*
13+
* @var string
14+
*/
15+
protected $string;
16+
17+
/**
18+
* The translated string.
19+
*
20+
* @var string|null
21+
*/
22+
protected $translation;
23+
24+
/**
25+
* The validator that may perform the translation.
26+
*
27+
* @var \Illuminate\Contracts\Translation\Translator
28+
*/
29+
protected $translator;
30+
31+
/**
32+
* Create a new potentially translated string.
33+
*
34+
* @param string $string
35+
* @param \Illuminate\Contracts\Translation\Translator $translator
36+
*/
37+
public function __construct($string, $translator)
38+
{
39+
$this->string = $string;
40+
41+
$this->translator = $translator;
42+
}
43+
44+
/**
45+
* Translate the string.
46+
*
47+
* @return $this
48+
*/
49+
public function translate()
50+
{
51+
if (! $this->translator->has($this->string)) {
52+
throw new RuntimeException("Unable to find translation [{$this->string}] for locale [{$this->translator->getLocale()}].");
53+
}
54+
55+
$this->translation = $this->translator->get($this->string);
56+
57+
return $this;
58+
}
59+
60+
/**
61+
* Get the original string.
62+
*
63+
* @return string
64+
*/
65+
public function original()
66+
{
67+
return $this->string;
68+
}
69+
70+
/**
71+
* Get the potentially translated string.
72+
*
73+
* @return string
74+
*/
75+
public function __toString()
76+
{
77+
return $this->translation ?? $this->string;
78+
}
79+
80+
/**
81+
* Get the potentially translated string.
82+
*
83+
* @return string
84+
*/
85+
public function toString()
86+
{
87+
return (string) $this;
88+
}
89+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<?php
2+
3+
namespace Illuminate\Validation;
4+
5+
use Illuminate\Contracts\Validation\DataAwareRule;
6+
use Illuminate\Contracts\Validation\ImplicitRule;
7+
use Illuminate\Contracts\Validation\InvokableRule;
8+
use Illuminate\Contracts\Validation\Rule;
9+
use Illuminate\Contracts\Validation\ValidatorAwareRule;
10+
use Illuminate\Translation\PotentiallyTranslatedString;
11+
12+
class InvokableValidationRule implements Rule, ValidatorAwareRule
13+
{
14+
/**
15+
* The invokable that validates the attribute.
16+
*
17+
* @var \Illuminate\Contracts\Validation\InvokableRule
18+
*/
19+
protected $invokable;
20+
21+
/**
22+
* Indicates if the validation invokable failed.
23+
*
24+
* @var bool
25+
*/
26+
protected $failed = false;
27+
28+
/**
29+
* The validation error messages.
30+
*
31+
* @var array
32+
*/
33+
protected $messages = [];
34+
35+
/**
36+
* The current validator.
37+
*
38+
* @var \Illuminate\Validation\Validator
39+
*/
40+
protected $validator;
41+
42+
/**
43+
* The data under validation.
44+
*
45+
* @var array
46+
*/
47+
protected $data = [];
48+
49+
/**
50+
* Create a new explicit Invokable validation rule.
51+
*
52+
* @param \Illuminate\Contracts\Validation\InvokableRule $invokable
53+
* @return void
54+
*/
55+
protected function __construct(InvokableRule $invokable)
56+
{
57+
$this->invokable = $invokable;
58+
}
59+
60+
/**
61+
* Create a new implicit or explicit Invokable validation rule.
62+
*
63+
* @param \Illuminate\Contracts\Validation\InvokableRule $invokable
64+
* @return \Illuminate\Contracts\Validation\ImplicitRule
65+
*/
66+
public static function make($invokable)
67+
{
68+
if ($invokable->implicit ?? false) {
69+
return new class($invokable) extends InvokableValidationRule implements ImplicitRule
70+
{
71+
//
72+
};
73+
}
74+
75+
return new InvokableValidationRule($invokable);
76+
}
77+
78+
/**
79+
* Determine if the validation rule passes.
80+
*
81+
* @param string $attribute
82+
* @param mixed $value
83+
* @return bool
84+
*/
85+
public function passes($attribute, $value)
86+
{
87+
$this->failed = false;
88+
89+
if ($this->invokable instanceof DataAwareRule) {
90+
$this->invokable->setData($this->validator->getData());
91+
}
92+
93+
if ($this->invokable instanceof ValidatorAwareRule) {
94+
$this->invokable->setValidator($this->validator);
95+
}
96+
97+
$this->invokable->__invoke($attribute, $value, function ($message) {
98+
$this->failed = true;
99+
100+
return $this->pendingPotentiallyTranslatedString($message);
101+
});
102+
103+
return ! $this->failed;
104+
}
105+
106+
/**
107+
* Get the validation error messages.
108+
*
109+
* @return array
110+
*/
111+
public function message()
112+
{
113+
return $this->messages;
114+
}
115+
116+
/**
117+
* Set the data under validation.
118+
*
119+
* @param array $data
120+
* @return $this
121+
*/
122+
public function setData($data)
123+
{
124+
$this->data = $data;
125+
126+
return $this;
127+
}
128+
129+
/**
130+
* Set the current validator.
131+
*
132+
* @param \Illuminate\Validation\Validator $validator
133+
* @return $this
134+
*/
135+
public function setValidator($validator)
136+
{
137+
$this->validator = $validator;
138+
139+
return $this;
140+
}
141+
142+
/**
143+
* Create a pending potentially translated string.
144+
*
145+
* @param string $message
146+
* @return \Illuminate\Translation\PotentiallyTranslatedString
147+
*/
148+
protected function pendingPotentiallyTranslatedString($message)
149+
{
150+
return new class($message, $this->validator->getTranslator(), fn ($message) => $this->messages[] = $message) extends PotentiallyTranslatedString
151+
{
152+
/**
153+
* The callback to call when the object destructs.
154+
*
155+
* @var \Closure
156+
*/
157+
protected $destructor;
158+
159+
/**
160+
* Create a new pending potentially translated string.
161+
*
162+
* @param string $string
163+
* @param \Illuminate\Contracts\Translation\Translator $translator
164+
* @param \Closure $destructor
165+
*/
166+
public function __construct($message, $translator, $destructor)
167+
{
168+
parent::__construct($message, $translator);
169+
170+
$this->destructor = $destructor;
171+
}
172+
173+
/**
174+
* Handle the object's destruction.
175+
*
176+
* @return void
177+
*/
178+
public function __destruct()
179+
{
180+
($this->destructor)($this->toString());
181+
}
182+
};
183+
}
184+
}

src/Illuminate/Validation/ValidationRuleParser.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Validation;
44

55
use Closure;
6+
use Illuminate\Contracts\Validation\InvokableRule;
67
use Illuminate\Contracts\Validation\Rule as RuleContract;
78
use Illuminate\Support\Arr;
89
use Illuminate\Support\Str;
@@ -114,6 +115,10 @@ protected function prepareRule($rule, $attribute)
114115
$rule = new ClosureValidationRule($rule);
115116
}
116117

118+
if ($rule instanceof InvokableRule) {
119+
$rule = InvokableValidationRule::make($rule);
120+
}
121+
117122
if (! is_object($rule) ||
118123
$rule instanceof RuleContract ||
119124
($rule instanceof Exists && $rule->queryCallbacks()) ||

0 commit comments

Comments
 (0)