【问题标题】:Symfony unable to validate collection formSymfony 无法验证收集表单
【发布时间】:2015-11-16 17:56:35
【问题描述】:

我正在开发一个称为目标的collection form,用户可以添加任意数量的目标,这部分工作正常,我能够显示/添加/编辑/删除目标就好了

我遇到的问题是如何验证数据。在表单上有一个goal target(整数)字段和saved to date(整数)字段。

规则是saved to date 的值不能超过goal target,为此我创建了custom validation,并在提交表单时选择该类。

SavedToDate.php

namespace MyBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
class SavedToDate extends Constraint
{
    public $message = '"%string%" Saved to date cannot be greater than target date.';
}

SavedToDateValidator.php

namespace MyBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class SavedToDateValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {
        $values = $this->context->getRoot()->getdata()->getGoals()->getValues();
        foreach($values as $item ){
            $target = $item->getTarget();
            $savedToDate = $item->getReached();
           if ($savedToDate > $target) {
                $this->context->buildViolation($constraint->message)
                    ->setParameter('%string%', $value)
                    ->addViolation();
            }
        }
    }

    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }
}

从阅读 symfony 文档看来,我需要添加约束 Valid,我在 validation.yml 中拥有。

goals:
    - Valid:

问题 1

假设当我针对第一个目标输入大于goal targetsaved to date 时,而不是仅针对该目标得到错误,我得到针对两个目标的错误。

注意第二个错误不应该存在,因为 8000 小于 20000

问题 2

假设针对这两个目标我给出的saved to date 大于goal target 然后我看到每个字段有 2 个错误。

这是我的视图模板

{% for goals in form.goals %}      
        <div class="container-fluid">
            <div class="row">
                <div class="col-lg-12">
                    {% if(form_errors(goals.target))  %}
                        <div class="alert alert-danger" role="alert">{{ form_errors(goals.target) }}</div>
                    {% endif %}
                    {% if(form_errors(goals.reached))  %}
                        <div class="alert alert-danger" role="alert">{{ form_errors(goals.reached) }}</div>
                    {% endif %}
                </div>
            </div>
        </div>
        <div class="row">
            <div class="col-xs-2" style="padding-top: 5%">
                <label class="" for="exampleInputEmail2">Goal target</label>
                <div class="form-group input-group">
                    {{ form_widget(goals.target, {'attr': {'class': 'form-control'}}) }}
                </div>


            </div>
            <div class="col-xs-2" style="padding-top: 5%">
                <label class="" for="exampleInputEmail2">Saved to date</label>

                <div class="form-group input-group">
                    {{ form_widget(goals.reached, {'attr': {'class': 'form-control'}}) }}
                </div>
            </div>
            <div class="col-xs-2" style="padding-top: 5%">
                <label class="" for="exampleInputEmail2">Goal deadline</label>

                <div class="form-group input-group">
                    {{ form_widget(goals.deadline, {'attr': {'class': 'form-control dp'}}) }}
                </div>
            </div>
            <div class="col-xs-2" style="padding-top: 5%">
                <label class="" for="exampleInputEmail2">Savings</label>

                <div class="form-group input-group">
                    {{ form_widget(goals.allocated, {'attr': {'class': 'form-control'}}) }}
                </div>

            </div>
        </div>
{% endfor %}

这是我的行动

public function prioritiseGoalsAction(Request $request)
{

    $em = $this->getDoctrine()->getManager();
    //get user id of currently logged in user
    $userId = $this->getUser()->getId();

    //get survey object of currently logged in user
    $userGoalsInfo = $em->getRepository('MyBundle:survey')->findOneByuserID($userId);

    //create the form
    $form = $this->createForm(new GoalsType(), $userGoalsInfo);
    $form->handleRequest($request);

    if ($request->isMethod('POST')) {
        if ($form->isValid()) {
            $em->persist($userGoalsInfo);
            $em->flush();
            $this->get('session')->getFlashBag()->add(
                'notice',
                'Your Goals information has been saved'
            );
            return $this->render('MyBundle:Default/dashboard:prioritise-my-goals.html.twig', array(
                'form' => $form->createView(),
            ));
        }
    }


    return $this->render('MyBundle:Default/dashboard:prioritise-my-goals.html.twig', array(
        'form' => $form->createView(),
    ));
}

在这一点上我很无能,因为我已经花了几个小时试图解决这个问题,我将非常感谢任何帮助。

【问题讨论】:

    标签: php symfony symfony-forms formcollection symfony-2.7


    【解决方案1】:

    这是一个类级别的约束,它将为您从表单中持久保存的目标类的每个实例触发。

    因为您要遍历验证器中的所有对象(为什么?)对于目标类的每个实例,您将检查所有目标实体,这并不理想(对于 2x 实体,您将检查每个实体 2x ,对于 3x 实体,您将检查每个实体 3x 等)。

    请注意,这里的 $value 是您的类对象,因此无需查看验证器中的其他实体。

    public function validate($value, Constraint $constraint)
    

    您应该编写类似的验证器(我没有检查语法):

    class SavedToDateValidator extends ConstraintValidator
    {
        public function validate($value, Constraint $constraint)
        {
                // value should already be an instance of Goal but you could put in a sanity check like
    
                if (!$value instanceof Goal) {
    
                    // throw an exception or whatever
                }                
    
                $target = $value->getTarget();
                $savedToDate = $value->getReached();
                if ($savedToDate > $target) {
                    $this->context->buildViolation($constraint->message)
                        ->setParameter('%string%', $value)
                        ->addViolation();
                }
            }
        }
    }
    

    阅读class constraint validators的文档

    【讨论】:

    • 请编辑您的代码示例,因为 ConstraintValidator 没有 getTargets() 函数。只有约束可以。
    • @DelphiLynx 注意到并更改了
    【解决方案2】:

    最后我能够解决问题。

    1. 创建自定义验证时您需要访问 整个班级,您需要在您的 Constraint 班级。就我而言,这是SavedToDate,我是 将其添加到 SavedToDateValidator 这是错误的。

      public function getTargets()
      {
          return self::CLASS_CONSTRAINT;
      }
      
    2. 为了确保验证错误正确显示在 在使用collection form 时,我必须 改进我的自定义Validatorvalidate() 功能 SavedToDateValidator,感谢@Richard 的提示。

      public function validate($value, Constraint $constraint)
      {
          if ($value instanceof Goals) {
              $target = $value->getTarget();
              $savedToDate = $value->getReached();
              if ($savedToDate > $target) {
                  $this->context->buildViolation($constraint->message)
                      ->setParameter('%goalname%', $value->getName())
                      ->setParameter('%reached%', $value->getReached())
                      ->setParameter('%targetamount%', $value->getTarget())
                      ->atPath('reached')
                      ->addViolation();
              }
          }
      }
      

      上述功能的重要部分之一是-&gt;atPath('reached') 这个atPath() 将错误粘贴到违规的字段 是,我之前没有这个,这导致显示 针对所有字段的错误消息,而不是唯一针对 错误实际属于的字段。 atpath('fieldname') 中的参数是您要链接错误的属性名称。但是为了得到这个 要工作,您还需要关闭error_bubbling 所以错误不会传递给父表单。

          $builder 
              ->add('goals', 'collection', array(
                    'type' => new GoalType(),
                    'allow_add' => true,
                    'by_reference' => false,
                    'error_bubbling' => false
                      ));
      

    这个解决方案对我有用,我必须承认,做它真的很有趣,让我兴奋不已。

    【讨论】:

    • 好东西,很高兴你把它整理好了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-23
    • 1970-01-01
    • 1970-01-01
    • 2019-01-12
    相关资源
    最近更新 更多