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)

Решение Вопроса

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

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

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

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

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

Инъекция EntityManager в IncrementOnlyValidator:

<code>// 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 }
</code>
 goulashsoup08 нояб. 2017 г., 12:19
Хорошая идея, но это не такт работать для коллекций, потому что коллекция в$objectСвойство s совпадает с коллекцией внутри$old_data (та же ссылка). Так что если элементы удаляются в форме, вы неполучить старые значения.
 A.D.08 сент. 2016 г., 16:48
Действительно хорошее объяснение. Отлично работает с SF3.1
 FoxException14 окт. 2014 г., 12:48
этоgetOriginalEntityData () метод действительно удобен!

анту использования.

За "просто" использовать случай, он может заполнить тяжелые, хотя. В случае сущности, редактируемой через (только) форму, вы можете просто добавить ограничение в 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+.

Доступ к 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 
{
 CSchulz18 сент. 2014 г., 01:17
@nifr Вы должны внедрить вместо контейнера нужный сервис. :)
 nifr18 сент. 2014 г., 13:30
@CSchulz I 'Я полностью осведомлен об улучшениях производительности и тестируемости, вводя прямые зависимости вместо контейнера. Этому ответу 1,5 года, и он служит лишь быстрым примером - не стесняйтесь редактировать / улучшать, что 'S, о чем является stackoverflow.
 caponica22 авг. 2014 г., 22:30
У меня проблемы с Доктриной ... этодает мне тот же объект / значения, хотя ям использую$query->useResultCache(false); в валидаторе. Клонирование объекта перед манипулированием им в форме / контроллере приводит к дублированию записей базы данных ... ну и чтоправильный способ сделать все это?
 Andy26 июн. 2013 г., 16:55
Спасибо, работает отлично :)
 Bastien Libersa31 июл. 2014 г., 15:05
В этом случае вам нужно клонировать предыдущий объект в новый (с помощью PHP "Клон ()» функция), прежде чем работать / привязка / и т. д. новый.
 nifr26 июн. 2013 г., 17:11
Рад, что смог помочь :)
 althaus07 мая 2014 г., 11:54
Краткое примечание: имейте в виду, что Doctrine может дать вам точно такой же объект для$previousState из-за внутреннего кеширования объектов. Были мои проблемы с этим. : [

Ваш ответ на вопрос