Я работаю над формой коллекции, которая называется целями, пользователь может добавить столько целей, сколько захочет, эта часть работает нормально, я могу показать/добавить/изменить/удалить цели просто отлично
Проблема заключается в том, как проверить данные. В форме есть поле goal target
(целое) и saved to date
поле 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 кажется, что мне нужно добавить ограничение Valid, которое у меня есть в validation.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(),
));
}
На данный момент я довольно неуловим, поскольку я потратил часы, пытаясь решить это, я буду очень признателен за любую помощь в этом.
Наконец, я смог решить проблему.
При создании пользовательских проверок, и вам нужен доступ ко всему классу, вам нужно добавить следующий фрагмент кода в свой класс Constraint
. В моем случае это SavedToDate
и я добавлял его в SavedToDateValidator
что было неправильно.
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
Чтобы убедиться, что ошибки проверки корректно отображаются в поле во время работы с формой сбора, мне пришлось улучшить функцию 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
));
Это решение сработало для меня, и я должен признать, что это было действительно забавно работать над этим, все взволновало меня.
Это ограничение уровня класса, и оно срабатывает для каждого экземпляра вашего целевого класса, который вы сохраняете из своей формы.
Поскольку вы повторяете все свои объекты в валидаторе (почему?) Для каждого экземпляра вашего целевого класса, вы будете проверять все ваши объекты цели, что не является идеальным (для 2x объекта вы будете проверять каждый объект 2x, для 3x сущность, вы проверите каждый объект 3x и т.д.).
Обратите внимание, что значение $ здесь - это ваш объект класса, поэтому нет необходимости смотреть на другие объекты в валидаторе.
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();
}
}
}
}
Прочтите документацию для валидаторов ограничения классов
getTargets()
. Только ограничение делает.