Permalink
Browse files

feature #24442 [Validator] Html5 Email Validation (PurpleBooth)

This PR was merged into the 4.1-dev branch.

Discussion
----------

[Validator] Html5 Email Validation

Currently we only support a very loose validation. There is now a
standard HTML5 element with matching regex. This will add the ability
to set a `mode` on the email validator. The mode will change the
validation that is applied to the field as a whole.

These modes are:

* loose: The pattern from previous Symfony versions (default)
* strict: Strictly matching the RFC
* html5: The regex used for the HTML5 Element

Deprecates the `strict=true` parameter in favour of `mode='strict'`

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #21531
| License       | MIT
| Doc PR        | symfony/symfony-docs#8487

<!--
- Bug fixes must be submitted against the lowest branch where they apply
  (lowest branches are regularly merged to upper ones so they get the fixes too).
- Features and deprecations must be submitted against the 3.4,
  legacy code removals go to the master branch.
- Please fill in this template according to the PR you're about to submit.
- Replace this comment by a description of what your PR is solving.
-->

Commits
-------

cf04108 [Validator] Html5 Email Validation
  • Loading branch information...
fabpot committed Dec 11, 2017
2 parents efc19fc + cf04108 commit 503effea72a62e7d46133976728cb5323cc3f443
View
@@ -17,6 +17,12 @@ Translation
* The `FileDumper::setBackup()` method is deprecated and will be removed in 5.0.
* The `TranslationWriter::disableBackup()` method is deprecated and will be removed in 5.0.
Validator
--------
* The `Email::__construct()` 'strict' property is deprecated and will be removed in 5.0. Use 'mode'=>"strict" instead.
* Calling `EmailValidator::__construct()` method with a boolean parameter is deprecated and will be removed in 5.0, use `EmailValidator("strict")` instead.
Workflow
--------
View
@@ -17,6 +17,13 @@ Translation
* The `FileDumper::setBackup()` method has been removed.
* The `TranslationWriter::disableBackup()` method has been removed.
Validator
--------
* The `Email::__construct()` 'strict' property has been removed. Use 'mode'=>"strict" instead.
* Calling `EmailValidator::__construct()` method with a boolean parameter has been removed, use `EmailValidator("strict")` instead.
Workflow
--------
@@ -21,6 +21,10 @@
*/
class Email extends Constraint
{
public const VALIDATION_MODE_HTML5 = 'html5';
public const VALIDATION_MODE_STRICT = 'strict';
public const VALIDATION_MODE_LOOSE = 'loose';
const INVALID_FORMAT_ERROR = 'bd79c0ab-ddba-46cc-a703-a7a4b08de310';
const MX_CHECK_FAILED_ERROR = 'bf447c1c-0266-4e10-9c6c-573df282e413';
const HOST_CHECK_FAILED_ERROR = '7da53a8b-56f3-4288-bb3e-ee9ede4ef9a1';
@@ -31,8 +35,37 @@ class Email extends Constraint
self::HOST_CHECK_FAILED_ERROR => 'HOST_CHECK_FAILED_ERROR',
);
/**
* @var string[]
*
* @internal
*/
public static $validationModes = array(
self::VALIDATION_MODE_HTML5,
self::VALIDATION_MODE_STRICT,
self::VALIDATION_MODE_LOOSE,
);
public $message = 'This value is not a valid email address.';
public $checkMX = false;
public $checkHost = false;
/**
* @deprecated since version 4.1, to be removed in 5.0. Set mode to "strict" instead.
*/
public $strict;
public $mode;
public function __construct($options = null)
{
if (is_array($options) && array_key_exists('strict', $options)) {
@trigger_error(sprintf('The \'strict\' property is deprecated since version 4.1 and will be removed in 5.0. Use \'mode\'=>"%s" instead.', self::VALIDATION_MODE_STRICT), E_USER_DEPRECATED);
}
if (is_array($options) && array_key_exists('mode', $options) && !in_array($options['mode'], self::$validationModes, true)) {
throw new \InvalidArgumentException('The \'mode\' parameter value is not valid.');
}
parent::__construct($options);
}
}
@@ -23,11 +23,41 @@
*/
class EmailValidator extends ConstraintValidator
{
private $isStrict;
/**
* @internal
*/
const PATTERN_HTML5 = '/^[a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/';
/**
* @internal
*/
const PATTERN_LOOSE = '/^.+\@\S+\.\S+$/';
private static $emailPatterns = array(
Email::VALIDATION_MODE_LOOSE => self::PATTERN_LOOSE,
Email::VALIDATION_MODE_HTML5 => self::PATTERN_HTML5,
);
public function __construct(bool $strict = false)
/**
* @var string
*/
private $defaultMode;
/**
* @param string $defaultMode
*/
public function __construct($defaultMode = Email::VALIDATION_MODE_LOOSE)
{
$this->isStrict = $strict;
if (is_bool($defaultMode)) {
@trigger_error(sprintf('Calling `new %s(%s)` is deprecated since version 4.1 and will be removed in 5.0, use `new %s("%s")` instead.', self::class, $defaultMode ? 'true' : 'false', self::class, $defaultMode ? Email::VALIDATION_MODE_STRICT : Email::VALIDATION_MODE_LOOSE), E_USER_DEPRECATED);
$defaultMode = $defaultMode ? Email::VALIDATION_MODE_STRICT : Email::VALIDATION_MODE_LOOSE;
}
if (!in_array($defaultMode, Email::$validationModes, true)) {
throw new \InvalidArgumentException('The "defaultMode" parameter value is not valid.');
}
$this->defaultMode = $defaultMode;
}
/**
@@ -49,11 +79,25 @@ public function validate($value, Constraint $constraint)
$value = (string) $value;
if (null === $constraint->strict) {
$constraint->strict = $this->isStrict;
if (null !== $constraint->strict) {
@trigger_error(sprintf('The %s::$strict property is deprecated since version 4.1 and will be removed in 5.0. Use %s::mode="%s" instead.', Email::class, Email::class, Email::VALIDATION_MODE_STRICT), E_USER_DEPRECATED);
if ($constraint->strict) {
$constraint->mode = Email::VALIDATION_MODE_STRICT;
} else {
$constraint->mode = Email::VALIDATION_MODE_LOOSE;
}
}
if (null === $constraint->mode) {
$constraint->mode = $this->defaultMode;
}
if (!in_array($constraint->mode, Email::$validationModes, true)) {
throw new \InvalidArgumentException(sprintf('The %s::$mode parameter value is not valid.', get_class($constraint)));
}
if ($constraint->strict) {
if (Email::VALIDATION_MODE_STRICT === $constraint->mode) {
if (!class_exists('\Egulias\EmailValidator\EmailValidator')) {
throw new RuntimeException('Strict email validation requires egulias/email-validator ~1.2|~2.0');
}
@@ -75,7 +119,7 @@ public function validate($value, Constraint $constraint)
return;
}
} elseif (!preg_match('/^.+\@\S+\.\S+$/', $value)) {
} elseif (!preg_match(self::$emailPatterns[$constraint->mode], $value)) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value))
->setCode(Email::INVALID_FORMAT_ERROR)
@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Validator\Tests\Constraints;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Email;
class EmailTest extends TestCase
{
/**
* @expectedDeprecation The 'strict' property is deprecated since version 4.1 and will be removed in 5.0. Use 'mode'=>"strict" instead.
* @group legacy
*/
public function testLegacyConstructorStrict()
{
$subject = new Email(array('strict' => true));
$this->assertTrue($subject->strict);
}
public function testConstructorStrict()
{
$subject = new Email(array('mode' => Email::VALIDATION_MODE_STRICT));
$this->assertEquals(Email::VALIDATION_MODE_STRICT, $subject->mode);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The 'mode' parameter value is not valid.
*/
public function testUnknownModesTriggerException()
{
new Email(array('mode' => 'Unknown Mode'));
}
}
@@ -23,7 +23,29 @@ class EmailValidatorTest extends ConstraintValidatorTestCase
{
protected function createValidator()
{
return new EmailValidator(false);
return new EmailValidator(Email::VALIDATION_MODE_LOOSE);
}
/**
* @expectedDeprecation Calling `new Symfony\Component\Validator\Constraints\EmailValidator(true)` is deprecated since version 4.1 and will be removed in 5.0, use `new Symfony\Component\Validator\Constraints\EmailValidator("strict")` instead.
* @group legacy
*/
public function testLegacyValidatorConstructorStrict()
{
$this->validator = new EmailValidator(true);
$this->validator->initialize($this->context);
$this->validator->validate('example@localhost', new Email());
$this->assertNoViolation();
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The "defaultMode" parameter value is not valid.
*/
public function testUnknownDefaultModeTriggerException()
{
new EmailValidator('Unknown Mode');
}
public function testNullIsValid()
@@ -64,6 +86,31 @@ public function getValidEmails()
array('fabien@symfony.com'),
array('example@example.co.uk'),
array('fabien_potencier@example.fr'),
array('example@example.co..uk'),
array('{}~!@!@£$%%^&*().!@£$%^&*()'),
array('example@example.co..uk'),
array('example@-example.com'),
array(sprintf('example@%s.com', str_repeat('a', 64))),
);
}
/**
* @dataProvider getValidEmailsHtml5
*/
public function testValidEmailsHtml5($email)
{
$this->validator->validate($email, new Email(array('mode' => Email::VALIDATION_MODE_HTML5)));
$this->assertNoViolation();
}
public function getValidEmailsHtml5()
{
return array(
array('fabien@symfony.com'),
array('example@example.co.uk'),
array('fabien_potencier@example.fr'),
array('{}~!@example.com'),
);
}
@@ -94,6 +141,95 @@ public function getInvalidEmails()
);
}
/**
* @dataProvider getInvalidHtml5Emails
*/
public function testInvalidHtml5Emails($email)
{
$constraint = new Email(
array(
'message' => 'myMessage',
'mode' => Email::VALIDATION_MODE_HTML5,
)
);
$this->validator->validate($email, $constraint);
$this->buildViolation('myMessage')
->setParameter('{{ value }}', '"'.$email.'"')
->setCode(Email::INVALID_FORMAT_ERROR)
->assertRaised();
}
public function getInvalidHtml5Emails()
{
return array(
array('example'),
array('example@'),
array('example@localhost'),
array('example@example.co..uk'),
array('foo@example.com bar'),
array('example@example.'),
array('example@.fr'),
array('@example.com'),
array('example@example.com;example@example.com'),
array('example@.'),
array(' example@example.com'),
array('example@ '),
array(' example@example.com '),
array(' example @example .com '),
array('example@-example.com'),
array(sprintf('example@%s.com', str_repeat('a', 64))),
);
}
public function testModeStrict()
{
$constraint = new Email(array('mode' => Email::VALIDATION_MODE_STRICT));
$this->validator->validate('example@localhost', $constraint);
$this->assertNoViolation();
}
public function testModeHtml5()
{
$constraint = new Email(array('mode' => Email::VALIDATION_MODE_HTML5));
$this->validator->validate('example@example..com', $constraint);
$this->buildViolation('This value is not a valid email address.')
->setParameter('{{ value }}', '"example@example..com"')
->setCode(Email::INVALID_FORMAT_ERROR)
->assertRaised();
}
public function testModeLoose()
{
$constraint = new Email(array('mode' => Email::VALIDATION_MODE_LOOSE));
$this->validator->validate('example@example..com', $constraint);
$this->assertNoViolation();
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The Symfony\Component\Validator\Constraints\Email::$mode parameter value is not valid.
*/
public function testUnknownModesOnValidateTriggerException()
{
$constraint = new Email();
$constraint->mode = 'Unknown Mode';
$this->validator->validate('example@example..com', $constraint);
}
/**
* @expectedDeprecation The 'strict' property is deprecated since version 4.1 and will be removed in 5.0. Use 'mode'=>"strict" instead.
* @expectedDeprecation The Symfony\Component\Validator\Constraints\Email::$strict property is deprecated since version 4.1 and will be removed in 5.0. Use Symfony\Component\Validator\Constraints\Email::mode="strict" instead.
* @group legacy
*/
public function testStrict()
{
$constraint = new Email(array('strict' => true));
@@ -110,7 +246,7 @@ public function testStrictWithInvalidEmails($email)
{
$constraint = new Email(array(
'message' => 'myMessage',
'strict' => true,
'mode' => Email::VALIDATION_MODE_STRICT,
));
$this->validator->validate($email, $constraint);

0 comments on commit 503effe

Please sign in to comment.