如何向集合中添加违规?

时间:2012-07-30 08:33:47

标签: symfony-forms symfony-2.1

我的表单如下:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $factory = $builder->getFormFactory();

    $builder->add('name');

    $builder->add('description');

    $builder->add('manufacturers', null, array(
        'required' => false
    ));

    $builder->add('departments', 'collection', array(
        'type' => new Department
    ));
}

我在表单所代表的实体上有一个类验证器,它调用:

    if (!$valid) {
        $this->context->addViolationAtSubPath('departments', $constraint->message);
    }

只会在表单中添加“全局”错误,而不会在子路径中添加错误。我认为这是因为departments是一个嵌入另一个FormType的集合。

如果我将departments更改为其他字段之一,则可以正常工作。

如何才能将此错误显示在正确的位置?我假设如果我的错误是在集合中的单个实体上,并因此以子表单形式呈现,它将正常工作,但我的标准是如果集合中没有任何实体被标记为活动,则会发生违规,因此它需要在父母层面。

3 个答案:

答案 0 :(得分:24)

默认情况下,表单将“error_bubbling”选项设置为true,这会导致您刚才描述的行为。如果您希望他们保留错误,您可以为单个表单关闭此选项。

$builder->add('departments', 'collection', array(
    'type' => new Department,
    'error_bubbling' => false,
));

答案 1 :(得分:2)

我在Symfony 3.3中一直在努力解决这个问题,我希望验证整个集合,但是将错误传递给相应的集合元素/字段。该集合将添加到表单中:

        $form->add('grades', CollectionType::class,
            [
                'label'         => 'student.grades.label',
                'allow_add'     => true,
                'allow_delete'  => true,
                'entry_type'    => StudentGradeType::class,
                'attr'          => [
                    'class' => 'gradeList',
                    'help'  => 'student.grades.help',
                ],
                'entry_options'  => [
                    'systemYear' => $form->getConfig()->getOption('systemYear'),
                ],
                'constraints'    => [
                    new Grades(),
                ],
            ]
        );

StudentGradeType是:

<?php

namespace Busybee\Management\GradeBundle\Form;

use Busybee\Core\CalendarBundle\Entity\Grade;
use Busybee\Core\SecurityBundle\Form\DataTransformer\EntityToStringTransformer;
use Busybee\Core\TemplateBundle\Type\SettingChoiceType;
use Busybee\Management\GradeBundle\Entity\StudentGrade;
use Busybee\People\StudentBundle\Entity\Student;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class StudentGradeType extends AbstractType
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * StaffType constructor.
     *
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('status', SettingChoiceType::class,
                [
                    'setting_name' => 'student.enrolment.status',
                    'label'        => 'grades.label.status',
                    'placeholder'  => 'grades.placeholder.status',
                    'attr'         => [
                        'help' => 'grades.help.status',
                    ],
                ]
            )
            ->add('student', HiddenType::class)
            ->add('grade', EntityType::class,
                [
                    'class'         => Grade::class,
                    'choice_label'  => 'gradeYear',
                    'query_builder' => function (EntityRepository $er) {
                        return $er->createQueryBuilder('g')
                            ->orderBy('g.year', 'DESC')
                            ->addOrderBy('g.sequence', 'ASC');
                    },
                    'placeholder'   => 'grades.placeholder.grade',
                    'label'         => 'grades.label.grade',
                    'attr'          => [
                        'help' => 'grades.help.grade',
                    ],
                ]
            );

        $builder->get('student')->addModelTransformer(new EntityToStringTransformer($this->om, Student::class));

    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver
            ->setDefaults(
                [
                    'data_class'         => StudentGrade::class,
                    'translation_domain' => 'BusybeeStudentBundle',
                    'systemYear'         => null,
                    'error_bubbling'     => true,
                ]
            );
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'grade_by_student';
    }


}

并且验证器看起来像:     

namespace Busybee\Management\GradeBundle\Validator\Constraints;

use Busybee\Core\CalendarBundle\Entity\Year;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class GradesValidator extends ConstraintValidator
{
    public function validate($value, Constraint $constraint)
    {

        if (empty($value))
            return;

        $current = 0;
        $year    = [];

        foreach ($value->toArray() as $q=>$grade)
        {
            if (empty($grade->getStudent()) || empty($grade->getGrade()))
            {

                $this->context->buildViolation('student.grades.empty')
                    ->addViolation();

                return $value;
            }

            if ($grade->getStatus() === 'Current')
            {
                $current++;

                if ($current > 1)
                {
                    $this->context->buildViolation('student.grades.current')
                        ->atPath('['.strval($q).']')  // could do a single atPath with a value of "[".strval($q)."].status"
                        ->atPath('status')      //  full path = children['grades'].data[1].status
                        ->addViolation();

                    return $value;

                }
            }

            $gy = $grade->getGradeYear();

            if (! is_null($gy))
            {
                $year[$gy] = empty($year[$gy]) ? 1 : $year[$gy]  + 1 ;

                if ($year[$gy] > 1)
                {
                    $this->context->buildViolation('student.grades.year')
                        ->atPath('['.strval($q).']')
                        ->atPath('grade')
                        ->addViolation();

                    return $value;

                }
            }
        }
    }
}

这会导致根据附加图像将错误添加到集合元素的字段中。 Error at Element/Field

克雷格

答案 2 :(得分:0)

我的情况非常相似。我有一个带有自定义表单的CollectionType(里面有DataTransformers等等),我需要逐个检查元素并标记它们的错误并将其打印在视图上。

我在ConstraintValidator(我的自定义验证器)上制作了该解决方案:

验证程序必须以 CLASS_CONSTRAINT 为目标,或者propertyPath不起作用。

public function validate($value, Constraint $constraint) {
    /** @var Form $form */
    $form = $this->context->getRoot();
    $studentsForm = $form->get("students"); //CollectionType's name in the root Type
    $rootPath = $studentsForm->getPropertyPath()->getElement(0);

    /** @var Form $studentForm */
    foreach($studentsForm as $studentForm){
        //Iterate over the items in the collection type
        $studentPath = $studentForm->getPropertyPath()->getElement(0);

        //Get the data typed on the item (in my case, it use an DataTransformer and i can get an User object from the child TextType)
        /** @var User $user */
        $user = $studentForm->getData();

        //Validate your data
        $email = $user->getEmail();
        $user = $userRepository->findByEmailAndCentro($email, $centro);

        if(!$user){
            //If your data is wrong build the violation from the propertyPath getted from the item Type
            $this->context->buildViolation($constraint->message)
                ->atPath($rootPath)
                ->atPath(sprintf("[%s]", $studentPath))
                ->atPath("email") //That last is the name property on the item Type
                ->addViolation();
        }
    }
}

我只是再次验证集合中的表单元素,并使用集合中错误的项目中的propertyPath构建违规。