Symfony2 Form Validator - Comparing old and new values before flush Symfony2 Form Validator - Comparing old and new values before flush symfony symfony

Symfony2 Form Validator - Comparing old and new values before flush


A complete example for Symfony 2.5 (http://symfony.com/doc/current/cookbook/validation/custom_constraint.html)

In this example, the new value for the field "integerField" of the entity "NoDecreasingInteger" must be higher of the stored value.

Creating the constraint:

// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnly.php;<?phpnamespace Acme\AcmeBundle\Validator\Constraints;use Symfony\Component\Validator\Constraint;/** * @Annotation */class IncrementOnly extends Constraint{  public $message = 'The new value %new% is least than the old %old%';  public function getTargets()  {    return self::CLASS_CONSTRAINT;  }  public function validatedBy()  {    return 'increment_only';  }}

Creating the constraint validator:

// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnlyValidator.php<?phpnamespace Acme\AcmeBundle\Validator\Constraints;use Symfony\Component\Validator\Constraint;use Symfony\Component\Validator\ConstraintValidator;use Doctrine\ORM\EntityManager;class IncrementOnlyValidator extends ConstraintValidator{  protected $em;  public function __construct(EntityManager $em)  {    $this->em = $em;  }  public function validate($object, Constraint $constraint)  {    $new_value = $object->getIntegerField();    $old_data = $this->em      ->getUnitOfWork()      ->getOriginalEntityData($object);    // $old_data is empty if we create a new NoDecreasingInteger object.    if (is_array($old_data) and !empty($old_data))      {        $old_value = $old_data['integerField'];        if ($new_value < $old_value)          {            $this->context->buildViolation($constraint->message)              ->setParameter("%new%", $new_value)              ->setParameter('%old%', $old_value)              ->addViolation();          }      }  }}

Binding the validator to entity:

// src/Acme/AcmeBundle/Resources/config/validator.ymlAcme\AcmeBundle\Entity\NoDecreasingInteger:  constraints:     - Acme\AcmeBundle\Validator\Constraints\IncrementOnly: ~

Injecting the EntityManager to IncrementOnlyValidator:

// src/Acme/AcmeBundle/Resources/config/services.ymlservices:   validator.increment_only:        class: Acme\AcmeBundle\Validator\Constraints\IncrementOnlyValidator        arguments: ["@doctrine.orm.entity_manager"]        tags:            - { name: validator.constraint_validator, alias: increment_only }


Accessing the EntityManager inside a custom validator in symfony2

you could check for the previous value inside your controller action ... but that would not really be a clean solution!

normal form-validation will only access the data bound to the form ... no "previous" data accessible by default.

The callback constraint you're trying to use does not have access to the container or any other service ... therefore you cant easily access the entity-manager (or whatever previous-data provider) to check for the previous value.

What you need is a custom validator on class level. class-level is needed because you need to access the whole object not only a single value if you want to fetch the entity.

The validator itself might look like this:

namespace Vendor\YourBundle\Validation\Constraints;use Symfony\Component\DependencyInjection\ContainerInterface;use Symfony\Component\Validator\Constraint;use Symfony\Component\Validator\ConstraintValidator;class StatusValidator extends ConstraintValidator{    protected $container;    public function __construct(ContainerInterface $container)    {        $this->container = $container;    }    public function validate($status, Constraint $constraint)    {        $em = $this->container->get('doctrine')->getEntityManager('default');        $previousStatus = $em->getRepository('YourBundle:Status')->findOneBy(array('id' => $status->getId()));        // ... do something with the previous status here        if ( $previousStatus->getValue() != $status->getValue() ) {            $this->context->addViolationAt('whatever', $constraint->message, array(), null);        }    }    public function getTargets()    {        return self::CLASS_CONSTRAINT;    }    public function validatedBy()    {       return 'previous_value';    }}

... afterwards register the validator as a service and tag it as validator

services:    validator.previous_value:        class: Vendor\YourBundle\Validation\Constraints\StatusValidator        # example! better inject only the services you need ...         # i.e. ... @doctrine.orm.entity_manager        arguments: [ @service_container ]                 tags:            - { name: validator.constraint_validator, alias: previous_value }

finally use the constraint for your status entity ( i.e. using annotations )

use Vendor\YourBundle\Validation\Constraints as MyValidation;/** * @MyValidation\StatusValidator */class Status {


Previous answers are perfectly valid, and may fit your use case.

For "simple" use case, it may fill heavy though.In the case of an entity editable through (only) a form, you can simply add the constraint on the FormBuilder:

<?phpnamespace AppBundle\Form\Type;// ...use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;/** * Class MyFormType */class MyFormType extends AbstractType{    /**     * {@inheritdoc}     */    public function buildForm(FormBuilderInterface $builder, array $options)    {        $builder            ->add('fooField', IntegerType::class, [                'constraints' => [                    new GreaterThanOrEqual(['value' => $builder->getData()->getFooField()])                ]            ])        ;    }}

This is valid for any Symfony 2+ version.