Partage
  • Partager sur Facebook
  • Partager sur Twitter

Symfony

Formulaire imbriqué et relation OneToMany

    17 décembre 2017 à 21:22:51

    Bonsoir,

    J'essaie d'améliorer l'application sur laquelle je travaille suite au cours du site sur Symfony 3 et je rencontre un soucis pour gérer l'ajout d'un formulaire imbriqué. 

    En reprenant le contexte de l'application, j'ai un formulaire d'ajout des annonces avec toutes les informations d'une annonce enregistrée dans la même table "advert". Je souhaite maintenant ajouter un champ au sein de mon formulaire afin d'afficher les différentes compétences "PHP, C++, C" par exemple. 

    Pour cela, au niveau de mes entités, j'ai bien entendu une table de liaison entre mon entité advert et mon entité skill qui se nomme AdvertSkill. Elle fait référence aux entités advert et skill via une relation ManyToOne.

    Dans l'idée on a vite envie d'écrire quelque chose du genre au sein de mon AdvertType (formulaire de l'ajout d'annonces) : 

                ->add('name', EntityType::class, array(
                    'class'             =>  'OCPlatformBundle:Skill',
                    'choice_label'      =>  'name',
                    'multiple'          =>  true,
                ))

    Le problème majeur étant que je n'ai pas d'attribut "name" dans mon entité advert, qui correspond au nom des compétences. A vrai dire, je ne vois pas bien comment faire le lien pour réussir à récupérer les différentes compétences enregistrées en base de données afin qu'elles soient correctement liées aux annonces à l'ajout, avec le niveau choisi "Débutant, Intermédiaire, Expert". L'attribut "level" correspondant au niveau est un attribut de la table de liaison "AdvertSkill".

    EDIT : 

    Bonjour,

    J'ai avancé un petit peu sur la problématique mais je ne parviens pas à afficher de données dans mon formulaire, un truc m'échappe et je ne sais pas encore quoi.

    Ma classe du formulaire AdvertType actuellement : 

    $builder
                ->add('date',       DateTimeType::class, array(
                    'view_timezone' => 'Europe/Paris',  // On affiche l'horaire avec le fuseau horaire de Paris
                    'with_seconds' => true              // On ajoute les secondes àl'affichage de l'horaire
                ))
                ->add('title',      TextType::class)
                ->add('content',    TextareaType::class)
                ->add('author',     TextType::class)
                ->add('email',      TextType::class)
                ->add('image',      ImageType::class)
                ->add('categories', EntityType::class, array(
                    'class'             =>  'OCPlatformBundle:Category',
                    'choice_label'      =>  'name',
                    'multiple'          =>  true
                ))
                ->add('advertSkill', CollectionType::class, array(
                    'entry_type'        => AdvertSkillType::class
                ))
                ->add('save',       SubmitType::class);

    Ma nouvelle classe de formulaire AdvertSkillType : 

            $builder
                ->add('advertSkill', EntityType::class, array(
                    'class'                  =>  'OCPlatformBundle:Skill',
                    'choice_label'           =>  'name'
                ))
                ->add('level', EntityType::class, array(
                    'class'             =>  'OCPlatformBundle:AdvertSkill',
                    'choices'           =>  array('Débutant', 'Intermédiaire', 'Expert')
                ));

    Les attributs de ma classe AdvertSkill : 

      /**
       * @ORM\Column(name="id", type="integer")
       * @ORM\Id
       * @ORM\GeneratedValue(strategy="AUTO")
       */
      private $id;
    
      /**
       * @ORM\Column(name="level", type="string", length=255)
       */
      private $level;
    
      /**
       * @ORM\ManyToOne(targetEntity="OC\PlatformBundle\Entity\Advert", inversedBy="advertSkill")
       * @ORM\JoinColumn(nullable=false)
       */
      private $advert;
    
      /**
       * @ORM\ManyToOne(targetEntity="OC\PlatformBundle\Entity\Skill", inversedBy="advertSkill")
       * @ORM\JoinColumn(nullable=false)
       */
      private $skill;

    L'attribut advertSkill créé dans la classe Advert : 

       /**
        * @ORM\OneToMany(targetEntity="OC\PlatformBundle\Entity\AdvertSkill", mappedBy="advert")
        */
        private $advertSkill;

    L'attribut advertSkill créé dans la classe Skill : 

      /**
       * @ORM\OneToMany(targetEntity="OC\PlatformBundle\Entity\AdvertSkill", mappedBy="skill")
       */
      private $advertSkill;





    -
    Edité par Romain Rnd 18 décembre 2017 à 17:19:24

    • Partager sur Facebook
    • Partager sur Twitter
      22 décembre 2017 à 11:31:28

      Bonjour,

      Je me permets de relancer le sujet après quelques jours en flottement.. :-)

      • Partager sur Facebook
      • Partager sur Twitter
        22 décembre 2017 à 11:42:13

        Salut !

        Romain Rnd a écrit:

        […] un truc m'échappe et je ne sais pas encore quoi. […]

        En fait nous non plus, vu que tu ne nous dis pas quel est ton problème actuel  :p
        Tu n'as fait que nous donner ton nouveau code.

        -
        Edité par Ymox 22 décembre 2017 à 11:42:54

        • Partager sur Facebook
        • Partager sur Twitter
          22 décembre 2017 à 13:06:10

          Salut ! 

          En fait, je ne sais pas comment gérer le fait d'afficher deux champs d'un formulaire dans le champ d'un autre formulaire, c'est clairement de l'imbrication de formulaires en soi. 

          La particularité ici étant que j'ai une relation Many-To-One de mon entité advertSkill (comprenant le niveau exigé) vers l'entité advert (mon annonce) et vers l'entité skill (les compétences). 

          J'ai suivi les conseils glanés ici et là sur le web, j'ai donc mis mon champs du formulaire de l'annonce correspondant à l'attribut advertSkill ajouté dans l'entité advert, en type Collection dans le but de contenir les deux champs (skill et level) de mon formulaire imbriqué advertSkillType.

          J'ai également ajouté un attribut advertSkill dans mon entité skill tout comme je l'ai fais pour mon entité advert afin de faire la correspondance entre les données à afficher.

          En revanche, avec le recul, j'ai créé un tableau pour afficher les différentes valeurs du niveau (Débutant, Intermédiaire, Expert) mais je n'ai pas créé de requête pour récupérer mes compétences en base de données afin de les afficher dans le champs nommé skill...

          J'espère avoir été clair sinon je peux reformuler mes explications.

          -
          Edité par Romain Rnd 22 décembre 2017 à 13:09:00

          • Partager sur Facebook
          • Partager sur Twitter
            22 décembre 2017 à 22:21:35

            Juste histoire d'être sûr : tu cherches à afficher un formulaire pour renseigner une annonce (Advert), tout en précisant les compétences et leurs niveaux (AdvertSkill), ou tu es plus loin ? Parce que tes explications me font penser que tu es juste, donc je ne comprends toujours pas ton problème… Plus concrètement, quels sont ces deux champs d'un formulaire que tu veux afficher dans le champ d'un autre ?

            Le champ pour Advert#advertSkill doit être CollectionType, et le type de la collection AdvertSkillType, probablement que c'est aussi simple que ça. Tu as une collection d'objets AdvertSkill dans une propriété d'un autre objet (Advert#advertSkills — pense au pluriel, ça va t'aider), donc tu auras un champ de type collection pour cette propriété Advert#AdvertSkills, collection de types du premier objet AdvertSkill — qui est donc l'objet "enfant".

            • Partager sur Facebook
            • Partager sur Twitter
              3 janvier 2018 à 16:58:29

              Bonjour et bonne année ! :)

              J'ai dû faire une bonne pause pendant les fêtes niveau informatique...

              Oui, je cherche à ajouter à mon formulaire d'annonces la liste des compétences disponibles (pouvoir en sélectionner plusieurs) ainsi qu'une liste des niveaux (un seul possible : débutant, intermédiaire, expert). 

              Les deux champs du formulaire advertSkillType, je veux les afficher dans le formulaire d'ajout d'une annonce. Il ressemblent à cela : 

                      $builder
                          ->add('names', EntityType::class, array(
                              'class'                  =>  'OCPlatformBundle:Skill',
                              'query_builder'          =>  function (EntityRepository $er) {
                                  return $er->createQueryBuilder('s')
                                      ->orderBy('s.name', 'ASC');
                              },
                              'choice_label'           =>  'name'
                          ));
                          ->add('levels', EntityType::class, array(
                              'class'             =>  'OCPlatformBundle:AdvertSkill',
                              'choices'           =>  array(0 => 'Débutant', 1 => 'Intermédiaire', 2 => 'Expert')
                          ));
                  }

              J'ai créé en plus les attributs "levels" (liste des niveaux) et "names" (liste des compétences) sous forme d'ArrayCollection dans les classes concernées avec leurs getters mais je m'y perds honnêtement. Je ne sais pas quelle est la bonne façon de procéder.

              • Partager sur Facebook
              • Partager sur Twitter
                4 janvier 2018 à 8:44:03

                Il y a une chose que je ne comprends pas : tu mets que le champ levels est EntityType, et utilise la classe AdvertSkill. Ce qui, si je traduis en français, veut dire que la classe AdvertSkill représente uniquement un niveau… Tu devrais plutôt utiliser ChoiceType et supprimer ce qui correspond à la ligne 11.

                Avec le code de ton message ci-dessus, c'est comme si tu avais une relation sur la propriété AdvertSkill#levels vers l'entité AdvertSkill, en fait. Et je ne pense pas que ce soit ce que tu souhaites.

                • Partager sur Facebook
                • Partager sur Twitter
                  4 janvier 2018 à 11:17:41

                  Effectivement, j'ai pris exemple sur l'attributs categories de ma classe advert, qui est une collection de catégories. Et le type du champ categories est EntityType.

                  Je dis que je m'y perds parce que j'ai testé plusieurs solutions qui ne fonctionnent pas, quelque chose m'échappe. Je ne sais pas concrètement ou placer mes attributs levels et names (les compétences en fait) et s'il faut même créer ces attributs au pluriel.

                  Ce dont je te parlais pour l'exemple des categories liées à une annonce : 

                  - Classe advert :

                      /**
                       * @ORM\ManyToMany(targetEntity="OC\PlatformBundle\Entity\Category", cascade={"persist"})
                       * @ORM\JoinTable(name="oc_advert_category")
                       */
                      private $categories;
                  

                  - Classe category : 

                  <?php
                  // src/OC/PlatformBundle/Entity/Category.php
                  
                  namespace OC\PlatformBundle\Entity;
                  
                  use Doctrine\ORM\Mapping as ORM;
                  
                  /**
                   * @ORM\Entity
                   * @ORM\Table(name="oc_category")
                   */
                  class Category
                  {
                    /**
                     * @ORM\Column(name="id", type="integer")
                     * @ORM\Id
                     * @ORM\GeneratedValue(strategy="AUTO")
                     */
                    private $id;
                  
                    /**
                     * @ORM\Column(name="name", type="string", length=255)
                     */
                    private $name;
                  
                    public function getId()
                    {
                      return $this->id;
                    }
                  
                    public function setName($name)
                    {
                      $this->name = $name;
                    }
                  
                    public function getName()
                    {
                      return $this->name;
                    }
                  }

                  - Classe advertType : 

                  class AdvertType extends AbstractType
                  {
                      public function buildForm(FormBuilderInterface $builder, array $options)
                      {
                          $builder
                              ->add('date',       DateTimeType::class, array(
                                  'view_timezone' => 'Europe/Paris',  // On affiche l'horaire avec le fuseau horaire de Paris
                                  'with_seconds' => true              // On ajoute les secondes à l'affichage de l'horaire
                              ))
                              ->add('title',      TextType::class)
                              ->add('content',    TextareaType::class)
                              ->add('author',     TextType::class)
                              ->add('email',      TextType::class)
                              ->add('image',      ImageType::class)
                              ->add('categories', EntityType::class, array(
                                  'class'             =>  'OCPlatformBundle:Category',
                                  'choice_label'      =>  'name',
                                  'multiple'          =>  true
                              ))
                              ->add('advertSkill', CollectionType::class, array(
                                  'entry_type'        => AdvertSkillType::class,
                                  'allow_add'         => true
                              ))
                              ->add('save',       SubmitType::class);
                  
                          $builder->addEventListener(
                              FormEvents::PRE_SET_DATA,               // 1er argument : L'évènement qui nous intéresse : ici, PRE_SET_DATA
                              function(FormEvent $event) {            // 2ème argument : La fonction à exécuter lorsque l'évènement est déclenché
                                  $advert = $event->getData();        // On récupère notre objet Advert sous-jacent 
                  
                                  // Cette condition est importante, on en reparle plus loin
                                  if (null == $advert) {
                                      return;                         // On sort de la fonction sans rien faire lorsque $advert vaut null
                                  }
                  
                                  // Si l'annonce n'est pas publiée, ou si elle n'existe pas encore en base (id est null)
                                  if (!$advert->getPublished() || null == $advert->getId()) {
                                      // Alors on ajoute le champ published
                                      $event->getForm()->add('published', CheckboxType::class, array('required' => false));
                                  } else {
                                      // Sinon, on le supprime
                                      $event->getForm()->remove('published');
                                  }
                              }
                          );
                      }
                      
                      
                      public function configureOptions(OptionsResolver $resolver)
                      {
                          $resolver->setDefaults(array(
                              'data_class' => 'OC\PlatformBundle\Entity\Advert'
                          ));
                      }
                  }
                  

                  J'ajoute la classe advertSkill afin de clarifier la situation : 

                  <?php
                  // src/OC/PlatformBundle/Entity/AdvertSkill.php
                  
                  namespace OC\PlatformBundle\Entity;
                  
                  use Doctrine\ORM\Mapping as ORM;
                  
                  use Doctrine\Common\Collections\ArrayCollection;
                  
                  /**
                   * @ORM\Entity
                   * @ORM\Table(name="oc_advert_skill")
                   */
                  class AdvertSkill
                  {
                    /**
                     * @ORM\Column(name="id", type="integer")
                     * @ORM\Id
                     * @ORM\GeneratedValue(strategy="AUTO")
                     */
                    private $id;
                  
                    /**
                     * @ORM\Column(name="level", type="string", length=255)
                     */
                    private $levels;
                  
                    /**
                     * @ORM\ManyToOne(targetEntity="OC\PlatformBundle\Entity\Advert", inversedBy="advertSkill")
                     * @ORM\JoinColumn(nullable=false)
                     */
                    private $advert;
                  
                    /**
                     * @ORM\ManyToOne(targetEntity="OC\PlatformBundle\Entity\Skill", inversedBy="advertSkill")
                     * @ORM\JoinColumn(nullable=false)
                     */
                    private $skill;
                  
                      public function __construct () 
                      {
                        $this->levels = new ArrayCollection();
                      }
                  
                      public function getId()
                      {
                          return $this->id;
                      }
                  
                  
                    
                      public function getLevels()
                      {
                          return $this->levels;
                      }
                  
                   
                      public function setAdvert(\OC\PlatformBundle\Entity\Advert $advert)
                      {
                          $this->advert = $advert;
                      }
                  
                   
                      public function getAdvert()
                      {
                          return $this->advert;
                      }
                  }




                  • Partager sur Facebook
                  • Partager sur Twitter
                    4 janvier 2018 à 11:50:35

                    Au premier regard, les propriétés me paraissent être dans les entités où je les aurais moi-même mises.

                    Maintenant, pour ce qui est de savoir où placer tes propriétés, c'est toi qui sait selon les informations que tu souhaites enregistrer. Parmi les questions qu'il faudrait se poser pour bien faire, il y a "qu'est-ce que j'ai de concret à enregistrer". Ici, des compétences, des annonces, des catégories. Ça, ce sont très souvent des entités au niveau de Doctrine.

                    Après viennent donc les relations. Une annonce est dans une catégorie, et une catégorie aura (je l'espère pour toi) plusieurs annonces. Donc une Many(Advert)ToOne(Category) — j'ai bien vu que tu as une ManyToMany entre les deux dernières entités, je voulais juste donner un exemple. ^^
                    Une annonce demande plusieurs compétences, et chacune de ces compétences peut concerner plusieurs annonces, donc c'est une Many(Advert)ToMany(Skill).

                    A toi de voir s'il te faut la relation inverse, ce n'est pas obligatoire ni une bonne pratique, mais au cas par cas, je pense que dans celui que j'ai pris comme exemole il faudra justement une relation bi-directionnelle. Note que dès que tu as une relation *ToMany depuis une entité, tu devrais mettre la propriété "de départ" au pluriel. Donc Many(Advert#skills)ToMany(Skill#adverts) et One(Advert#category)ToMany(Category#adverts).

                    Maintenant, il y a le cas du niveau de compétence demandé par l'annonce. Un niveau concerne une annonce et une compétence, pas directement l'annonce ni directement une compétence. Du coup, la ManyToMany dont on a parlé juste avant devient une One(Advert)ToMany(AdvertSkill)ToOne(Skill). Cette entité permet en fait de qualifier la relation initiale entre l'annonce et la compétence en spécifiant le niveau.

                    Pour ce qui est des formulaires maintenant, il faut là aussi penser par relation ou en cascade. Tu souhaites qu'on puisse saisir une annonce, donc il te faut un AdvertType. Cette annonce doit mentionner certaines capacités et à certains niveaux, donc liaison avec AdvertSkillType. Le fait d'utiliser une collection est très courant dans les cas de "OneToManyToOne", parce que le EntityType avec AdvertSkill ne permettrait de choisir que dans des objets AdvertSkill existants. Donc obligé de choisir une paire compétence - niveau déjà définie… ce qui n'est pas le but.

                    -
                    Edité par Ymox 4 janvier 2018 à 11:50:57

                    • Partager sur Facebook
                    • Partager sur Twitter
                      15 janvier 2018 à 18:23:18

                      Bonsoir, 

                      Merci pour ta dernière réponse, je te suis jusque là. J'ai dorénavant mon champs compétences bien alimenté et mon champs niveau bien alimenté également. Cependant, je dois maintenant concevoir un script JQuery pour à la sélection d'une compétence et d'un niveau, enregistrer cette information afin qu'elle soit visible sur l'interface d'ajout en gérant la possibilité d'ajouter d'autres couples de compétences/niveaux. 

                      Ces différents couples doivent être également pris en compte par Doctrine derrière pour être insérés en BDD. Est-ce que ça te parle ? 

                      J'avoue que c'est un peu corsé l'affaire pour le moment...

                      • Partager sur Facebook
                      • Partager sur Twitter
                        15 janvier 2018 à 22:11:50

                        Qu'est-ce qui enregistre une compétence et un niveau ? Ton entité AdvertSkill. Et chouette ! Elle est liée à ton entité Advert. Donc tu vas te créer un champ pour avoir des AdvertSkill dans le formulaire de Advert. Une collection d'AdvertSkillType.

                        -
                        Edité par Ymox 16 janvier 2018 à 10:08:18

                        • Partager sur Facebook
                        • Partager sur Twitter
                          16 janvier 2018 à 10:05:48

                          Salut, 

                          C'est déjà le cas ! C'est ce que j'ai écris, j'ai actuellement le code ci-dessous dans ma classe advert : 

                              /**
                               * @ORM\OneToMany(targetEntity="OC\PlatformBundle\Entity\AdvertSkill", mappedBy="advert")
                               */                                                                   
                              private $advertSkills;
                          
                              public function __construct() 
                              {
                                  //Par défaut, la date de l'annonce est la date d'aujourd'hui
                                  $this->date = new \DateTime();
                                  $this->categories = new ArrayCollection();
                                  $this->applications = new ArrayCollection();
                                  $this->advertSkills = new ArrayCollection();
                              }
                          
                          
                              public function setAdvertSkills(\OC\PlatformBundle\Entity\advertSkills $advertSkill)
                              {
                                  $this->advertSkills[] = $advertSkill;
                              }
                          
                          
                              public function getAdvertSkills()
                              {
                                  return $this->advertSkills;
                              }

                          Par contre, je ne sais pas si c'est correct mais j'ai également cette collection d'advertSkill au sein de ma classe skill.

                          Ci-dessous la classe advertSkill :

                            /**
                             * @ORM\ManyToOne(targetEntity="OC\PlatformBundle\Entity\Advert", inversedBy="advertSkills")
                             * @ORM\JoinColumn(nullable=false)
                             */
                            private $advert;
                          
                            /**
                             * @ORM\ManyToOne(targetEntity="OC\PlatformBundle\Entity\Skill", inversedBy="advertSkills")
                             * @ORM\JoinColumn(nullable=false)
                             */
                            private $skill;
                          

                          Et ci-dessous la classe skill : 

                            /**
                             * @ORM\OneToMany(targetEntity="OC\PlatformBundle\Entity\AdvertSkill", mappedBy="skill")
                             */ 
                            private $advertSkills;
                          
                            public function setAdvertSkills(\OC\PlatformBundle\Entity\advertSkills $advertSkill)
                            {
                              $this->advertSkills[] = $advertSkill;
                            }
                          
                            public function getAdvertSkills()
                            {
                              return $this->advertSkills;
                            }

                          Quelque chose doit clairement m'échapper parce que je me triture le cerveau pour pouvoir ajouter plusieurs couples de skill/level sachant qu'il y en a un qui est un entityType et l'autre un ChoiceType. Pour le moment, je ne peux choisir qu'un seul couple sur le formulaire et soumettre le tout..



                          • Partager sur Facebook
                          • Partager sur Twitter
                            16 janvier 2018 à 10:08:59

                            Tu me montres ton formulaire AdvertType actuel, s'il te plaît ?

                            • Partager sur Facebook
                            • Partager sur Twitter
                              16 janvier 2018 à 11:12:28

                              Voici : 

                                      $builder
                                          ->add('date',       DateTimeType::class, array(
                                              'view_timezone' => 'Europe/Paris',  // On affiche l'horaire avec le fuseau horaire de Paris
                                              'with_seconds' => true              // On ajoute les secondes à l'affichage de l'horaire
                                          ))
                                          ->add('title',      TextType::class)
                                          ->add('content',    TextareaType::class)
                                          ->add('author',     TextType::class)
                                          ->add('email',      TextType::class)
                                          ->add('image',      ImageType::class)
                                          ->add('categories', EntityType::class, array(
                                              'class'             =>  'OCPlatformBundle:Category',
                                              'choice_label'      =>  'name',
                                              'multiple'          =>  true
                                          ))
                                          ->add('advertSkills', CollectionType::class, array(
                                              'entry_type'        => AdvertSkillType::class,
                                              'prototype'         => true,
                                              'allow_add'         => true,
                                              'allow_delete'      => true,
                                              'label'             => false
                                          ))
                                          ->add('save',       SubmitType::class);

                              Je suis en train de supprimer l'attribut $advertSkills dans mon entité Skill parce que je pense qu'il n'a rien à faire là sachant qu'il est déjà dans l'entité advert, ce qui est plus logique je pense. 

                              D'ailleurs je pense que la liaison entre l'attribut $advert de l'entité advertSkill et l'attribut $advertSkills de l'entité advert n'a pas lieu d'être si je ne m'abuse... il faut que j'en créé un nouveau spécialement pour faire la liaison vers la collection d'advertSkills.

                              -
                              Edité par Romain Rnd 16 janvier 2018 à 11:14:50

                              • Partager sur Facebook
                              • Partager sur Twitter
                                16 janvier 2018 à 11:33:42

                                Si si, il te faut la dernière relation dont tu parles. Sans ça, tu fais comment pour demander un niveau particulier d'une compétence particulière pour une annonce ? Reprends le raisonnement que j'ai exposé six messages ci-dessus.

                                Pour la relation entre AdvertSkill et Skill, il te la faut au moins dans un sens (depuis AdvertSkill vers Skill, au vu de tes besoins). Mais comme je l'ai dit, des relations bi-directionnelles ne sont pas toujours nécessaires. Cependant, si tu imagines pouvoir proposer un moteur de recherches qui permet de faire le tri des annonces par compétences, alors il te faudrait avoir une relation bi-directionnelle.

                                Je vois qu'il te manque 'by_reference' => false dans la déclaration du champ de formulaire pour la collection.

                                -
                                Edité par Ymox 21 octobre 2023 à 0:43:51

                                • Partager sur Facebook
                                • Partager sur Twitter
                                  16 janvier 2018 à 14:27:15

                                  En fait, je n'ai pas l'esprit clair et je manque de recul parce que je pense ne pas avoir encore bien compris le fonctionnement complet des relations gérées par Doctrine.

                                  Du coup, j'ai corrigé en prenant en compte ta remarque ! J'ai donc ma collection d'advertSkills au sein de mon entité advert à travers l'attribut $advertSkills qui est une collection en relation OneToMany vers mon entité advertSkill via l'attribut $advert qui lui est à l'inverse en relation ManyToOne vers l'attribut $advertSkills de mon entité advert. On se suit jusque là ?

                                  On est d'accord que cet attribut $advert au sein de mon entité advertSkill, c'est lui qui permet au sein de la BDD d'avoir ce lien par la clé primaire $advert vers la collection d'advertSkills ?

                                  Par ailleurs, je suis actuellement sur un script que je suis en train de modifier pour pouvoir générer des couples level/skill dans mon formulaire et en supprimer.

                                  -
                                  Edité par Romain Rnd 16 janvier 2018 à 14:30:41

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    16 janvier 2018 à 15:58:24

                                    D'abord, une petite astuce pour savoir quelle relation il y a entre 2 entités :

                                    une Annonce a une seule Catégorie, donc on a une relation Annonce -> xxxToOne -> Catégorie

                                    Une Catégorie a plusieurs Annonces, donc on a une relation Catégorie -> xxxToMany -> Annonce

                                    Donc, on se retrouve avec "One Catégorie" et "Many Annonce", et donc la relation sera Annonce -> ManyToOne -> Catégorie

                                    une Annonce a plusieurs Skills, donc on a une relation Annonce -> xxxToMany -> Skill

                                    un Skill a plusieurs Annonces, donc on a une relation Skill -> xxxToMany ->Annonce

                                    A première vue on a donc une relation ManyToMany, mais il y a un autre paramètre à prendre en compte : a-t-on des informations supplémentaires à enregistrer concernant cette relation ? Par exemple, si on veut préciser le Level requis pour ce Skill dans cette Annonce. Dans ce cas, on transforme notre relation ManyToMany en relation OneToMany-ManyToOne avec une entité AnnonceSkill au milieu.

                                    On obtient donc au final une relation Annonce -> OneToMany -> AnnonceSkill -> ManyToOne -> Skill, et on enregistre notre paramètre Level dans la nouvelle entité AnnonceSkill.

                                    Une fois qu'on a enregistré ces relations dans nos entité (et éventuellement créé AnnonceSkill), on peut s'attaquer au formulaire AnnonceType. Attention de ne pas oublier de générer les getters et les setters de notre nouvelle entité avec 

                                    php bin\console d:g:entities...

                                    et de mettre à jour la structure de la base de donnée avec

                                    php bin\console d:s:u --force

                                    Commençons avec la Catégorie : pour une relation ManyToOne, on utilise en général un EntityType, qui nous permettra de sélectionner une catégorie.

                                    Poursuivons avec les Skills (les AnnonceSkill en fait) : pour une relation OneToMany, on passe principalement par un CollectionType, mais ça implique qu'on va devoir créer un formulaire AnnonceSkillType, et c'est lui qui sera répété autant de fois que nécessaire.

                                    Ce formulaire AnnonceSkillType comprendra donc un EntityType (pour sélectionner un Skill). Et comme on a vu que Level est un paramètre de AnnonceSkill, c'est ici qu'on va le mettre. Comme on a 3 Levels possibles (toujours les même), le plus adapté est d'utiliser un ChoiceType avec les 3 valeurs pré-renseignées :

                                    $builder->add('Level', ChoiceType::class, array(
                                        'choices'  => array(
                                    	'Débutant' => 1,
                                    	'Intermédiaire' => 2,
                                    	'Confirmé' => 3,
                                        )
                                    ))
                                    

                                    Voilà pour un survol du problème, en espérant avoir été suffisamment clair...

                                    -
                                    Edité par HarvestR 16 janvier 2018 à 16:22:29

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      17 janvier 2018 à 12:44:01

                                      Merci HarvestR pour ton éclaircissement général sur la situation !

                                      Je rencontre un petit soucis actuellement ! Je remonte l'erreur suivante : 

                                      Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "OC\PlatformBundle\Entity\Advert#$advertSkills", got "Doctrine\Common\Collections\ArrayCollection" instead.

                                      Il me dit concrètement qu'il attend le type Collection|Array puisque c'est le retour donné par le formulaire au niveau de mon champ advertSkills de type Collection. J'ai bien mon attribut $advertSkills dans ma classe advert de type ArrayCollection initialisé via le constructeur, ce qui me semble logique. Pourquoi me pose t-il cet ultimatum du coup ?

                                      D'autant plus que ArrayCollection() implémente l'interface Collection à la base, je trouve ça curieux.

                                      Autrement, mon script en jQuery fonctionne correctement pour ajouter ou effacer un couple de skill/level du formulaire. :)

                                      -
                                      Edité par Romain Rnd 17 janvier 2018 à 12:44:57

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        17 janvier 2018 à 14:41:58

                                        Je ne sais pas d'où vient cette erreur, je ne l'ai jamais rencontrée.

                                        Voici un exemple de formulaire imbriqués que j'utilise :

                                        // Le formulaire parent (PlatType)
                                        
                                        public function buildForm(FormBuilderInterface $builder, array $options)
                                        {
                                            $builder
                                        	->add('nom', TextType::class)
                                        	->add('categorie', EntityType::class, array(
                                        	    'class' => PlatCategorie::class,
                                        	    'choice_label' => function ($categorie) {
                                        		return $categorie->getDisplay();}
                                        	))
                                        	->add('ingredients', CollectionType::class, array(
                                        	    'entry_type' => IngredPlatType::class,
                                        	    'required' => false,
                                        	    'allow_add'    => true,
                                        	    'allow_delete' => true,
                                        	    'by_reference' => false,
                                        	    'label' => false
                                        	))
                                            ;
                                        }
                                        
                                        // le formulaire enfant répété (IngredPlatType)
                                        public function buildForm(FormBuilderInterface $builder, array $options)
                                        {
                                            $builder
                                        	->add('ingred', HiddenType::class, array('invalid_message' => 'Ingrédient non reconnu',))
                                        	->add('qte', TextType::class)
                                        	->add('prix', TextType::class, array('mapped' => false))
                                        	->add('unite', HiddenType::class)
                                        	->add('isRetire', CheckboxType::class, array('label'    => 'Retiré après cuisson',))
                                        	->add('Enregistrer', SubmitType::class)
                                            ;
                                        }

                                        ATTENTION : Pour les collections avec "allow_add" et/ou "allow_delete", il faut mettre le paramètre "By_reference => false"


                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          17 janvier 2018 à 16:09:15

                                          J'ai bien la même structure que toi, je comprends pas non plus pour le moment ! 

                                          Je pense qu'il doit y avoir un soucis au niveau d'une des entités en terme de relations... le type ArrayCollection est bien présent dans mon entité advert et c'est ce qu'on utilisé couramment dans ce cas de figure donc je piges pas pourquoi il me dit qu'un type Collection|Array est attendu...

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            17 janvier 2018 à 18:06:17

                                            Tu peux mettre une copie de ton entité Advert ici ?
                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              17 janvier 2018 à 18:48:37

                                              /**
                                               * Advert
                                               *
                                               * @ORM\Table(name="oc_advert")
                                               * @ORM\Entity(repositoryClass="OC\PlatformBundle\Repository\AdvertRepository")
                                               * @ORM\HasLifecycleCallbacks()
                                               */
                                              class Advert
                                              {
                                                  /**
                                                   * @var int
                                                   *
                                                   * @ORM\Column(name="id", type="integer")
                                                   * @ORM\Id
                                                   * @ORM\GeneratedValue(strategy="AUTO")
                                                   */
                                                  private $id;
                                              
                                                  /**
                                                   * @var \DateTime
                                                   *
                                                   * @ORM\Column(name="date", type="datetime")
                                                   * @Assert\DateTime()
                                                   */
                                                  private $date;
                                              
                                                  /**
                                                   * @var string
                                                   *
                                                   * @ORM\Column(name="title", type="string", length=255)
                                                   * @Assert\Length(min=10)
                                                   */
                                                  private $title;
                                              
                                                  /**
                                                   * @var string
                                                   *
                                                   * @ORM\Column(name="author", type="string", length=255)
                                                   * @Assert\Length(min=2)
                                                   */
                                                  private $author;
                                              
                                                  /**
                                                   * @var string
                                                   *
                                                   * @ORM\Column(name="content", type="text")
                                                   * @Assert\NotBlank()
                                                   * @Antiflood()
                                                   */
                                                  private $content;
                                              
                                                  /**
                                                   * @ORM\Column(name="published", type="boolean")
                                                   */
                                                  private $published = true;
                                              
                                                  /**
                                                   * @ORM\OneToOne(targetEntity="OC\PlatformBundle\Entity\Image", cascade={"persist", "remove"})
                                                   * @Assert\Valid()
                                                  */
                                                  private $image;
                                              
                                                  /**
                                                   * @ORM\ManyToMany(targetEntity="OC\PlatformBundle\Entity\Category", cascade={"persist"})
                                                   * @ORM\JoinTable(name="oc_advert_category")
                                                   */
                                                  private $categories;
                                              
                                                  /**
                                                   * @ORM\OneToMany(targetEntity="OC\PlatformBundle\Entity\Application", mappedBy="advert")
                                                   */
                                                  private $applications;  // Notez le « s », une annonce est liée à plusieurs candidatures
                                              
                                                  /**
                                                   * @ORM\Column(name="updated_at", type="datetime", nullable=true)
                                                   */
                                                  private $updatedAt;
                                              
                                                  /**
                                                   * @ORM\Column(name="nb_applications", type="integer")
                                                   */
                                                  private $nbApplications = 0;
                                              
                                                  /**
                                                   * @ORM\Column(name="email", type="text", length=255)
                                                   */
                                                  private $email;
                                              
                                                  /**
                                                   * @Gedmo\Slug(fields={"title"})
                                                   * @ORM\Column(name="slug", type="string", length=255, unique=true)
                                                   */
                                                  private $slug;
                                              
                                                  /**
                                                   * @ORM\Column(name="ip", type="text", length=255)
                                                   */
                                                  private $ip;
                                              
                                                  /**
                                                   * @ORM\ManyToOne(targetEntity="OC\UserBundle\Entity\User", inversedBy="advert")
                                                   */
                                                  private $user;
                                              
                                                  /**
                                                   * @ORM\OneToMany(targetEntity="OC\PlatformBundle\Entity\AdvertSkill", mappedBy="advert")
                                                   */                                                                   
                                                  private $advertSkills;
                                              
                                              
                                                  public function __construct() 
                                                  {
                                                      //Par défaut, la date de l'annonce est la date d'aujourd'hui
                                                      $this->date = new \DateTime();
                                                      $this->categories = new ArrayCollection();
                                                      $this->applications = new ArrayCollection();
                                                      $this->advertSkills = new ArrayCollection();
                                                  }
                                              
                                              
                                                  public function getId()
                                                  {
                                                      return $this->id;
                                                  }
                                              
                                                
                                                  public function setDate($date)
                                                  {
                                                      $this->date = $date;
                                                  }
                                              
                                                
                                                  public function getDate()
                                                  {
                                                      return $this->date;
                                                  }
                                              
                                                 
                                                  public function setTitle($title)
                                                  {
                                                      $this->title = $title;
                                                  }
                                              
                                                
                                                  public function getTitle()
                                                  {
                                                      return $this->title;
                                                  }
                                              
                                                 
                                                  public function setAuthor($author)
                                                  {
                                                      $this->author = $author;
                                                  }
                                              
                                                
                                                  public function getAuthor()
                                                  {
                                                      return $this->author;
                                                  }
                                              
                                                 
                                                  public function setContent($content)
                                                  {
                                                      $this->content = $content;
                                                  }
                                              
                                                 
                                                  public function getContent()
                                                  {
                                                      return $this->content;
                                                  }
                                              
                                                
                                                  public function setPublished($published)
                                                  {
                                                      $this->published = $published;
                                                  }
                                              
                                                 
                                                  public function getPublished()
                                                  {
                                                      return $this->published;
                                                  }
                                              
                                                  
                                                  public function setImage(Image $image)
                                                  {
                                                      $this->image = $image;
                                                  }
                                              
                                                 
                                                  public function getImage()
                                                  {
                                                      return $this->image;
                                                  }
                                              
                                                
                                                  public function addCategory(\OC\PlatformBundle\Entity\Category $category)
                                                  {
                                                      $this->categories[] = $category;
                                                  }
                                              
                                                 
                                                  public function removeCategory(Category $category)
                                                  {
                                                      $this->categories->removeElement($category);
                                                  }
                                              
                                                  
                                                  public function getCategories()
                                                  {
                                                      return $this->categories;
                                                  }
                                              
                                                 
                                                  public function addApplication(\OC\PlatformBundle\Entity\Application $application)
                                                  {
                                                      $this->applications[] = $application;
                                              
                                                      //On lie l'annonce à la candidature
                                                      $application->setAdvert($this);
                                                  }
                                              
                                                 
                                                  public function removeApplication(\OC\PlatformBundle\Entity\Application $application)
                                                  {
                                                      $this->applications->removeElement($application);
                                              
                                                       // Et si notre relation était facultative (nullable=true, ce qui n'est pas notre cas ici attention) :        
                                                      // $application->setAdvert(null);
                                                  }
                                              
                                                  public function getApplications()
                                                  {
                                                      return $this->applications;
                                                  }
                                              
                                                
                                                  public function setUpdatedAt(\DateTime $updatedAt = null)
                                                  {
                                                      $this->updatedAt = $updatedAt;
                                                  }
                                              
                                                
                                                  public function getUpdatedAt()
                                                  {
                                                      return $this->updatedAt;
                                                  }
                                              
                                                  /**
                                                   *  @ORM\PreUpdate()
                                                   */
                                                  public function updateDate() 
                                                  {
                                                      $this->setUpdatedAt(new \DateTime());
                                                  }
                                              
                                                  public function increaseApplication() 
                                                  {
                                                      $this->nbApplications++;
                                                  }
                                              
                                                  public function decreaseApplication() 
                                                  {
                                                      $this->nbApplications--;
                                                  }
                                              
                                              
                                                  public function setNbApplications($nbApplications)
                                                  {
                                                      $this->nbApplications = $nbApplications;
                                                  }
                                              
                                                  public function getNbApplications()
                                                  {
                                                      return $this->nbApplications;
                                                  }
                                              
                                              
                                                  public function setSlug($slug)
                                                  {
                                                      $this->slug = $slug;
                                                  }
                                              
                                              
                                                  public function getSlug()
                                                  {
                                                      return $this->slug;
                                                  }
                                              
                                              
                                                  public function setEmail($email)
                                                  {
                                                      $this->email = $email;
                                                  }
                                              
                                                
                                                  public function getEmail()
                                                  {
                                                      return $this->email;
                                                  }
                                              
                                                  public function setIp($ip)
                                                  {
                                                      $this->ip = $ip;
                                                  }
                                              
                                                  public function getIp()
                                                  {
                                                      return $this->ip;
                                                  }
                                              
                                                  public function setUser($user)
                                                  {
                                                      $this->user = $user;
                                                  }
                                              
                                                  public function getUser() 
                                                  {
                                                      return $this->user;
                                                  }
                                              
                                                  public function setAdvertSkills(\Doctrine\Common\Collections\ArrayCollection $advertSkill)
                                                  {
                                                      $this->advertSkills[] = $advertSkill;
                                                  }
                                              
                                              
                                                  public function getAdvertSkills()
                                                  {
                                                      return $this->advertSkills;
                                                  }
                                              }
                                              EDIT :

                                              J'ai réussi à débloquer la situation ! Il devait y avoir une petite erreur dans le code au niveau des getters/setters de l'entité Skill de souvenir. 

                                              Et il fallait que je modifie les getters/setters de l'attribut $advertSkill ajouté à l'image de ceux de l'entité Application déjà existants.

                                                  public function addAdvertSkill(\OC\PlatformBundle\Entity\AdvertSkill $advertSkill)
                                                  {
                                                      $this->advertSkills->add($advertSkill);
                                              
                                                      $advertSkill->setAdvert($this);
                                                  }
                                              
                                                  public function removeAdvertSkill(\OC\PlatformBundle\Entity\AdvertSkill $advertSkill) 
                                                  {
                                                      $this->advertSkills->removeElement($advertSkill);
                                                  }
                                              
                                                  public function getAdvertSkills()
                                                  {
                                                      return $this->advertSkills;
                                                  }

                                              -
                                              Edité par Romain Rnd 17 janvier 2018 à 21:13:07

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                18 janvier 2018 à 11:19:10

                                                Avec un CollectionType ayant 'by_reference' => false, il faut effectivement enregistrer les 2 entités de la relation explicitement, comme tu l'as fait (enregistrer l'advertskill dans l'annonce, et enregistrer l'annonce dans le adverskill).

                                                Par ailleurs, j'ai vu que tu avais une propriété $nbApplications également. S'il s'agit du nombre de candidats enregistrés pour l'annonce, c'est un peu inutile, tu peux obtenir cette information en faisant un 'count' sur $Applications directement. De manière générale on n'enregistre pas les résultats d'un calcul (ici un comptage) dans la base de données. Sans compter que plus tard, quand le code se développera et deviendra plus complexe, tu risques d'oublier de mettre à jour cette information, et donc d'avoir une valeur fausse au final.

                                                -
                                                Edité par HarvestR 18 janvier 2018 à 11:50:02

                                                • Partager sur Facebook
                                                • Partager sur Twitter
                                                  18 janvier 2018 à 13:09:27

                                                  HarvestR a écrit:

                                                  Avec un CollectionType ayant 'by_reference' => false, il faut effectivement enregistrer les 2 entités de la relation explicitement, comme tu l'as fait (enregistrer l'advertskill dans l'annonce, et enregistrer l'annonce dans le adverskill).

                                                  C'est surprenant, je n'ai pas à le faire dans mes projets récents… A moins que dans "explicitement" tu inclus l'utilisation des cascade={"persist"} dans les annotations ? Ce comportement n'est effectivement pas précisé pour la relation qui nous occupe.

                                                  • Partager sur Facebook
                                                  • Partager sur Twitter
                                                    18 janvier 2018 à 13:39:45

                                                    C'est en fait expliqué dans la doc officielle, sous le chapitre "Doctrine: Cascading Relations and saving the "Inverse" side"

                                                    http://symfony.com/doc/3.4/form/form_collections.html#allowing-tags-to-be-removed

                                                    • Partager sur Facebook
                                                    • Partager sur Twitter
                                                      18 janvier 2018 à 13:45:51

                                                      Ah, donc c'est l'histoire d'appeler le setter d'une entité dans l'adder de l'autre en cas de ManyToMany, OK. J'ai cru que c'était dans le contrôleur qu'il fallait le faire.

                                                      • Partager sur Facebook
                                                      • Partager sur Twitter
                                                        18 janvier 2018 à 13:48:17

                                                        C'est ce que Romain a fait :

                                                        public function addAdvertSkill(\OC\PlatformBundle\Entity\AdvertSkill $advertSkill)
                                                        {
                                                            $this->advertSkills->add($advertSkill);
                                                         
                                                            $advertSkill->setAdvert($this);
                                                        }



                                                        • Partager sur Facebook
                                                        • Partager sur Twitter
                                                          18 janvier 2018 à 18:00:11

                                                          HarvestR : Effectivement, c'est assez clair dans la documentation que tu as fourni ! J'ai procédé par imitation par rapport à une autre relation que j'avais de fonctionnelle au sein de mon code.

                                                          Pour l'attribut nbApplications, je suis bien au courant de la manière de procéder vis-à-vis des calculs en règle générale. Cet attribut provient simplement d'un TP et il est resté dans l'état pour le moment. Cela permettait entre autre de compter la totalité des annonces non liées à une application pour un système de purge des annonces de souvenir, sans avoir à faire une jointure avec une autre table.

                                                          • Partager sur Facebook
                                                          • Partager sur Twitter
                                                            18 janvier 2018 à 18:03:29

                                                            Ah les résidus dans le code, on a tous connu ça :D

                                                            -
                                                            Edité par HarvestR 18 janvier 2018 à 18:03:45

                                                            • Partager sur Facebook
                                                            • Partager sur Twitter
                                                              22 août 2018 à 16:16:53

                                                              Bonjour, 

                                                              Je me permet de demander un peu d'aide sur ce point également, 

                                                              J'ai bien suivi vos échanges mais malheureusement cela ne résout pas mon problème. 

                                                              De mon côté, sois j'affiche une liste de compétences avec choix multiple dans mon form, mais dans ce cas rien ne persiste en base, d'un autre j'affiche une liste avec un choix unique et la j'arrive à insérer MA donnée en base

                                                              J'ai fait, défait, refait etc mon code mais je n'arrive pas à obtenir le résultat désiré. 

                                                              Si l'auteur de ce sujet est dans le coin et peut passer m'aider ou me partager l'intégralité de ses entité/forms je serais comblé ! 

                                                              D'avance merci ! 

                                                              • Partager sur Facebook
                                                              • Partager sur Twitter
                                                              Merci à OcR et à tous les contributeurs qui partagent leur passion. Vous m'apportez énormément au quotidien.

                                                              Symfony

                                                              × 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