-
-
Save vmosoti/8ef268588cbdbf33f263e4733571254b to your computer and use it in GitHub Desktop.
<?php | |
// custom validator in laravel to validate comma separated emails. | |
\Validator::extend("emails", function($attribute, $values, $parameters) { | |
$value = explode(',', $values); | |
$rules = [ | |
'email' => 'required|email', | |
]; | |
if ($value) { | |
foreach ($value as $email) { | |
$data = [ | |
'email' => $email | |
]; | |
$validator = \Validator::make($data, $rules); | |
if ($validator->fails()) { | |
return false; | |
} | |
} | |
return true; | |
} | |
}); | |
// Custom message for that validation | |
// pass this array as third parameter in \Validator::make | |
array('emails' => ':attribute must have valid email addresses.'); | |
// Usage: | |
$rules['notifications'] = 'emails'; // 'emails' is name of new rule. |
@kartikkapatel solutions fails for nested attributes and there is no need to use "{$attribute}"
<?php
namespace App\Rules\Emails;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Support\Facades\Validator;
class CommaSeparatedEmails implements Rule
{
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return Validator::make(
[
"attribute" => explode(',', $value)
],
[
"attribute.*" => 'required|email'
]
)->passes();
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return 'The :attribute must have valid email addresses.';
}
}
now this works
use App\Rules\Emails\CommaSeparatedEmails;
$request->validate([
'some.nested.field' => ['nullable', new CommaSeparatedEmails],
]);
Nice snippet. I found a spatie's package which does something similar https://github.com/spatie/laravel-validation-rules#delimited
"attribute" => explode(',', $value)
can be further transformed to
"attribute" => array_map('trim', explode(',', $value))
if you want comma and space separated emails (which is more natural for some users) to be valid too.
Here is a Laravel 11.x compatible version of a similar rule I wrote for my projects.
This rule supports custom delimiters (defaults to support comma, space, newlines), as well as minimum + maximum number of e-mails. It works properly with nested attributes.
I have also provided the Pest Unit tests which are fairly robust.
Hopefully this helps somebody else out.
<?php
namespace App\Website\Account\Rules;
use Closure;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
use Illuminate\Contracts\Validation\ValidationRule;
class DelimitedEmails implements ValidationRule
{
protected array $delimiters;
protected ?int $minEmails;
protected ?int $maxEmails;
/**
* Constructor to accept optional custom delimiters and min/max email counts.
*
* @param array|string|null $delimiters Delimiters to split emails by (default: comma, space, newline).
* @param int|null $minEmails Minimum number of emails allowed.
* @param int|null $maxEmails Maximum number of emails allowed.
*/
public function __construct(array|string $delimiters = [',', ' ', "\n"], ?int $minEmails = null, ?int $maxEmails = null)
{
$this->delimiters = (array) $delimiters;
$this->minEmails = $minEmails;
$this->maxEmails = $maxEmails;
}
/**
* Validate the email list.
*
* @param string $attribute
* @param mixed $value
* @param Closure $fail
* @return void
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$emails = array_values($this->splitEmails($value));
if ($this->minEmails !== null && count($emails) < $this->minEmails) {
$pluralMinimumEmailsLabel = Str::plural('email', $this->minEmails);
$fail("The $attribute must contain at least {$this->minEmails} {$pluralMinimumEmailsLabel}.");
return;
}
if ($this->maxEmails !== null && count($emails) > $this->maxEmails) {
$pluralMaximumEmailsLabel = Str::plural('email', $this->maxEmails);
$fail("The $attribute must contain no more than {$this->maxEmails} $pluralMaximumEmailsLabel.");
return;
}
$data = [];
Arr::set($data, $attribute, $emails);
$validator = Validator::make(
$data,
["$attribute.*" => 'required|email']
);
if (!$validator->passes()) {
$fail("The $attribute must contain only valid email addresses.");
}
}
/**
* Split the email string by the provided delimiters.
*
* @param string $value
* @return array
*/
protected function splitEmails(string $value): array
{
$pattern = implode('|', array_map('preg_quote', $this->delimiters));
return array_filter(preg_split("/($pattern)/", $value), fn($email) => !empty($email));
}
}
Here are the Pest Unit tests:
<?php
use App\Website\Account\Rules\DelimitedEmails;
use Illuminate\Support\Facades\Validator;
it('validates a default comma-separated list of emails', function () {
expect(Validator::make(
['emails' => '[email protected], [email protected], [email protected]'],
['emails' => [new DelimitedEmails()]]
)->passes())->toBeTrue();
});
it('fails if invalid emails are present in default comma-separated list', function () {
expect(Validator::make(
['emails' => '[email protected], invalid-email, [email protected]'],
['emails' => [new DelimitedEmails()]]
)->passes())->toBeFalse();
});
it('validates with custom delimiters', function () {
expect(Validator::make(
['emails' => '[email protected];[email protected]|[email protected]'],
['emails' => [new DelimitedEmails([';', '|'])]]
)->passes())->toBeTrue();
});
it('fails when not enough emails are provided', function () {
$validator = Validator::make(
['emails' => '[email protected]'],
['emails' => [new DelimitedEmails(',', 3)]]
);
expect($validator->passes())->toBeFalse();
expect($validator->errors()->first('emails'))->toBe('The emails must contain at least 3 emails.');
});
it('fails when too many emails are provided', function () {
$validator = Validator::make(
['emails' => '[email protected], [email protected], [email protected]'],
['emails' => [new DelimitedEmails(',', 1, 2)]]
);
expect($validator->passes())->toBeFalse();
expect($validator->errors()->first('emails'))->toBe('The emails must contain no more than 2 emails.');
});
it('validates with newline-separated emails', function () {
expect(Validator::make(
['emails' => "[email protected]\n[email protected]\n[email protected]"],
['emails' => [new DelimitedEmails("\n")]]
)->passes())->toBeTrue();
});
it('validates with mixed delimiters', function () {
expect(Validator::make(
['emails' => "[email protected] [email protected], [email protected]\n[email protected]"],
['emails' => [new DelimitedEmails([',', ' ', "\n"])]]
)->passes())->toBeTrue();
});
dataset('invalidEmails', [
['[email protected], invalid-email'],
['invalid-email, invalid2@example'],
['[email protected];invalid-email;[email protected]'],
]);
it('fails when list contains invalid emails', function (string $emailList) {
expect(Validator::make(
['emails' => $emailList],
['emails' => [new DelimitedEmails([',', ';'])]]
)->passes())->toBeFalse();
})->with('invalidEmails');
it('validates the minimum and maximum number of emails', function () {
expect(Validator::make(
['emails' => '[email protected], [email protected], [email protected]'],
['emails' => [new DelimitedEmails([',', ' '], 2, 4)]]
)->passes())->toBeTrue();
});
it('validates emails from a nested attribute using dot notation', function () {
expect(Validator::make(
['data' => ['user' => ['emails' => '[email protected], [email protected] [email protected]']]],
['data.user.emails' => [new DelimitedEmails([',', ' ', "\n"], 2, 5)]]
)->passes())->toBeTrue();
});
it('fails validation for a nested attribute when emails are invalid', function () {
$validator = Validator::make(
['data' => ['user' => ['emails' => '[email protected], invalid-email, [email protected]']]],
['data.user.emails' => [new DelimitedEmails([',', ' '])]]
);
expect($validator->passes())->toBeFalse();
expect($validator->errors()->first('data.user.emails'))->toBe('The data.user.emails must contain only valid email addresses.');
});
it('fails validation for nested attribute when not enough emails are provided', function () {
$validator = Validator::make(
['data' => ['user' => ['emails' => '[email protected]']]],
['data.user.emails' => [new DelimitedEmails(',', 3)]]
);
expect($validator->passes())->toBeFalse();
expect($validator->errors()->first('data.user.emails'))->toBe('The data.user.emails must contain at least 3 emails.');
});
Perfect, just what I needed. Thx mate.