9

我的表格如下所示:

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

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

如果我更改departments为其他字段之一,它工作正常。

我怎样才能让这个错误出现在正确的位置?我认为如果我的错误发生在集合中的单个实体上并因此以子表单呈现,它会正常工作,但我的标准是,如果集合中的任何实体都没有标记为活动,则会发生违规,因此它需要处于父级。

4

3 回答 3

24

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

$builder->add('departments', 'collection', array(
    'type' => new Department,
    'error_bubbling' => false,
));
于 2012-07-31T16:42:47.883 回答
4

我在 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;

                }
            }
        }
    }
}

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

克雷格

于 2017-10-27T04:35:45.543 回答
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 构建违规。

于 2018-02-20T11:09:54.037 回答