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');
}
}
Спасибо заранее!
Ответы
Ответ 1
Полный пример для 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 }
Ответ 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
{
Ответ 3
Предыдущие ответы совершенно верны и могут соответствовать вашему варианту использования.
Для "простого" варианта использования он может заполнить все, хотя.
В случае объекта, редактируемого через (только) форму, вы можете просто добавить ограничение на 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+.