1

我正在研究一个称为目标的集合表单,用户可以添加任意数量的目标,这部分工作正常,我能够很好地显示/添加/编辑/删除目标

在此处输入图像描述

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

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

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 文档看来,我需要添加我在里面的约束Validvalidation.yml

goals:
    - Valid:

问题 1

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

注意第二个错误不应该存在,因为 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(),
    ));
}

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

4

2 回答 2

2

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

因为您正在为目标类的每个实例遍历验证器中的所有对象(为什么?),所以您将检查所有目标实体,这并不理想(对于 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();
            }
        }
    }
}

阅读类约束验证器的文档

于 2015-11-16T18:55:37.557 回答
0

最后我能够解决这个问题。

  1. 在创建自定义验证并且您需要访问整个类时,您需要在您的类中添加以下代码 Constraint。就我而言SavedToDate,这是我添加的SavedToDateValidator错误。

    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }
    
  2. 为了确保在使用集合表单时验证错误正确显示在字段中,我必须改进我validate()的自定义功能Validator 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();
            }
        }
    }
    

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

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

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

于 2015-11-17T12:25:23.107 回答