diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md index b3058828ce3f6..b88e4ab0cb3e6 100644 --- a/UPGRADE-3.4.md +++ b/UPGRADE-3.4.md @@ -413,6 +413,53 @@ Validator * Not setting the `strict` option of the `Choice` constraint to `true` is deprecated and will throw an exception in Symfony 4.0. + * The `strict` option of the `Email` constraint is deprecated and will + be removed in Symfony 4.0, use the `mode` option instead. + + Before: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Assert\Email(strict=true) + */ + private $property; + ``` + + After: + + ```php + use Symfony\Component\Validator\Constraints as Assert; + + /** + * @Assert\Email(mode="strict") + */ + private $property; + ``` + + * Calling the `EmailValidator` with a boolean constructor argument is deprecated and will + be removed in Symfony 4.0. + + Before: + + ```php + use Symfony\Component\Validator\Constraints\EmailValidator; + + $strictValidator = new EmailValidator(true); + $looseValidator = new EmailValidator(false); + ``` + + After: + + ```php + use Symfony\Component\Validator\Constraints\Email; + use Symfony\Component\Validator\Constraints\EmailValidator; + + $strictValidator = new EmailValidator(Email::VALIDATION_MODE_STRICT); + $looseValidator = new EmailValidator(Email::VALIDATION_MODE_LOOSE); + ``` + Yaml ---- diff --git a/src/Symfony/Component/Validator/Constraints/Email.php b/src/Symfony/Component/Validator/Constraints/Email.php index a9d9ab15391fa..96e609b0d9024 100644 --- a/src/Symfony/Component/Validator/Constraints/Email.php +++ b/src/Symfony/Component/Validator/Constraints/Email.php @@ -21,6 +21,10 @@ */ class Email extends Constraint { + const VALIDATION_MODE_HTML5 = 'html5'; + const VALIDATION_MODE_STRICT = 'strict'; + 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,36 @@ 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 3.4, to be removed in 4.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 3.4 and will be removed in 4.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); + } } diff --git a/src/Symfony/Component/Validator/Constraints/EmailValidator.php b/src/Symfony/Component/Validator/Constraints/EmailValidator.php index 25df180de0c21..e45a3c92b62d2 100644 --- a/src/Symfony/Component/Validator/Constraints/EmailValidator.php +++ b/src/Symfony/Component/Validator/Constraints/EmailValidator.php @@ -24,13 +24,40 @@ class EmailValidator extends ConstraintValidator { /** - * @var bool + * @internal */ - private $isStrict; + 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, + ); + + /** + * @var string + */ + private $defaultMode; - public function __construct($strict = false) + /** + * @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 3.4 and will be removed in 4.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; } /** @@ -52,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 3.4 and will be removed in 4.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'); } @@ -78,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) diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php new file mode 100644 index 0000000000000..ed8651ad25e15 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Constraints/EmailTest.php @@ -0,0 +1,46 @@ + + * + * 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; +use Symfony\Component\Validator\Constraints\File; + +class EmailTest extends TestCase +{ + /** + * @expectedDeprecation The 'strict' property is deprecated since version 3.4 and will be removed in 4.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')); + } +} diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php index 94857c1784173..b48eba9be6b28 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php @@ -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 3.4 and will be removed in 4.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 3.4 and will be removed in 4.0. Use 'mode'=>"strict" instead. + * @expectedDeprecation The Symfony\Component\Validator\Constraints\Email::$strict property is deprecated since version 3.4 and will be removed in 4.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);