Skip to content

Commit 832ebc0

Browse files
[11.x] Add fluent Email validation rule (#54067)
* Add fluent `Email` validation rule * Fix style ci * add descriptive method names for email validation rules * Apply review suggestions * fix typo * formatting * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent ab7cdf6 commit 832ebc0

File tree

5 files changed

+790
-2
lines changed

5 files changed

+790
-2
lines changed

src/Illuminate/Validation/Rule.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Validation\Rules\ArrayRule;
88
use Illuminate\Validation\Rules\Can;
99
use Illuminate\Validation\Rules\Dimensions;
10+
use Illuminate\Validation\Rules\Email;
1011
use Illuminate\Validation\Rules\Enum;
1112
use Illuminate\Validation\Rules\ExcludeIf;
1213
use Illuminate\Validation\Rules\Exists;
@@ -169,6 +170,16 @@ public static function prohibitedIf($callback)
169170
return new ProhibitedIf($callback);
170171
}
171172

173+
/**
174+
* Get an email rule builder instance.
175+
*
176+
* @return \Illuminate\Validation\Rules\Email
177+
*/
178+
public static function email()
179+
{
180+
return new Email;
181+
}
182+
172183
/**
173184
* Get an enum rule builder instance.
174185
*
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
<?php
2+
3+
namespace Illuminate\Validation\Rules;
4+
5+
use Egulias\EmailValidator\EmailValidator;
6+
use Egulias\EmailValidator\Validation\DNSCheckValidation;
7+
use Egulias\EmailValidator\Validation\Extra\SpoofCheckValidation;
8+
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
9+
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
10+
use Egulias\EmailValidator\Validation\RFCValidation;
11+
use Illuminate\Container\Container;
12+
use Illuminate\Contracts\Validation\DataAwareRule;
13+
use Illuminate\Contracts\Validation\Rule;
14+
use Illuminate\Contracts\Validation\ValidatorAwareRule;
15+
use Illuminate\Support\Arr;
16+
use Illuminate\Support\Collection;
17+
use Illuminate\Support\Facades\Validator;
18+
use Illuminate\Support\Traits\Conditionable;
19+
use Illuminate\Support\Traits\Macroable;
20+
use Illuminate\Validation\Concerns\FilterEmailValidation;
21+
use InvalidArgumentException;
22+
23+
class Email implements Rule, DataAwareRule, ValidatorAwareRule
24+
{
25+
use Conditionable, Macroable;
26+
27+
public bool $validateMxRecord = false;
28+
public bool $preventSpoofing = false;
29+
public bool $nativeValidation = false;
30+
public bool $nativeValidationWithUnicodeAllowed = false;
31+
public bool $rfcCompliant = false;
32+
public bool $strictRfcCompliant = false;
33+
34+
/**
35+
* The validator performing the validation.
36+
*
37+
* @var \Illuminate\Validation\Validator
38+
*/
39+
protected $validator;
40+
41+
/**
42+
* The data under validation.
43+
*
44+
* @var array
45+
*/
46+
protected $data;
47+
48+
/**
49+
* An array of custom rules that will be merged into the validation rules.
50+
*
51+
* @var array
52+
*/
53+
protected $customRules = [];
54+
55+
/**
56+
* The error message after validation, if any.
57+
*
58+
* @var array
59+
*/
60+
protected $messages = [];
61+
62+
/**
63+
* The callback that will generate the "default" version of the file rule.
64+
*
65+
* @var string|array|callable|null
66+
*/
67+
public static $defaultCallback;
68+
69+
/**
70+
* Set the default callback to be used for determining the email default rules.
71+
*
72+
* If no arguments are passed, the default email rule configuration will be returned.
73+
*
74+
* @param static|callable|null $callback
75+
* @return static|void
76+
*/
77+
public static function defaults($callback = null)
78+
{
79+
if (is_null($callback)) {
80+
return static::default();
81+
}
82+
83+
if (! is_callable($callback) && ! $callback instanceof static) {
84+
throw new InvalidArgumentException('The given callback should be callable or an instance of '.static::class);
85+
}
86+
87+
static::$defaultCallback = $callback;
88+
}
89+
90+
/**
91+
* Get the default configuration of the file rule.
92+
*
93+
* @return static
94+
*/
95+
public static function default()
96+
{
97+
$email = is_callable(static::$defaultCallback)
98+
? call_user_func(static::$defaultCallback)
99+
: static::$defaultCallback;
100+
101+
return $email instanceof static ? $email : new static;
102+
}
103+
104+
/**
105+
* Ensure that the email is an RFC compliant email address.
106+
*
107+
* @param bool $strict
108+
* @return $this
109+
*/
110+
public function rfcCompliant(bool $strict = false)
111+
{
112+
if ($strict) {
113+
$this->strictRfcCompliant = true;
114+
} else {
115+
$this->rfcCompliant = true;
116+
}
117+
118+
return $this;
119+
}
120+
121+
/**
122+
* Ensure that the email is a strictly enforced RFC compliant email address.
123+
*
124+
* @return $this
125+
*/
126+
public function strict()
127+
{
128+
return $this->rfcCompliant(true);
129+
}
130+
131+
/**
132+
* Ensure that the email address has a valid MX record.
133+
*
134+
* Requires the PHP intl extension.
135+
*
136+
* @return $this
137+
*/
138+
public function validateMxRecord()
139+
{
140+
$this->validateMxRecord = true;
141+
142+
return $this;
143+
}
144+
145+
/**
146+
* Ensure that the email address is not attempting to spoof another email address using invalid unicode characters.
147+
*
148+
* @return $this
149+
*/
150+
public function preventSpoofing()
151+
{
152+
$this->preventSpoofing = true;
153+
154+
return $this;
155+
}
156+
157+
/**
158+
* Ensure the email address is valid using PHP's native email validation functions.
159+
*
160+
* @param bool $allowUnicode
161+
* @return $this
162+
*/
163+
public function withNativeValidation(bool $allowUnicode = false)
164+
{
165+
if ($allowUnicode) {
166+
$this->nativeValidationWithUnicodeAllowed = true;
167+
} else {
168+
$this->nativeValidation = true;
169+
}
170+
171+
return $this;
172+
}
173+
174+
/**
175+
* Specify additional validation rules that should be merged with the default rules during validation.
176+
*
177+
* @param string|array $rules
178+
* @return $this
179+
*/
180+
public function rules($rules)
181+
{
182+
$this->customRules = array_merge($this->customRules, Arr::wrap($rules));
183+
184+
return $this;
185+
}
186+
187+
/**
188+
* Determine if the validation rule passes.
189+
*
190+
* @param string $attribute
191+
* @param mixed $value
192+
* @return bool
193+
*/
194+
public function passes($attribute, $value)
195+
{
196+
$this->messages = [];
197+
198+
if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
199+
return false;
200+
}
201+
202+
$emailValidator = Container::getInstance()->make(EmailValidator::class);
203+
204+
$passes = $emailValidator->isValid((string) $value, new MultipleValidationWithAnd($this->buildValidationRules()));
205+
206+
if (! $passes) {
207+
$this->messages = [trans('validation.email', ['attribute' => $attribute])];
208+
209+
return false;
210+
}
211+
212+
if ($this->customRules) {
213+
$validator = Validator::make(
214+
$this->data,
215+
[$attribute => $this->customRules],
216+
$this->validator->customMessages,
217+
$this->validator->customAttributes
218+
);
219+
220+
if ($validator->fails()) {
221+
return $this->fail($validator->messages()->all());
222+
}
223+
}
224+
225+
return true;
226+
}
227+
228+
/**
229+
* Build the array of underlying validation rules based on the current state.
230+
*
231+
* @return array
232+
*/
233+
protected function buildValidationRules()
234+
{
235+
$rules = [];
236+
237+
if ($this->rfcCompliant) {
238+
$rules[] = new RFCValidation;
239+
}
240+
241+
if ($this->strictRfcCompliant) {
242+
$rules[] = new NoRFCWarningsValidation;
243+
}
244+
245+
if ($this->validateMxRecord) {
246+
$rules[] = new DNSCheckValidation;
247+
}
248+
249+
if ($this->preventSpoofing) {
250+
$rules[] = new SpoofCheckValidation;
251+
}
252+
253+
if ($this->nativeValidation) {
254+
$rules[] = new FilterEmailValidation;
255+
}
256+
257+
if ($this->nativeValidationWithUnicodeAllowed) {
258+
$rules[] = FilterEmailValidation::unicode();
259+
}
260+
261+
if ($rules) {
262+
return $rules;
263+
}
264+
265+
return [new RFCValidation];
266+
}
267+
268+
/**
269+
* Adds the given failures, and return false.
270+
*
271+
* @param array|string $messages
272+
* @return bool
273+
*/
274+
protected function fail($messages)
275+
{
276+
$messages = Collection::wrap($messages)
277+
->map(fn ($message) => $this->validator->getTranslator()->get($message))
278+
->all();
279+
280+
$this->messages = array_merge($this->messages, $messages);
281+
282+
return false;
283+
}
284+
285+
/**
286+
* Get the validation error message.
287+
*
288+
* @return array
289+
*/
290+
public function message()
291+
{
292+
return $this->messages;
293+
}
294+
295+
/**
296+
* Set the current validator.
297+
*
298+
* @param \Illuminate\Contracts\Validation\Validator $validator
299+
* @return $this
300+
*/
301+
public function setValidator($validator)
302+
{
303+
$this->validator = $validator;
304+
305+
return $this;
306+
}
307+
308+
/**
309+
* Set the current data under validation.
310+
*
311+
* @param array $data
312+
* @return $this
313+
*/
314+
public function setData($data)
315+
{
316+
$this->data = $data;
317+
318+
return $this;
319+
}
320+
}

src/Illuminate/Validation/Rules/File.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class File implements Rule, DataAwareRule, ValidatorAwareRule
8686
* If no arguments are passed, the default file rule configuration will be returned.
8787
*
8888
* @param static|callable|null $callback
89-
* @return static|null
89+
* @return static|void
9090
*/
9191
public static function defaults($callback = null)
9292
{

src/Illuminate/Validation/Rules/Password.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public function __construct($min)
124124
* If no arguments are passed, the default password rule configuration will be returned.
125125
*
126126
* @param static|callable|null $callback
127-
* @return static|null
127+
* @return static|void
128128
*/
129129
public static function defaults($callback = null)
130130
{

0 commit comments

Comments
 (0)