Bonjour,

J'ai un formulaire "AdvertType" qui fait appel à un sous formulaire "VehicleType" qui lui-même contient un champ de type "EntityType" que je voudrais scinder en deux groupes de cases à cocher dans mon template.

Voici le code probant dans "AdvertType" :

...
class AdvertType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
...
            ->add('vehicle', VehicleType::class)
...

Celui dans "VehicleType" :

...
class VehicleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
...
            ->add('equipments', EntityType::class, array('class' => 'App\Entity\Equipment', 'multiple' => true, 'expanded' => true, 'choice_label' => 'equipment',))
...

Et voici ce que je fais dans le template twig pour tenter d'afficher les équipements en fonction de leur appartenance (catégorie) :

..
        {{ form_errors(formAdvert.vehicle.equipments) }}
        {{ form_label(formAdvert.vehicle.equipments, 'Equipement du porteur', {'label_attr': {'class': 'foo'}}) }}
        {% for equipment in formAdvert.vehicle.equipments if equipment.belonging == 'Porteur' %}

            {{ form_row ( equipment ) }}

        {% endfor %}

        {{ form_errors(formAdvert.vehicle.equipments) }}
        {{ form_label(formAdvert.vehicle.equipments, 'Equipement de la cellule', {'label_attr': {'class': 'foo'}}) }}
        {% for equipment in formAdvert.vehicle.equipments if equipment.belonging == 'Cellule' %}

            {{ form_row ( equipment ) }}

        {% endfor %}
...

Lors de la génération du template, j'obtiens l'erreur :

"Neither the property "belonging" nor one of the methods "belonging()", "getbelonging()"/"isbelonging()"/"hasbelonging()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView"."

Je suppose que ça provient de ma condition if qui accède à la valeur d'un attribut d'un objet embarqué dans un formulaire lui-même embarqué.

Quelqu'un aurait une idée de la manière de procéder pour réaliser ce que je voudrais?

Merci d'avance pour votre aide.

14 réponses


Digivia
Réponse acceptée

En fait tu n'as pas 2 entités pour la même donnée...
Tu as une entité Vehicle avec ces 2 champs (+ les autres) : equipmentCellule et equipmentPorteur. Ces deux champs dans ton form, sont des entityType qui vont requeter la même entité equipment avec un discriminant (belonging). Ils sont une relation OneToMany vers equipment. Je ne vois pas ce qui n'est pas propre?

Salut,

C'est dans ton form que tu vas sélectionner les valeurs à afficher, via l'option query_builder de ton EntityType. Et si tu veux 2 champs, il faut builder deux champs dans ton FormType. Rien à faire de spécial dans la vue (surtout pas de foreach sur un formView), un form_row suffit.

dubitoph
Auteur

Merci beaucoup pour ton intrevention!

Effectivement, c'était mon idée première. J'avais d'aileurs fait ceci dans mon builder :

...
class VehicleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $belongingCarrier = 'Porteur';
        $belongingCell = 'Cellule';
...
            ->add('equipments', EntityType::class, array('class' => 'App\Entity\Equipment', 'multiple' => true, 'expanded' => true, 'choice_label' => 'equipment',
                  'query_builder' => function(EquipmentRepository $er) use ($belongingCarrier) {
                                        return $er->queryFindByBelonging($belongingCarrier);
                                     }            
                                                                )
                 )
            ->add('equipments', EntityType::class, array('class' => 'App\Entity\Equipment', 'multiple' => true, 'expanded' => true, 'choice_label' => 'equipment',
                  'query_builder' => function(EquipmentRepository $er) use ($belongingCell) {
                                        return $er->queryFindByBelonging($belongingCell);
                                     }            
                                                                )
                 )
 ...

Cependant, étant donné que je ne peux pas avoir deux champs du même nom ('equipments'), et que je ne peux pas nommer un des deux d'une autre façon sinon j'obtiens une erreur commme quoi il n'existe pas , je n'ai pas pu aboutir via cette voie non plus :-(.

Y aurait-il un moyen pour que je puisse faire coexister ces deux champs dans le même builder?

Effectivement, il te faut 1 champ dans ton entité par champ dans ton Form Builder.
Si tu veux pouvoir l'intégrer plusieurs fois, il te faut créer ce champ dans un Form type et ajouter ce FormType dans un champ CollectionType...
Peux-tu nous en dire plus sur l'objectif final de ton form? J'ai du mal à saisir la finalité...

dubitoph
Auteur

Encore merci pour ton aide : je patauge depuis plusieurs jours avec ce problème...

Je voudrais réaliser un formulaire d'insertion d'annonces de véhicules qui contiennent différents équipements liés soit au porteur, soit à la cellule. L'utilisateur, en ecodant son annonce, devrait pouvoir cocher les différents équipements (options) du véhicule. Pour ce faire et à des fins de clareté, je voudrais lui présenter les options en deux groupes distincts : celui liée aux options du porteur et celui lié aux options de la cellule.

Dans mon entité, j'ai un attribut "belonging" qui permet de faire la distinction entre l'appartenance au porteur ou l'appartenance à la cellule.

dubitoph
Auteur

Si je fais un second formulaire et que j'y fais appel dans mon premier, jle problème reste le même : dans mon formulaire, j'aurai encore deux champs portant le même nom : un champ "equipments" de type EntityType et un champ "equipments" de type FormType...

Si tu fais un second formulaire (avec ton entityType), tu l'intègres au premier via un champ collectionType. Ca devrait être possible de l'intégrer 2 fois. Cela veut dire que tu auras une relation ManyToOne ou ManyToMany entre Equipment et Vehicle. Mais cela doit déja être le cas, puisque je vois que ton EntityType est configuré avec l'option multiple à true.
Après, je trouve pas cette solution très élégante... Et pas simple à maintenir ensuite.
Solution possible :
Dans ton entité, tu créé 2 propriétés, une pour le porteur, une pour la cellule, et un troisième pour agréger les 2 (enfin si tu en as réellement besoin). Ensuite tu utilise les Event Doctrine (PrePersist et PreUpdate) pour mettre à jour ta 3ème propriété avec les valeurs des 2 premières.

Je ne trouve pas choquant d'avoir 2 propriété dans ton entité pour les équipements, puisque ce sont véritablement des données différentes et qu'elles ont un discriminant (porteur ou cellule).

dubitoph
Auteur

De fait, je ne vois pas comment faire autrement, mais je n'aime pas du tout ça car ce n'est pour moi pas propre car, avoir deux tables pour un même type de données n'est pas des plus séduisants. Ca fait 2 entités à maintenir, deux repositories, deux formulaires d'encodage, etc...

dubitoph
Auteur

N'y aurait-il pas la possibilité de travailler avec un champ non mappé?

J'ai effectué ceci dans mon builder :

            ->add('equipments', EntityType::class, array('class' => 'App\Entity\Equipment', 'multiple' => true, 'expanded' => true, 'choice_label' => 'equipment',
                  'query_builder' => function(EquipmentRepository $er) use ($belongingCarrier) {
                                        return $er->queryFindByBelonging($belongingCarrier);
                                     }            
                                                                )
                 )            
            ->add('equipmentsCell', EntityType::class, array('class' => 'App\Entity\Equipment', 'multiple' => true, 'expanded' => true, 'choice_label' => 'equipment',
                  'mapped' => false,  
                  'query_builder' => function(EquipmentRepository $er) use ($belongingCell) {
                                        return $er->queryFindByBelonging($belongingCell);
                                     }            
                                                                )
                 )

Et ceci dans mon template :

        {{ form_errors(formAdvert.vehicle.equipments) }}
        {{ form_label(formAdvert.vehicle.equipments, 'Equipement du porteur', {'label_attr': {'class': 'foo'}}) }}
        {{ form_widget(formAdvert.vehicle.equipments) }}

        {{ form_errors(formAdvert.vehicle.equipmentsCell) }}
        {{ form_label(formAdvert.vehicle.equipmentsCell, 'Equipement de la cellule', {'label_attr': {'class': 'foo'}}) }}
        {{ form_widget(formAdvert.vehicle.equipmentsCell) }}

Tout s'affiche correctement, comme voulu. Cependant, dans mon controller, je ne parviens pas à accéder aux données de "equipmentsCell". Lorsque je fais ceci :

        if ($form->isSubmitted() && $form->isValid()) 
        {
            $equipmentsCell = $request->request->get("equipmentsCell");
            var_dump($equipmentsCell);

J'obtiens l'affichage de null.

dubitoph
Auteur

Ok, oui, je commence à comprendre... De fait, ça me paraît pas mal...

Et si tu veux te faciliter la vie, tu as un super bundle pour administrer ton entité equipment : easyadminbundle
Toute la config se fait via de la config en fichier yaml, c'est top.

dubitoph
Auteur

J'ai fait comme tu 'avais précédemment conseillé, à savoir que j'ai créé une propriété carrierEquipment et un autre cellEquipment dans mon entité Vehicle. Toutes deux sont une collection d'entités Equipment qui elle à toujours sont attribut belonging.

Dans mon builder, j'ai mes deux attributs qui sont liés chacun à une requêtes qui l'une, ramène que les équipements liés au porteur et l'autre uniquement ceux liés à la cellule.

Dans mon template, je peux alors afficher chacun des contenus séparément.

Merci beaucoup pour tes réflexions qui m'ont permis de m'en sortir!

Cool! Content d'avoir pu t'aider ;)