14

我在 Symfony 的表单验证处理方面遇到了一些问题。我想根据其数据验证绑定到实体的表单。有很多关于如何使用FormEvents动态修改表单字段的信息。我在这个主题上缺少的是如何控制/修改验证。

我的简化用例是:

  1. 用户可以将事件添加到日历。
  2. 验证检查是否已经存在事件。
  3. 如果发生冲突,验证将引发错误。
  4. 用户现在应该能够忽略此错误/警告。

验证是作为目标实现ValidatorConstraint::CLASS_CONSTRAINT(因为它考虑了更多的东西)。

我尝试过了:

  • 绕过验证组,但无法访问实体范围的验证器。
  • 破解FormEvents并添加一个额外的字段,例如“忽略日期警告”。
  • 绕过提交按钮将其更改为“强制提交”之类的内容。

...但从未找到有效的解决方案。使用基于单个属性的验证器进行更简单的黑客攻击也无法奏效。:(

有没有一种 Symfony 方法来动态控制验证?

编辑:我的代码如下所示:

use Doctrine\ORM\Mapping as ORM;
use Acme\Bundle\Validator\Constraints as AcmeAssert;

/**
 * Appointment
 *
 * @ORM\Entity
 * @AcmeAssert\DateIsValid
 */
class Appointment
{
  /**
   * @ORM\Column(name="title", type="string", length=255)
   *
   * @var string
   */
  protected $title;

  /**
   * @ORM\Column(name="date", type="date")
   *
   * @var \DateTime
   */
  protected $date;
}

用作服务的验证器:

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
 * Validates the date of an appointment.
 */
class DateIsValidValidator extends ConstraintValidator
{
    /**
     * {@inheritdoc}
     */
    public function validate($appointment, Constraint $constraint)
    {
        if (null === $date = $appointment->getDate()) {
            return;
        }

        /* Do some magic to validate date */
        if (!$valid) {
            $this->context->addViolationAt('date', $constraint->message);
        }
    }
}

对应的Constraint类被设置为以实体类为目标。

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class DateIsValid extends Constraint
{
    public $message = 'The date is not valid!';

    /**
     * {@inheritdoc}
     */
    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }

    /**
     * {@inheritdoc}
     */
    public function validatedBy()
    {
        return 'acme.validator.appointment.date';
    }
}

编辑2:尝试FormEvents...我也尝试了所有不同的事件。

$form = $formFactory->createBuilder()
    ->add('title', 'text')
    ->add('date', 'date')
    ->addEventListener(FormEvents::WHICHONE?,  function(FormEvent $event) {
        $form = $event->getForm();

        // WHAT TO DO HERE?
        $form->getErrors(); // Is always empty as all events run before validation?

        // I need something like
        if (!$dateIsValid) {
            $form->setValidationGroup('ignoreWarning');
        }
    });

编辑 3:正确声明了约束。那不是问题:

services:
    validator.acme.date:
        class: AcmeBundle\Validator\Constraints\DateValidator
        arguments: ["@acme.other_service"]
        tags:
            - { name: validator.constraint_validator, alias: acme.validator.appointment.date }
4

4 回答 4

2

验证是在实体上完成的,表单所做的只是执行对象的验证。您可以根据提交的数据选择组

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => function(FormInterface $form) {
            $data = $form->getData();
            if (Entity\Client::TYPE_PERSON == $data->getType()) {
                return array('person');
            } else {
                return array('company');
            }
        },
    ));
}

在嵌入式表单&&级联验证上使用这种方法时我遇到了问题

编辑:使用 flash 确定是否必须进行验证。

// service definition
    <service id="app.form.type.callendar" class="%app.form.type.callendar.class%">
        <argument type="service" id="session" />
        <tag name="form.type" alias="my_callendar" />
    </service>


// some controller
public function somAvtion() 
{
    $form = $this->get('app.form.type.callendar');
    ...
}

// In the form
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'validation_groups' => function(FormInterface $form) {
            $session = $form->getSession();
            if ($session->getFlashBag()->get('callendar_warning', false)) {
                return array(false);
            } else {
                return array('Validate_callendar');
            }
        },
    ));
}
于 2014-07-12T00:32:22.127 回答
1

您的用户如何与应用程序交互以告诉它忽略警告?是否有某种额外的按钮?在这种情况下,您可以简单地检查用于提交表单的按钮或添加某种隐藏字段 ( ignore_validation) 等。无论您最终从哪里获得该用户输入(闪存和依赖注入,基于提交的数据等),我都会然后使用验证组和闭包来确定要验证的内容(就像juanmf他的回答中解释的那样)。

RE 您的第二种方法(表单事件),您可以为事件侦听器添加优先级:正如您在Symfony 的表单验证事件侦听器中看到的那样,它们FormEvents::POST_SUBMIT用于启动验证过程。因此,如果您只是添加一个事件侦听器,它会在验证侦听器之前被调用,因此尚未发生验证。如果您向侦听器添加负优先级,您应该还可以访问表单验证错误:

$builder->addEventListener(FormEvents::POST_SUBMIT, function(){...}, -900);
于 2014-09-30T09:28:19.487 回答
0

老问题但是...

我将首先按照您的建议和上面的其他答案在表单中添加一个字段(acceptCollision)。

然后您的验证器可以执行以下操作:

public function validate($appointment, Constraint $constraint)
    {
    if (null === $date = $appointment->getDate()) {
        return;
    }

    if ($appointment->getAcceptCollision()) {
       $valid = true;
       } elseif (

                 // Check Unicity of the date (no collision)
                 ) {
                    $valid = true;
       } else {
         $valid = false;
       }

    if (!$valid) {
        $this->context->addViolationAt('date', $constraint->message);
    }
}
于 2014-12-03T11:45:59.390 回答
0

我认为您遇到问题是因为您使用了错误的概念。应该运行哪个验证的决定属于控制器,而不是验证器。

所以我只需检查控制器按下了哪个提交按钮(或者是否选中了一个复选框)并切换验证组。但是表单应该在视觉上有所不同,所以我可能会为这两种状态创建 2 个表单(都扩展了一个基本的表单类型或使用选项的一种表单类型)。

于 2015-04-15T14:55:01.967 回答