Forked from technoknol/custom validator in laravel to validate comma separated emails.php
Created
February 23, 2018 12:50
-
-
Save vmosoti/8ef268588cbdbf33f263e4733571254b to your computer and use it in GitHub Desktop.
custom validator in laravel to validate comma separated emails.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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. |
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.');
});
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
"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.