Symfony 无法验证集合表单
Symfony unable to validate collection form
我正在研究一个叫做目标的 collection form,用户可以根据需要添加任意数量的目标,这部分工作正常,我能够 show/add/edit/delete 目标很好
我遇到的问题是如何验证数据。在表单上有一个 goal target
(整数)字段和 saved to date
(整数)字段。
规则是 saved to date
的值不能超过 goal target
为此,我创建了 custom validation 并且 class 在选择表单时已提交。
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:
Problem 1
假设当我针对第一个目标输入大于 goal target
的 saved to date
时,我得到的不是仅针对该目标的错误,而是针对这两个目标的错误。
注意 第二个错误不应该出现,因为 8000 小于 20000
Problem 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(),
));
}
在这一点上我很无能,因为我花了几个小时试图解决这个问题,我将非常感谢这方面的任何帮助。
这是一个 class 级别限制,它会在您的目标 class 的每个实例中触发,您坚持使用您的表单。
因为您要针对目标的每个实例遍历验证器中的所有对象(为什么?)class您将检查所有目标实体,这并不理想(对于 2x 实体您将检查每个实体 2x,对于 3x 实体,您将检查每个实体 3x,等等)。
请注意,此处的 $value 是您的 class 对象,因此无需查看验证器中的其他实体。
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
class。就我而言,这是 SavedToDate
而我是
将其添加到 SavedToDateValidator
中是错误的。
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
确保验证错误正确显示
在使用 collection form 时,我不得不
改进我的 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')
中的参数是 属性 您想要 link 错误的名称。但是为了得到这个
工作你还需要关闭 error_bubbling
所以错误不会传递给父表单。
$builder
->add('goals', 'collection', array(
'type' => new GoalType(),
'allow_add' => true,
'by_reference' => false,
'error_bubbling' => false
));
这个解决方案对我很有效,我必须承认它真的很有趣,让我很兴奋。
我正在研究一个叫做目标的 collection form,用户可以根据需要添加任意数量的目标,这部分工作正常,我能够 show/add/edit/delete 目标很好
我遇到的问题是如何验证数据。在表单上有一个 goal target
(整数)字段和 saved to date
(整数)字段。
规则是 saved to date
的值不能超过 goal target
为此,我创建了 custom validation 并且 class 在选择表单时已提交。
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
.
goals:
- Valid:
Problem 1
假设当我针对第一个目标输入大于 goal target
的 saved to date
时,我得到的不是仅针对该目标的错误,而是针对这两个目标的错误。
注意 第二个错误不应该出现,因为 8000 小于 20000
Problem 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(),
));
}
在这一点上我很无能,因为我花了几个小时试图解决这个问题,我将非常感谢这方面的任何帮助。
这是一个 class 级别限制,它会在您的目标 class 的每个实例中触发,您坚持使用您的表单。
因为您要针对目标的每个实例遍历验证器中的所有对象(为什么?)class您将检查所有目标实体,这并不理想(对于 2x 实体您将检查每个实体 2x,对于 3x 实体,您将检查每个实体 3x,等等)。
请注意,此处的 $value 是您的 class 对象,因此无需查看验证器中的其他实体。
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
class。就我而言,这是SavedToDate
而我是 将其添加到SavedToDateValidator
中是错误的。public function getTargets() { return self::CLASS_CONSTRAINT; }
确保验证错误正确显示 在使用 collection form 时,我不得不 改进我的
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')
中的参数是 属性 您想要 link 错误的名称。但是为了得到这个 工作你还需要关闭 error_bubbling 所以错误不会传递给父表单。$builder ->add('goals', 'collection', array( 'type' => new GoalType(), 'allow_add' => true, 'by_reference' => false, 'error_bubbling' => false ));
这个解决方案对我很有效,我必须承认它真的很有趣,让我很兴奋。