如何动态控制表单的验证?

时间:2014-06-20 10:34:17

标签: forms validation symfony symfony-2.5

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

我的简化用例是:

  1. 用户可以将事件添加到日历中。
  2. 验证检查是否已有事件。
  3. 如果发生碰撞,验证将引发错误。
  4. 用户现在应该可以忽略此错误/警告。
  5. 验证以Validator实现,Constraint::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 个答案:

答案 0 :(得分:2)

验证在实体上完成,所有Forms都执行Object的验证。 你可以choose groups based on submitted data

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

我在嵌入式表格和&&amp ;;上使用这种方法时遇到了问题。 cascade-validation

编辑:使用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');
            }
        },
    ));
}

答案 1 :(得分:0)

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

RE第二种方法(表单事件),您可以为事件侦听器添加优先级:正如您在his answer中看到的那样,他们使用FormEvents::POST_SUBMIT来启动验证过程。因此,如果您只是添加一个事件侦听器,它将在验证侦听器之前调用,因此尚未进行验证。 如果您向侦听器添加否定优先级,则应该还能够访问表单验证错误:

$builder->addEventListener(FormEvents::POST_SUBMIT, function(){...}, -900);

答案 2 :(得分: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);
    }
}

答案 3 :(得分:0)

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

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