Alberto Fecchi Alberto Fecchi - 1 month ago 16
PHP Question

Symfony3: validate nested (optional) forms

i have three documents in my project: Event, OrganizerProfile and User.

Users can have multiple OrganizerProfile (it's like 'pages' on Facebook) and Events.

When a User creates and Event, he can assign an "OrganizerProfile" to the event (User Alberto creates an event for "Company A" that's called "Event X").

To achieve this, i've created this forms:

OrganizerProfileType.php

class OrganizerProfileType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder
->add('email', EmailType::class)
->add('name', TextType::class)
->add('description', TextType::class, ['required' => false])
...


EventType.php

class EventType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$profileChoices = ... //List of existing profiles

$builder
->add('profile_list', ChoiceType::class, [
'choices' => $profileChoices,
'required' => false,
'mapped' => false,
])
->add('profile', OrganizerProfileType::class, [
'required' => true,
])
->add('title', TextType::class, ['required' => false])
->add('description', TextType::class, ['required' => false])
...


In the field "profile_list" users can find their existing OrganizerProfiles. Users can pick one of them and assign it to the event BUT if the user doesn't pick an existing profile he must insert information in the "profile" form.

I'd like to make "optional" the profile form and make it required only if the users don't pick an existing profile.

How can i do this? Thanks

Answer

You can implement by Validation Groups Based on the Submitted Data.

So your FormType should be as follow:

class EventType extends AbstractType {

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $profileChoices = [...];

        $builder
            ->add('profile_list', ChoiceType::class, [
                'choices'  => $profileChoices,
                'required' => false,
                'mapped'   => false,
            ])
            ->add('profile', OrganizerProfileType::class, [
                'constraints' => [
                    new Valid()
                ]
            ])
            ->add('title', TextType::class, ['required' => false])
            ->add('description', TextType::class, ['required' => false]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'validation_groups' => function (FormInterface $form) {

                if (null === $form->get('profile_list')->getData()) {
                    return ['without_profile'];
                }

                return ['with_profile'];  // Or simply 'Default'
            },
        ]);
    }

}

UPDATE: Use the validation group in the other FormType also

Then you need to handle the validation of the subform with the validation group, as example:

class OrganizerProfileType extends AbstractType{

    public function buildForm(FormBuilderInterface $builder, array $options){
        $builder
            ->add('email', EmailType::class, [
                    'constraints' => [
                        new NotNull(
                            [
                                'groups' => ['without_profile', 'with_profile']
                            ]
                        )
            ]])
            ->add('name', TextType::class, [
                    'constraints' => [
                        new NotNull(
                            [
                                'groups' => ['without_profile', 'with_profile']
                            ]
                        )
            ]])
            ->add('description', TextType::class, ['required' => false])

Hope this help

Comments