Symfony2 Form Validator - Сравнение старых и новых значений перед сбросом

Мне было интересно, если есть способ сравнить старые и новые значения в валидаторе в сущности до сброса.

у меня есть Server юридическое лицо, которое оказывает в форме штраф. Сущность имеет отношение к status (N->1), который, когда статус изменяется с Unracked в Racked, необходимо проверить SSH и FTP доступ к серверу. Если доступ не достигнут, валидатор должен потерпеть неудачу.

Я сопоставил обратный вызов валидатора с методом isServerValid() в пределах Server сущность, как описано здесь http://symfony.com/doc/current/reference/constraints/Callback.html. Я, очевидно, могу получить доступ к "новым" значениям через $this->status, но как я могу получить исходное значение?

В псевдокоде, что-то вроде этого:

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');
    }
}

Заранее спасибо!

3 ответа

Решение

Полный пример для Symfony 2.5 ( http://symfony.com/doc/current/cookbook/validation/custom_constraint.html)

В этом примере новое значение для поля "integerField" сущности "NoDecreasingInteger" должно быть выше сохраненного значения.

Создание ограничения:

// 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';
  }
}

Создание валидатора ограничения:

// 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();
          }
      }
  }
}

Привязка валидатора к сущности:

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

Инъекция EntityManager в 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 }

Доступ к EntityManager из пользовательского валидатора в symfony2

Вы можете проверить предыдущее значение внутри действия вашего контроллера... но это не будет действительно чистым решением!

обычная проверка формы будет обращаться только к данным, привязанным к форме... никакие "предыдущие" данные не доступны по умолчанию.

Ограничение обратного вызова, которое вы пытаетесь использовать, не имеет доступа к контейнеру или любой другой службе... поэтому вы не можете легко получить доступ к диспетчеру сущностей (или любому другому поставщику предыдущих данных), чтобы проверить предыдущее значение.

Что вам нужно, это пользовательский валидатор на уровне класса. Уровень класса необходим, потому что вам нужно получить доступ ко всему объекту, а не только к одному значению, если вы хотите получить объект.

Сам валидатор может выглядеть так:

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';
    }
}

... впоследствии зарегистрируйте валидатор как сервис и отметьте его как валидатор

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 }

наконец, используйте ограничение для вашей статусной сущности (т.е. с помощью аннотаций)

use Vendor\YourBundle\Validation\Constraints as MyValidation;

/**
 * @MyValidation\StatusValidator
 */
class Status 
{

Для записи, вот способ сделать это с помощью Symfony5.

Во-первых, вам нужно внедрить службу EntityManagerInterface в конструктор вашего валидатора. Затем используйте его для получения исходного объекта.

/** @var EntityManagerInterface */
private $entityManager;

/**
 * MyValidator constructor.
 * @param EntityManagerInterface $entityManager
 */
public function __construct(EntityManagerInterface $entityManager)
{
    $this->entityManager = $entityManager;
}

/**
 * @param string $value
 * @param Constraint $constraint
 */
public function validate($value, Constraint $constraint)
{    
    $originalEntity = $this->entityManager
        ->getUnitOfWork()
        ->getOriginalEntityData($this->context->getObject());

    // ...
}

Предыдущие ответы вполне допустимы и могут соответствовать вашему варианту использования.

Для "простого" варианта использования, он может заполнить тяжелый, хотя. В случае сущности, редактируемой через (только) форму, вы можете просто добавить ограничение в FormBuilder:

<?php

namespace 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()])
                ]
            ])
        ;
    }
}

Это действительно для любой версии Symfony 2+.

Другие вопросы по тегам