Partage
  • Partager sur Facebook
  • Partager sur Twitter

Symfony/Listes déroulantes dynamiques "null given"

Sujet résolu
    23 août 2016 à 13:25:56

    Bonjour,

    Je suis confrontée à un problème sous Symfony3 que je n'arrive pas à résoudre malgré plusieurs recherches.

    Je vais déjà présenter le contexte rapidement. Dans le cadre de mon travail, je bosse sur une application devant gérer des dossiers de sinistres (accidents de voitures, vol, dégradation de bâtiments, etc). Un dossier peut être classé dans 2 catégories possibles. La structure de la société fait qu'un dossier peut être rattaché à ce que l'on appelle un centre d'exploitation.  Ce centre d'exploitation est lui-même rattaché à une des 3 agences principales de notre structure. De plus, un dossier a une date de clôture (qui peut être plusieurs mois ou plusieurs années après la création du dossier en question).

    Côté formulaire, lorsque je souhaite créer un dossier, parmi l'ensemble des champs à remplir, se trouvent 2 listes déroulantes : une pour les agences & une pour les centres d'exploitation. Lorsque je sélectionne l'agence, la liste des centres correspondants se met à jour automatiquement. J'ai suivi la doc officielle de Symfony qui m'a permis de faire ce que je souhaitais avec les form events. Tout fonctionne correctement pour la création.
    Aussi, la catégorie à choisir prend également la forme d'une liste déroulante.

    Là où ça coince, c'est à l'édition d'un dossier. Lorsque je souhaite modifier l'agence, dans le but de modifier également le centre rattaché, la liste des centres ne se met pas à jour. J'ai l'erreur suivante :

    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

    Visiblement, la catégorie, dont j'ai parlée précédemment, est à null. Sauf qu'avec un var_dump à plusieurs endroits dans mon code (controller, fichier type, côté vue, entre autre), j'ai pu voir que ce n'était pas le cas.

    Le code (raccourci car il y a pleeeein d'attributs) de mon entité Sinistre (dossier) :

    <?php
    
    namespace EDVS\SinistreBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Validator\Constraints\DateTime;
    
    /**
    * Sinistre
    *
    * @ORM\Table(name="sinistre")
    * @ORM\Entity(repositoryClass="EDVS\SinistreBundle\Repository\SinistreRepository")
    * @ORM\HasLifecycleCallbacks()
    */
    class Sinistre
    {
        /**
         * @var int
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
    
        /**
         * @ORM\ManyToOne(targetEntity="EDVS\SinistreBundle\Entity\Categorie", cascade={"persist"})
         * @ORM\JoinColumn(nullable=false)
         */
        private $categorie;
    
        [...]
    
        /**
         * Get id
         *
         * @return int
         */
        public function getId()
        {
            return $this->id;
        }
    
        /**
         * Set categorie
         *
         * @param \EDVS\SinistreBundle\Entity\Categorie $categorie
         *
         * @return Sinistre
         */
        public function setCategorie(\EDVS\SinistreBundle\Entity\Categorie $categorie)
        {
            $this->categorie = $categorie;
    
            return $this;
        }
    
        /**
         * Get categorie
         *
         * @return \EDVS\SinistreBundle\Entity\Categorie
         */
        public function getCategorie()
        {
            return $this->categorie;
        }
        
        [...]
    }
    

    L'entité Catégorie

    <?php
    
    namespace EDVS\SinistreBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * Categorie
     *
     * @ORM\Table(name="categorie")
     * @ORM\Entity(repositoryClass="EDVS\SinistreBundle\Repository\CategorieRepository")
     */
    class Categorie
    {
        /**
         * @var int
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
    
        /**
         * @var string
         *
         * @ORM\Column(name="intituleCat", type="string", length=255)
         */
        private $intituleCat;
    
        /**
         * Get id
         *
         * @return int
         */
        public function getId()
        {
            return $this->id;
        }
    
        /**
         * Set intituleCat
         *
         * @param string $intituleCat
         *
         * @return Categorie
         */
        public function setIntituleCat($intituleCat)
        {
            $this->intituleCat = $intituleCat;
    
            return $this;
        }
    
        /**
         * Get intituleCat
         *
         * @return string
         */
        public function getIntituleCat()
        {
            return $this->intituleCat;
        }
    } 

    Mon formulaire (raccourci, vu le nombre de champs présents)

    <?php
    
    namespace EDVS\SinistreBundle\Form;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Form\Extension\Core\Type\TextType;
    use Symfony\Component\Form\Extension\Core\Type\DateType;
    use Symfony\Component\Form\Extension\Core\Type\TimeType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\Extension\Core\Type\SubmitType;
    use Symfony\Component\Form\Extension\Core\Type\TextareaType;
    use Symfony\Component\Form\FormInterface;
    use Symfony\Component\Form\FormEvent;
    use Symfony\Component\Form\FormEvents;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Bridge\Doctrine\Form\Type\EntityType;
    use EDVS\AgenceCentreBundle\Entity\Agence;
    use EDVS\AgenceCentreBundle\Entity\Centre;
    use EDVS\SinistreBundle\Form\TiersType;
    use EDVS\SinistreBundle\Form\VehiculeType;
    use EDVS\SinistreBundle\Form\ResponsableDossierType;
    use Doctrine\ORM\EntityManager;
    
    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
            ));
        }
    }
    

    Concernant les "CentreUtilisé" & "AgenceUtilisée", il s'agit d'entités "mirroir" basées sur les entités "Centre" & "Agence", dans lesquelles sont archivés les centres & agences réellement utilisés dans des dossiers (on peut imaginer qu'un centre ou une agence ne soit jamais concerné par un dossier de sinistre). Ce sont les entités "Centre" & "Agence" qui sont utilisées dans les listes déroulantes car elles sont censés refléter la structure Agences/Centres du moment (des changements peuvent arriver d'année en année, du style création ou suppression d'une agence par exemple). Avoir ces archives en parallèle permet d'historiser les possibles anciens centres & agences, surtout si des dossiers ne sont pas encore clôturés & qu'ils concernent un centre supprimé par exemple.

    J'espère avoir mis assez d'informations & surtout, j'espère être claire dans mes explications :euh: Je ne suis pas encore hyper calée avec Symfony & notamment les Form Events, peut-être que je les utilise mal. Si jamais vous avez rencontrés un problème similaire ou que vous avez une piste, n'hésitez pas à me le faire savoir, toute aide sera grandement appréciée :)




    -
    Edité par Heybee 23 août 2016 à 13:32:59

    • Partager sur Facebook
    • Partager sur Twitter
      25 août 2016 à 8:58:43

      Salut,

      A quelle ligne tu as ton erreur ?

      Dans ta stack trace, tu devrais pouvoir remonter la file et trouver dans quelle appel à fonction de ton code se trouve l'erreur. C'est le fichier de ton dossier src le plus en haut.

      • Partager sur Facebook
      • Partager sur Twitter
      Nous sommes tous débutant pour quelqu'un et expert pour quelqu'un d'autre...
        25 août 2016 à 9:48:29

        Merci pour ta réponse.

        Alors pour répondre à ta question, l'erreur a l'air de se déclencher dans le setCategorie(). L'objet $categorie passé en paramètre serait à null à cet endroit-là.

        Je vais peut-être dire une bêtise mais vu que je ne l'appelle pas explicitement, c'est Symfony qui doit l'appeler tout seul, avec son système de formulaire ? Ce que je ne comprends pas, c'est que cette erreur est déclenchée après une requête Ajax, qui elle-même est déclenchée suite à la sélection d'un élément dans une liste déroulante qui n'a rien à voir avec la catégorie :o

        • Partager sur Facebook
        • Partager sur Twitter
          25 août 2016 à 10:05:36

          Hum, je vais peut-être dire une bêtise à mon tour...

          Je ne sais pas du tout ce que fait ta requête Ajax ni pourquoi (j'avoue n'avoir fait que très peu d'Ajax, et rien sur SF), mais il est possible que tu fasses une recherche sur tes sinistrés où, plus probable vu que c'est setCategorie qui est en cause, que tu fasses ta sauvegarde de sinistré ou de ton agence dans cette requête. Et dans ce cas-là, il est probable que tu ne remettes pas la catégorie. ça me semble un peu tiré par les cheveux, mais je vois pas ce qui pourrait causer ce genre d'erreurs.

          -
          Edité par Tartare2240. 25 août 2016 à 10:06:11

          • Partager sur Facebook
          • Partager sur Twitter
          Nous sommes tous débutant pour quelqu'un et expert pour quelqu'un d'autre...
            25 août 2016 à 10:22:51

            Voilà le code de ma requête Ajax :

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

            Dès que l'on change d'agence dans la liste déroulante correspondante, on récupère la nouvelle valeur sélectionnée & si la requête  est réussie, on remplit la liste déroulante des centres avec les centres correspondants. C'est étroitement lié avec le fichier SinistreType.php que j'ai copié dans mon 1er post. Lorsque je sélectionne une nouvelle agence, la requête n'est pas en "success".

            Aussi, là où ça reste un peu "magique" pour moi, c'est que l'attribut "url" est à undefined. C'est le cas également dans mon formulaire de création mais les listes fonctionnent correctement :o

            J'ai suivi la doc officielle Symfony : http://symfony.com/doc/3.0/form/dynamic_form_modification.html#form-events-submitted-data

            • Partager sur Facebook
            • Partager sur Twitter
              15 septembre 2016 à 13:37:00

              Après avoir laissé ce code de côté pendant un petit moment, j'ai finalement trouvé une solution à mon problème. Donc pour info, dans la requête Ajax que je fais pour actualiser la liste des centres en fonction de l'agence sélectionnée, j'ajoute la valeur présente dans la liste des catégories dans le tableau passé en paramètre "data".

              $(document).ready(function() {
                  var agence = $('#sinistre_edit_agence');
                  var categorie = $('#sinistre_edit_categorie');
              
                  /* 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();
              
                      $.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();
                          }
                      });
                  });
              });

              Je ne comprends pas trop pourquoi j'ai à faire ça pour ce champ là en particulier, peut-être parce qu'il s'agit aussi d'une liste déroulante ? En tout cas, mon problème est résolu.

              -
              Edité par Heybee 15 septembre 2016 à 13:37:34

              • Partager sur Facebook
              • Partager sur Twitter
                17 novembre 2018 à 10:43:45

                Salut j'ai eu le même soucis que toi lors de mon formulaire d'édition, mon new fonctionner très bien, et c'est parce que dans ton modifier

                            $form->add('centre', EntityType::class, array(
                                'class'         => 'EDVSAgenceCentreBundle:Centre',
                                'placeholder'   => 'Choisir un centre',
                                'choices'       => $centres,
                                'choice_label'  => 'nom',
                            ));

                ici tu dois comparer l'objet soumis dans le form, et ton objet en bdd (il sera vide pendant la création) tu dois comparer l'agence en bdd et l'agence du form

                            $form->add('centre', EntityType::class, array(
                                'class'         => 'EDVSAgenceCentreBundle:Centre',
                                'placeholder'   => 'Choisir un centre',
                                'choices'       => $centres,
                                'choice_label'  => 'nom',
                                'data' => (bddAgence != formAgence) ? new Centre : bddAgence->getCentre
                            ));

                c'est un exemple ... mais dans le cas d'une création bddAgence = null donc il va instancier un objet vide cente dans ton form (et donc un objet qu'il attend) et dans le cas d'une modification soit il reprendra ton entité bdd soit il instanciera un nouvel objet vide

                pour avoir l'agence courante j'utilise le $buider que j'injecte dans mon $modifier

                        $formModifier = function(FormInterface $form, EntityList $entityList = null) use ($builder){
                
                                'data' => ($entityList != $builder->getData()->getEntityList()) ? null : $builder->getData()->getFilterDQE()
                




                si jamais voici mon code pour mon form :

                <?php
                
                namespace App\Form\Admin;
                
                use App\Entity\DQEInterface;
                use App\Entity\EntityList;
                use App\Entity\Filter;
                use App\Form\FilterDQEType;
                use Symfony\Bridge\Doctrine\Form\Type\EntityType;
                use Symfony\Component\Form\AbstractType;
                use Symfony\Component\Form\Form;
                use Symfony\Component\Form\FormBuilderInterface;
                use Symfony\Component\Form\FormEvent;
                use Symfony\Component\Form\FormEvents;
                use Symfony\Component\Form\FormInterface;
                use Symfony\Component\OptionsResolver\OptionsResolver;
                
                class FilterFormType extends AbstractType
                {
                
                    public function buildForm(FormBuilderInterface $builder, array $options)
                    {
                        $builder
                            ->add('userType')
                            ->add('entityList', EntityType::class, [
                                'class' => EntityList::class,
                                'required' => true,
                                'placeholder' => 'Choisissez votre entité',
                            ])
                        ;
                
                        $formModifier = function(FormInterface $form, EntityList $entityList = null) use ($builder){
                
                            $choiceDQEList = $this->getListeDQEForForm($entityList);
                
                            $form->add('filterDQE', FilterDQEType::class, [
                                'disabled' => empty($choiceDQEList) ? true : false,
                                'data_parent' => $choiceDQEList,
                                'data' => ($entityList != $builder->getData()->getEntityList()) ? null : $builder->getData()->getFilterDQE()
                            ]);
                        };
                
                        // nous créons le champs grace au form modifier
                        $builder->addEventListener(
                            FormEvents::PRE_SET_DATA,
                            function (FormEvent $event) use ($formModifier) {
                
                                $form = $event->getForm();
                                $entityList = $event->getData()->getEntityList();
                                $formModifier($form, $entityList);
                
                            }
                        );
                
                        $builder->get('entityList')->addEventListener(
                            FormEvents::POST_SUBMIT,
                            function (FormEvent $event) use ($formModifier) {
                                $form = $event->getForm()->getParent();
                                $entityList = $event->getForm()->getData();
                                $formModifier($form, $entityList);
                            }
                        );
                
                
                    }
                
                    public function configureOptions(OptionsResolver $resolver)
                    {
                        $resolver->setDefaults([
                            'data_class' => Filter::class,
                        ]);
                    }
                
                    public function getListeDQEForForm(?EntityList $entityList)
                    {
                        $object = null;
                        if($entityList){
                            $object = '\App\Entity\\'.$entityList->getName();
                            /** @var DQEInterface $object */
                            $object = new $object;
                        }
                
                        $listDQE = method_exists($object, 'getListDQE') ? $object->getListDQE() : [];
                
                        return $listDQE;
                    }
                }
                

                et mon ajax (la doc sf ;) )

                {% extends '@EasyAdmin/default/new.html.twig' %}
                {% form_theme form with easyadmin_config('design.form_theme') %}
                
                {% block entity_form %}
                
                    {{ form_start(form, { 'attr' : { 'class': 'form-horizontal  new-form' } }) }}
                
                    {{ form_widget(form) }}
                    <button class="btn btn-primary pull-right">Enregistrer</button>
                
                    {{ form_end(form) }}
                
                {% endblock entity_form %}
                
                {% block body_javascript %}
                    {{ parent() }}
                
                    <script>
                
                        $(document).ready(function(){
                            $('[data-toggle="tooltip"]').tooltip()
                        });
                
                        let $entityList = $('#filter_form_entityList');
                        let $filterDQE = $('#filter_form_filterDQE');
                        // When sport gets selected ...
                        $entityList.change(function() {
                            // ... retrieve the corresponding form.
                            let $form = $(this).closest('form');
                            // Simulate form data, but only include the selected value.
                            let data = {};
                            data[$entityList.attr('name')] = $entityList.val();
                            
                            // Submit data via AJAX to the form's action path.
                            $.ajax({
                                url : $form.attr('action'),
                                type: $form.attr('method'),
                                data : data,
                                success: function(html) {
                                    $('#filter_form_filterDQE').replaceWith(
                                        // ... with the returned one from the AJAX response.
                                        $(html).find('#filter_form_filterDQE')
                                    );
                
                                    $('[data-toggle="tooltip"]').tooltip()
                
                
                                }
                            });
                        });
                    </script>
                
                    <script type="text/javascript" src="{{ asset('build/app.js') }}"></script>
                
                {% endblock %}





                -
                Edité par YannickLecesne 17 novembre 2018 à 10:45:19

                • Partager sur Facebook
                • Partager sur Twitter
                  3 janvier 2019 à 11:57:37

                  Bonjour,

                  Avez-vous essayé de faire avec un Serialize

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



                  • Partager sur Facebook
                  • Partager sur Twitter

                  Symfony/Listes déroulantes dynamiques "null given"

                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                  • Editeur
                  • Markdown