How can I add a violation to a collection?

后端 未结 3 1089
天命终不由人
天命终不由人 2020-12-10 06:06

My form looks like this:

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

    $build         


        
相关标签:
3条回答
  • 2020-12-10 06:23

    I have a case very similar. I have a CollectionType with a Custom Form (with DataTransformers inside, etc...), i need check one by one the elements and mark what of them is wrong and print it on the view.

    I make that solution at the ConstraintValidator (my custom validator):

    The validator must target to CLASS_CONSTRAINT to work or the propertyPath doesnt work.

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

    Just i validate agains the form elements in the collection and build the violation using the propertyPath from the item in the collection that is wrong.

    0 讨论(0)
  • By default, forms have the option "error_bubbling" set to true, which causes the behavior you just described. You can turn off this option for individual forms if you want them to keep their errors.

    $builder->add('departments', 'collection', array(
        'type' => new Department,
        'error_bubbling' => false,
    ));
    
    0 讨论(0)
  • 2020-12-10 06:30

    I have been wrestling with this issue in Symfony 3.3, where I wished to validate an entire collection, but pass the error to the appropriate collection element/field. The collection is added to the form thus:

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

    The StudentGradeType is:

    <?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';
        }
    
    
    }
    

    and the validator looks like:

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

    This results in the error being added to the field in the element of the collection as per the attach image.

    Craig

    0 讨论(0)
提交回复
热议问题