Symfony2 Form Validator - Comparing old and new values before flush

Andy picture Andy · Jun 25, 2013 · Viewed 10.1k times · Source

I was wondering if there is a way to compare old and new values in a validator within an entity prior to a flush.

I have a Server entity which renders to a form fine. The entity has a relationship to status (N->1) which, when the status is changed from Unracked to Racked, needs to check for SSH and FTP access to the server. If access is not achieved, the validator should fail.

I have mapped a validator callback to the method isServerValid() within the Server entity as described here http://symfony.com/doc/current/reference/constraints/Callback.html. I can obviously access the 'new' values via $this->status, but how can I get the original value?

In pseudo code, something like this:

public function isAuthorValid(ExecutionContextInterface $context)
{
    $original = ... ; // get old values
    if( $this->status !== $original->status && $this->status === 'Racked' && $original->status === 'Unracked' )
    {
        // check ftp and ssh connection
        // $context->addViolationAt('status', 'Unable to connect etc etc');
    }
}

Thanks in advance!

Answer

targzeta picture targzeta · Sep 30, 2014

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;
<?php
namespace 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
<?php
namespace 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.yml
Acme\AcmeBundle\Entity\NoDecreasingInteger:
  constraints:
     - Acme\AcmeBundle\Validator\Constraints\IncrementOnly: ~

Injecting the EntityManager to IncrementOnlyValidator:

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