javascriptajaxformssymfonyinvalidargumentexception

Symfony - dynamic drop down lists not working only when editing


First of all, I'm French, so I hope my english is not so bad ^^' I have a problem with Symfony3 and I don't know how to resolve it.

I have a form with 3 drop down lists. 2 of theme are related : Agency and Center. One agency can have several centers. So, when I select an agency, the list with the centers is updated. The third drop down list is Category. They're all related to entities with the same name.

To do that with Symfony, I followed the official documentation : http://symfony.com/doc/3.0/form/dynamic_form_modification.html#form-events-submitted-data

Everything works fine in the creation form. But when I use the same form for editing, with the same drop down lists, the ajax request doesn't work :/ It says to me that the category (in the third drop down list) is set to null.

Uncaught PHP Exception Symfony\Component\PropertyAccess\Exception\InvalidArgumentException: "Expected argument of type "EDVS\SinistreBundle\Entity\Categorie", "NULL" given" at C:\wamp\www\SinistraV2\vendor\symfony\symfony\src\Symfony\Component\PropertyAccess\PropertyAccessor.php line 254

It has nothing to do with my 2 others drop down lists but apparently, this is the problem. To be clearer, this error is triggered when I select an agency in my editing form. Doing this launched an Ajax request and it's supposed to update the list with the centers but it's not the case.

When I look at the stack trace, the parameter passed in the setCategory() function is null.

I verified in the controller and the formType file, my category isn't null. The drop down list is filled with the available categories and set to the category which is persisted in the database.

Here, my "formType" file :

    class SinistreType extends AbstractType
    {
        /**
         * @param FormBuilderInterface $builder
         * @param array $options
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            // Récupération des sous-types, passés en paramètre via les options du form (voir aussi function configureOptions plus bas)
            $sousTypeSinistre = $options['sousTypeSinistre'];
            $em = $options['em'];

            $builder
            [...]
                ->add('categorie', EntityType::class, array(
                    'class'         => 'EDVSSinistreBundle:Categorie',
                    'placeholder'   => 'Choisir une catégorie',
                    'choice_label'  => 'intituleCat',
                ))
                [...]
                ->add('save', SubmitType::class);

            ;

            /**
             * Liste déroulante dynamique
             * Selon l'agence sélectionnée, la liste de centres correspondant change
             */
            $centresModifier = function (FormInterface $form, Agence $agence = null) {
                /**
                 * Si l'agence passée en param est null, $centres reçoit un tableau vide,
                 * sinon $centres prend comme valeur la liste des centres rattachés à l'agence
                 */
                $centres = null === $agence ? array() : $agence->getCentres();

                // Ajout, dans le formulaire, de la liste déroulante contenant les centres récupérés précédemment
                $form->add('centre', EntityType::class, array(
                    'class'         => 'EDVSAgenceCentreBundle:Centre',
                    'placeholder'   => 'Choisir un centre',
                    'choices'       => $centres,
                    'choice_label'  => 'nom',
                ));
            };

            // Evénement appelé au moment de la construction du formulaire
            $builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($centresModifier, $em) {
                $data = $event->getData(); // Entité Sinistre
                $form = $event->getForm();

                // Dans le cas d'une modification (données provenant de la BDD)
                if ($data->getCentreUtilise()) {
                    // Récupération du centre & de l'agence par rapport au centreUtilisé & à l'agenceUtilisée liés au dossier en cours de modification
                    $centre = $em->getRepository('EDVSAgenceCentreBundle:Centre')->getCentreByNom($data->getCentreUtilise()->getNom());
                    $agence = $em->getRepository('EDVSAgenceCentreBundle:Agence')->getAgenceByNom($data->getCentreUtilise()->getAgenceUtilisee()->getNom());

                    // Sélection de l'agence & du centre dans les listes déroulantes correspondantes
                    $data->setCentre($centre[0]);
                    $data->setAgence($agence[0]);

                    // Affichage de la liste des agences disponibles & des centres correspondants
                    $centresModifier($form, $data->getAgence());
                } else { // Dans le cas d'une création (données vides)
                    $centresModifier($form, $data->getAgence());
                }
            });

            // Evénement appelé juste après que le formulaire ait été validé, concerne le champ "agence" seulement
            $builder->get('agence')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($centresModifier) {
            $form = $event->getForm();
            $agence = $form->getData();

            $centresModifier($form->getParent(), $agence);
        });
}

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class'        => 'EDVS\SinistreBundle\Entity\Sinistre',
        'sousTypeSinistre'  => null, // Déclaration du paramètre "typeSinistre" pour qu'il puisse être reconnu comme option (transfert du param controller vers form)
        'em'                => null, // Idem
    ));
}

}

The javascript part :

$(document).ready(function() {
    var agence = $('#sinistre_edit_agence');

    /* Actualisation de la liste déroulante des centres en fonction de l'agence sélectionnée */
    agence.change(function() {
        // Animation "Chargement en cours"
        toggleLoading();

        var form = $(this).closest('form');
        var data = {};
        data[agence.attr('name')] = agence.val();

        $.ajax({
            url : form.attr('action'),
            type: form.attr('method'),
            data : data,
            success: function(html) {
                $('#sinistre_edit_centre').replaceWith(
                    $(html).find('#sinistre_edit_centre')
                );
                toggleLoading();
            },
            error: function(error) {
                console.error(error);
                toggleLoading();
            }
        });
    });
});

It seems to be the same problem here : Symfony 2: Dynamic Form Event returns InvalidArgumentException only when editing

But there's no answer and I really don't know what I'm doing wrong. I think I'm not using the form events correctly, it's completely new to me.

I hope I'll find some help here, thanks in advance !


Solution

  • Ok, so I found a solution, apparently the problem was in my javascript. For the attribute "data" in the Ajax request, I have to pass the value of the drop down list "Category". Then, when I run the Ajax request, it works fine. If I don't do that, like I said in my first message, the Ajax request fails because the "Category" value is set to null.

    My new javascript :

    $(document).ready(function() {
    var agence = $('#sinistre_edit_agence');
    
    /* Actualisation de la liste déroulante des centres en fonction de l'agence sélectionnée */
    agence.change(function() {
        // Animation "Chargement en cours"
        toggleLoading();
    
        var form = $(this).closest('form');
        var data = {};
        data[agence.attr('name')] = agence.val();
        data[categorie.attr('name')] = categorie.val(); // FIX : the category value
    
        $.ajax({
            url : form.attr('action'),
            type: form.attr('method'),
            data : data, // Now, contains my "Agence" value and "Category" value
            success: function(html) {
                $('#sinistre_edit_centre').replaceWith(
                    $(html).find('#sinistre_edit_centre')
                );
                toggleLoading();
            },
            error: function(error) {
                console.error(error);
                toggleLoading();
            }
        });
    });
    

    });

    I have to admit that I don't really understand why the third drop down list, which has nothing to do with my 2 others related drop down lists, was the problem !