Sunday, January 17, 2016

Uniqueness Validation for Entity Collection - Symfony2

Sometimes it is necessary to maintain the uniqueness of a field across the entities. For example you may want the email address to be unique across all the users throughout a Symfony2 application. For this, you can simply use the Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity.

But consider the case where you want to persist a collection of entities at once. You want a particular field of these entities to be unique. Will the above mentioned UniqueEntity constraint work in this scenario? Well, the answer is "No". UniqueEntity checks the uniqueness of the new value against the values which are already persisted. In our case, the uniqueness has to be validated across the entities which are not yet persisted.

Consider the following example where the uniqueField is the unique field across all the MyEntity entities. Suppose you are saving a collection of MyEntity's at once. Now you want  all the uniqueField values to be different from each other, and they also must not have the same values as the already persisted values.

First add the constraint class.

1:  <?php  
2:  namespace MyBundle\Validator\Constraint;  
3:  use Symfony\Component\Validator\Constraint;  
4:  /**  
5:   *  
6:   * @Annotation  
7:   */  
8:  class UniqueInCollection extends Constraint {  
9:    public $message = 'This value has been already entered above.';  
10:    // The property path used to check wether objects are equal  
11:    // If none is specified, it will check that objects are equal  
12:    public $propertyPath = null;  
13:  }  

Then add the constraint validator class.

1:  <?php  
2:  namespace MyBundle\Validator\Constraint;  
3:  use Symfony\Component\Validator\Constraint;  
4:  use Symfony\Component\Validator\ConstraintValidator;  
5:  use Symfony\Component\Form\Util\PropertyPath;  
6:  /**  
7:   * Description of UniqueInCollectionValidator  
8:   *  
9:   * @author sahan  
10:   */  
11:  class UniqueInCollectionValidator extends ConstraintValidator {  
12:    private $collectionValues = array();  
13:    public function validate($value, Constraint $constraint) {  
14:      // Apply the property path if specified  
15:      if ($constraint->propertyPath) {  
16:        $propertyPath = new PropertyPath($constraint->propertyPath);  
17:        $value = $propertyPath->getValue($value);  
18:      }  
19:      // Check that the value is not in the array  
20:      if (in_array($value, $this->collectionValues))  
21:        $this->context->addViolation($constraint->message, array());  
22:      // Add the value in the array for next items validation  
23:      $this->collectionValues[] = $value;  
24:    }  
25:  }  

Now you are ready to go. You can use this validator in your validation file. In my case, I will go ahead with YAML.

1:  MyBundle\Entity\MyEntity:  
2:    constraints:  
3:      # validates across existing entities  
4:      - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity: uniqueField  
5:    properties:  
6:      uniqueField:  
7:        # validates across the entity collection  
8:        - MyBundle\Validator\Constraint\UniqueInCollection: ~  

This validator will attach the validation message (in case it is invalid) to the uniqueField property.

References:
http://stackoverflow.com/questions/11782056/how-to-validate-unique-entities-in-an-entity-collection-in-symfony2

No comments:

Post a Comment