首页 文章

Symfony无法验证收集表单

提问于
浏览
1

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

enter image description here

我遇到的问题是如何验证数据 . 在表单上有一个 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文档看来,我似乎需要在 validation.yml 中添加约束Valid .

goals:
    - Valid:

问题1

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

NOTE 第二个错误不应该存在,因为8000小于20000
enter image description here

问题2

假设我反对这两个目标 saved to date 大于 goal target 我然后看到每个字段有2个错误 .

enter image description here

This is my view template

{% 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 %}

This is my Action

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

在这一点上,我很无能为力,因为我花了几个小时试图解决这个问题,我真的很感激这方面的任何帮助 .

2 回答

  • 0

    最后我能够解决问题 .

    • 创建自定义验证 and 时,您需要访问整个类,您需要在 Constraint 类中添加以下代码 . 在我的情况下,这是 SavedToDate ,我在 SavedToDateValidator 中添加它是错误的 .
    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }
    
    • 为了确保在使用collection form时对字段正确显示验证错误,我必须改进自定义_5473378_ SavedToDateValidatorvalidate() 函数,感谢@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
                    ));
    

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

  • 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();
                }
            }
        }
    }
    

    阅读class constraint validators的文档

相关问题