feature #9140 [Validator][Email] - Strict validation and soft dependency (egulias)

This PR was squashed before being merged into the 2.5-dev branch (closes #9140).

Discussion
----------

[Validator][Email] - Strict validation and soft dependency

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | yes
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #1581, #4930
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/3469

TODO
---------
- [x] submit changes to the documentation
- [x] document the BC breaks
- [x] finish the code
- [x] gather feedback for my changes

In #1581 @bschussek suggested to pass the strict_email config to  `Validator\EmailValidator::__construct($strict)`, I did not put it there yet since the constraint can receive that configuration each time the constraint is used despite the fact of the global configuration. This could lead to some logic in the constructor and I wanted first to integrate the strict validator.

BC Break
--------------
I'm not sure of this, but as a soft dependency is added and now some emails that where valid before no longer are I thought it is.

Commits
-------

3368630 #1581 - Strict in Email constraint and use of Egulias\EmailValidator
This commit is contained in:
Fabien Potencier 2014-03-27 07:02:36 +01:00
commit 5a4885edb4
10 changed files with 89 additions and 7 deletions

View File

@ -44,3 +44,31 @@ Form
public function getErrors($deep = false, $flatten = true)
{
```
Validator
---------
* EmailValidator has changed to allow `non-strict` and `strict` email validation
Before:
Email validation was done with php's `filter_var()`
After:
Default email validation is now done via a simple regex which may cause invalid emails (not RFC compilant) to be
valid. This is the default behaviour.
Strict email validation has to be explicitly activated in the configuration file by adding
```
framework_bundle:
//...
validation:
strict_email: true
//...
```
Also you have to add to your composer.json:
```
"egulias/email-validator": "1.1.*"
```

View File

@ -73,7 +73,8 @@
"monolog/monolog": "~1.3",
"propel/propel1": "1.6.*",
"ircmaxell/password-compat": "1.0.*",
"ocramius/proxy-manager": ">=0.3.1,<0.6-dev"
"ocramius/proxy-manager": ">=0.3.1,<0.6-dev",
"egulias/email-validator": "1.1.0"
},
"autoload": {
"psr-0": { "Symfony\\": "src/" },

View File

@ -445,6 +445,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('cache')->end()
->booleanNode('enable_annotations')->defaultFalse()->end()
->scalarNode('translation_domain')->defaultValue('validators')->end()
->booleanNode('strict_email')->defaultFalse()->end()
->end()
->end()
->end()

View File

@ -678,6 +678,9 @@ class FrameworkExtension extends Extension
$container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $this->getValidatorXmlMappingFiles($container));
$container->setParameter('validator.mapping.loader.yaml_files_loader.mapping_files', $this->getValidatorYamlMappingFiles($container));
$definition = $container->findDefinition('validator.email');
$definition->replaceArgument(0, $config['strict_email']);
if (array_key_exists('enable_annotations', $config) && $config['enable_annotations']) {
$loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain');
$arguments = $loaderChain->getArguments();

View File

@ -18,6 +18,7 @@
<parameter key="validator.mapping.loader.xml_files_loader.mapping_files" type="collection" />
<parameter key="validator.mapping.loader.yaml_files_loader.mapping_files" type="collection" />
<parameter key="validator.expression.class">Symfony\Component\Validator\Constraints\ExpressionValidator</parameter>
<parameter key="validator.email.class">Symfony\Component\Validator\Constraints\EmailValidator</parameter>
</parameters>
<services>
@ -69,5 +70,10 @@
<argument type="service" id="property_accessor" />
<tag name="validator.constraint_validator" alias="validator.expression" />
</service>
<service id="validator.email" class="%validator.email.class%">
<argument></argument>
<tag name="validator.constraint_validator" alias="validator.email" />
</service>
</services>
</container>

View File

@ -127,6 +127,7 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
'enabled' => false,
'enable_annotations' => false,
'translation_domain' => 'validators',
'strict_email' => false,
),
'annotations' => array(
'cache' => 'file',

View File

@ -25,4 +25,13 @@ class Email extends Constraint
public $message = 'This value is not a valid email address.';
public $checkMX = false;
public $checkHost = false;
public $strict = null;
/**
* {@inheritDoc}
*/
public function validatedBy()
{
return 'validator.email';
}
}

View File

@ -14,6 +14,7 @@ namespace Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Egulias\EmailValidator\EmailValidator as StrictEmailValidator;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
@ -22,6 +23,18 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
*/
class EmailValidator extends ConstraintValidator
{
/**
* isStrict
*
* @var Boolean
*/
private $isStrict;
public function __construct($strict = false)
{
$this->isStrict = $strict;
}
/**
* {@inheritDoc}
*/
@ -40,12 +53,23 @@ class EmailValidator extends ConstraintValidator
}
$value = (string) $value;
$valid = filter_var($value, FILTER_VALIDATE_EMAIL);
if (null === $constraint->strict) {
$constraint->strict = $this->isStrict;
}
if ($constraint->strict && class_exists('\Egulias\EmailValidator\EmailValidator')) {
$strictValidator = new StrictEmailValidator();
$valid = $strictValidator->isValid($value, false);
} elseif ($constraint->strict === true) {
throw new \RuntimeException('Strict email validation requires egulias/email-validator');
} else {
$valid = preg_match('/.+\@.+\..+/', $value);
}
if ($valid) {
$host = substr($value, strpos($value, '@') + 1);
// Check for host DNS resource records
if ($valid && $constraint->checkMX) {
$valid = $this->checkMX($host);
} elseif ($valid && $constraint->checkHost) {

View File

@ -22,7 +22,7 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
$this->validator = new EmailValidator();
$this->validator = new EmailValidator(false);
$this->validator->initialize($this->context);
}
@ -100,7 +100,14 @@ class EmailValidatorTest extends \PHPUnit_Framework_TestCase
array('example'),
array('example@'),
array('example@localhost'),
array('example@example.com@example.com'),
);
}
public function testStrict()
{
$this->context->expects($this->never())
->method('addViolation');
$this->validator->validate('example@localhost', new Email(array('strict' => true)));
}
}

View File

@ -26,7 +26,8 @@
"symfony/yaml": "~2.0",
"symfony/config": "~2.2",
"doctrine/annotations": "~1.0",
"doctrine/cache": "~1.0"
"doctrine/cache": "~1.0",
"egulias/email-validator": "~1.0"
},
"suggest": {
"doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
@ -34,7 +35,8 @@
"symfony/http-foundation": "",
"symfony/intl": "",
"symfony/yaml": "",
"symfony/config": ""
"symfony/config": "",
"egulias/email-validator": "Strict (RFC compliant) email validation"
},
"autoload": {
"psr-0": { "Symfony\\Component\\Validator\\": "" }