Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nullable custom form entity with Symfony

Tags:

forms

php

symfony

My application manage families. One family consist of 1 or N members.

I want the possibility to add one parent or two and 0 or N children. The children part works fine, but I have a hard time dealing with 1 or 2 parents.

Here is my family form type:

 $builder
        ... many attributes
        ->add('parent1', MemberType::class)
        ->add('parent2', MemberType::class)

Parent and parent2 are OneToOne association (Family to member). The member form type :

 $builder
        ->add('firstName', TextType::class, [
            'label' => 'Prénom',
            'constraints' => array(
                new NotBlank(),
                new Length(array('max' => 150))
            )
        ])
        ... many other attributes with choices or not

I thought of a checkbox that grey out the fields of the parent 2 if unchecked, but the member values are all required. Because of that SF2 does not validate my form.

If I set required => false to these fields (in the builder) then the user will have the possibility to validate without filling everything (which I don't want).

I'd like to create the following process :

  • Either we fill all the fields of the member2 in order to validate the form
  • Either we check a checkbox (single parent) and no field is required, and my final member2 will be null (or another solution)
like image 501
FR073N Avatar asked May 01 '16 21:05

FR073N


1 Answers

After reading a lot of documentation, I found the solution of my problem here : http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html#cookbook-form-events-submitted-data

In order to make an entity not required, you ought to add events listener and set the data as null post submit.

First step

Add the orphanRemoval=true option to your attribute

/**
 * @ORM\OneToOne(targetEntity="AppBundle\Entity\Member", orphanRemoval=true, cascade={"persist", "remove"})
 * @ORM\JoinColumn(name="parent2_id", referencedColumnName="id",nullable=true)
 */
private $parent2;

Second step

Add a new field to your form, a not mapped checkbox

   $builder
        ->add('parent1', MemberType::class)
        ->add('withParent2', CheckboxType::class, [
            'mapped'            => false,
            'required'          => false,
            'data'              => true
        ])
        ->add('parent2', MemberType::class, [
            'required'          => false
        ])

We'll use this checkbox to set the parent2 to null if not checked.

Next to this, add your event listeners :

   //this event will set whether or not the checkbox should be checked
   $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
        $form = $event->getForm();
        $family = $event->getData();

        if ($family->getId()) {
            $form->add('withParent2', CheckboxType::class, [
                'mapped'        => false,
                'required'      => false,
                'data'          => $family->getParent2() ? true : false
            ]);
        }
    });

    //Event when the form is submitted, before database update
    $builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) {

        //if the checkbox was not checked, it means that there was not a second parent
        $withParent2 = $event->getForm()->get('withParent2')->getData();
        if (!$withParent2) {

            // we set this attribute to null, and disable the form validation
            $event->getData()->setParent2(null);
            $event->stopPropagation();
        }

    }, 900);

Third step

Our form is working fine this way, the only problem left is the javascript verification.

Just do a jquery function that remove the required attribute from your fields.

 function toggleParent2Requirement(checked){
        if (!checked) {
            $("[id^=family_parent2]").prop("required", false);
            $("[id^=family_parent2]").attr('disabled', true);
        }
        else {
            $("[id^=family_parent2]").prop("required", true);
            $("[id^=family_parent2]").attr('disabled', false);
        }
    }

Here you make a oneToOne relation optional. The only part that I'm not proud is the stopPropagation part. This was in the documentation, and I don't know if we can only disable this field's verification in a more clean way.

like image 87
FR073N Avatar answered Nov 09 '22 00:11

FR073N