Last active
February 1, 2023 14:35
-
-
Save rybakit/4705749 to your computer and use it in GitHub Desktop.
Nested chain validator for symfony > 2.1
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 | |
namespace Acme\Validator\Constraints; | |
use Symfony\Component\Validator\Constraint; | |
use Symfony\Component\Validator\Constraints\Valid; | |
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | |
/** | |
* @Annotation | |
*/ | |
class Chain extends Constraint | |
{ | |
public $constraints; | |
public $stopOnError = true; | |
/** | |
* {@inheritdoc} | |
*/ | |
public function __construct($options = null) | |
{ | |
// no known options set? $options is the constraints array | |
if (is_array($options) && !array_intersect(array_keys($options), array('groups', 'constraints', 'stopOnError'))) { | |
$options = array('constraints' => $options); | |
} | |
parent::__construct($options); | |
if (!is_array($this->constraints)) { | |
throw new ConstraintDefinitionException('The option "constraints" is expected to be an array in constraint '.__CLASS__.'.'); | |
} | |
foreach ($this->constraints as $constraint) { | |
if (!$constraint instanceof Constraint) { | |
throw new ConstraintDefinitionException('The value '.$constraint.' is not an instance of Constraint in constraint '.__CLASS__.'.'); | |
} | |
if ($constraint instanceof Valid) { | |
throw new ConstraintDefinitionException('The constraint Valid cannot be nested inside constraint '.__CLASS__.'.'); | |
} | |
} | |
} | |
public function getRequiredOptions() | |
{ | |
return array('constraints'); | |
} | |
} |
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 | |
namespace Acme\Validator\Constraints; | |
use Symfony\Component\Validator\Constraint; | |
use Symfony\Component\Validator\ConstraintValidator; | |
class ChainValidator extends ConstraintValidator | |
{ | |
public function validate($value, Constraint $constraint) | |
{ | |
$group = $this->context->getGroup(); | |
$propertyPath = $this->context->getPropertyPath(); | |
$violationList = $this->context->getViolations(); | |
$violationCountPrevious = $violationList->count(); | |
foreach ($constraint->constraints as $constr) { | |
$this->context->validateValue($value, $constr, $propertyPath, $group); | |
if ($constraint->stopOnError && (count($violationList) !== $violationCountPrevious)) { | |
return; | |
} | |
} | |
} | |
} |
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 | |
namespace Acme\Validator; | |
use Symfony\Component\Validator\Constraint; | |
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; | |
class NoCacheConstraintValidatorFactory implements ConstraintValidatorFactoryInterface | |
{ | |
/** | |
* @var ConstraintValidatorFactoryInterface | |
*/ | |
private $factory; | |
/** | |
* @var array | |
*/ | |
private $nonCachableClasses; | |
public function __construct(ConstraintValidatorFactoryInterface $factory, array $nonCachableClasses) | |
{ | |
$this->factory = $factory; | |
$this->nonCachableClasses = $nonCachableClasses; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
public function getInstance(Constraint $constraint) | |
{ | |
$className = $constraint->validatedBy(); | |
if (in_array($className, $this->nonCachableClasses, true)) { | |
return new $className(); | |
} | |
return $this->factory->getInstance($constraint); | |
} | |
} |
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 | |
use Symfony\Component\Validator\Constraints\Date; | |
use Symfony\Component\Validator\Constraints\Type; | |
use Acme\Validator\Constraints\Chain; | |
$constraint1 = new Chain([new Type('string'), new Date()]); | |
$constraint2 = new Chain([ | |
'constraints' => [new Type('string'), new Date()], | |
'stopOnError' => true, | |
]); | |
// Symfony Validator Component v2.x has an issue with nested constraints, | |
// see https://github.com/symfony/Validator/blob/fc0650c1825c842f9dcc4819a2eaff9922a07e7c/ConstraintValidatorFactory.php#L48. | |
// If you plan to use nested `Chain` constraints, consider using the `NoCacheConstraintValidatorFactory` decorator. | |
// Here is a usage example for the Silex application: | |
$app->register(new Silex\Provider\ValidatorServiceProvider()); | |
$app['validator.validator_factory'] = $app->share($app->extend('validator.validator_factory', function ($factory, $app) { | |
return new Acme\Validator\NoCacheConstraintValidatorFactory($factory, [ | |
'Acme\\Validator\\Constraints\\ChainValidator', | |
]); | |
})); |
Very useful, thanks.
As per your comment
If you plan to use nested
Chain
constraints, consider using theNoCacheConstraintValidatorFactory
decorator.
Is this still valid for Symfony 3.3. Also how can this be used in Symfony project rather than Silex.
Pretty good solution to prevent abusing validation groups. Only change required to get this working in >3.0 is:
$this->context->getValidator()
->inContext($this->context)
->atPath($propertyPath)
->validate($value, $constr, $group);
instead of:
$this->context->validateValue($value, $constr, $propertyPath, $group);
Dont need to use the NoCache factory either.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
symfony/symfony#10690