Formulaire sur relation n:m avec clé primaire composite

Le problème exposé dans ce sujet a été résolu.

Hello,

J’ai un petit souci de compréhension des Forms de Symfony je pense :

Le code actuel côté Entity (pas de problème de ce côté là) :

J’ai une relation OneToMany / ManyToOne : Entity User ==> Entity UsersIndicators <== Entity Indicator. (Note : J’ai une clé primaire composite sur UsersIndicators sur les propriétés $user et $indicator). L’Entity UsersIndicators possède des champs supplémentaires comme $color et $displayed.

Mon problème côté formulaire

J’ai besoin d’avoir un formulaire, qui, pour un utilisateur donné, avoir une liste de ses indicateurs (propriété $userIndicators de l’entity User qui mappe vers UsersIndicators). A côté de ses indicateur, pour chacun d’entres eux, une checkbox (boolean) pour savoir si oui ou non il veut l’afficher (propriété $displayed de l’entité UsersIndicators), ainsi qu’un selecteur de couleur (string) avec la propriété $color de toujours la même entité UsersIndicators. A savoir que je ne veux pas qu’il puisse ajouter ou supprimer des indicateurs mais seulement jouer avec les propriétés qu’il y a en plus dans l’entité à savoir $color et $displayed.

Je m’y prend surement très mal mais j’ai essayé de créer un formType de UsersIndicators, pour l’instant très simple, certe :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

namespace App\Form;

use App\Entity\UsersIndicators;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserCockpitParametersType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('user') // Clé primaire, on est d'accord que je peux la virer du formulaire ?
            ->add('indicator') // Clé primaire, on est d'accord que je peux la virer du formulaire ?
            ->add('color')
            ->add('displayed')
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => UsersIndicators::class,
        ]);
    }
}

`

mais dans mon Controller, lorsque je fais ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

public function indexAction()
{
   [...]
   $user = $userManager->getUserForPerformanceMilestone($id);

   $cockpitParametersForm = $this->createForm(UserCockpitParametersForm::class, $user->getUserIndicators());
   [...]
}

`

J’ai le droit à cette exception :

The form’s view data is expected to be an instance of class App\Entity\UsersIndicators, but is an instance of class Doctrine\ORM\PersistentCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\ORM\PersistentCollection to an instance of App\Entity\UsersIndicators.

Symfony me dit que je devrais opter pour un ViewTransformer mais j’avoue être un peu perdu… Des pistes à me donner ?

Je vous remercie :)

+0 -0

Salut !

Si j’ai bien compris, UsersIndicators possède une clé composite qui reprend les clés primaires des deux autres entités ? Ce n’était officiellement supporté qu’avec doctrine 2.1 donc Symfony 2.0, l’alternative officielle étant d’avoir un champ unique comme clé primaire.

Ensuite, pour faire ce que tu souhaites, je vois deux solutions :

  1. Tu utilises un champ non-mappé EntityType (choix multiple) dans ton formulaire afin de permettre de choisir les indicateurs, et dans le contrôleur ou dans l’entité User elle-même tu boucles sur les indicateurs choisis et créés le UsersIndicators correspondant ;
  2. Tu prévois un champ de type collection de UsersIndicators dans ton UserType, et le champ Indicator de cet UsersIndicatorsType est un EntityType (choix unique).

Petite note au passage : les entités, on les nomme plus facilement au singulier, cela évite de se mélanger les pinceaux avec les propriétés en relations *ToMany.

+0 -0

Salut Ymox et merci de ta réponse !

Ah je suis étonné concernant les clés composites. Côté BDD je trouve ça pratique et saint, côté Object ça me semblait assez logique : le principe de composition en Objet notamment.

Tu as une idée de pourquoi ils ont arrêtés de supporter les clés composites ? :o

Il me semblait que pour les relations n:m on avait une exception et qu’on mettait celle-ci au pluriel ! User (singulier) => UsersIndicators (pluriel) <= Indicator (singulier). Si il est conseillé de mettre aussi celle-ci au singulier je corrigerais ça alors :p


Pour tes solutions je suis pas sûr d’avoir tout saisi mais je vais creuser ça je reviens si j’ai loupé un détail :)

Merci encore Ymox !

Tu as une idée de pourquoi ils ont arrêtés de supporter les clés composites ? :o

Oui, pour des raisons de performance si je me souviens bien.

Il me semblait que pour les relations n:m on avait une exception et qu’on mettait celle-ci au pluriel ! User (singulier) => UsersIndicators (pluriel) <= Indicator (singulier). Si il est conseillé de mettre aussi celle-ci au singulier je corrigerais ça alors :p

Non, tu peux les nommer comme tu le souhaites, mais si tu mets au pluriel des propriétés qui contiennent un simple objet, ça risque de te faire faire de la gymnastique de cerveau qui, à mon avis, peut être évitée.
Aussi, ne mélange pas les entités (où chacune d’entre elles représente une instance précise, donc singulier) et les propriétés dans les relations (qui, elles, "s’accordent en nombre" selon la seconde partie du type de relation).

+0 -0

Hmm..

En faite tu viens de m’éclaircir un point. L’entité UsersIndicators je devrais la mettre aussi au singulier car elle représente une instance précise qui est constitué d’un user et d’un indicator, tu as tout as fait raison :o

Pour revenir au formulaire, j’avoue être un peu pommé. J’te montre juste ce que j’avais fais avant que tu me répondes pour la première fois :

J’ai un formulaire pour l’entité UsersIndicators :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
[...]
class UsersIndicatorsFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('color')
            ->add('displayed')
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => UsersIndicators::class,
        ]);
    }
}

Un autre formulaire pour l’entité User:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
[...]
class EditUserIndicatorsFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('userIndicators', UsersIndicatorsFormType::class);
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}

`

Sauf que du coup, dans mon controller, quand je fais mon $form = $this->createForm(EditUserIndicatorsFormType::class, $user);

J’ai la fameuse exception de symfony

"The form’s view data is expected to be an instance of class App\Entity\UsersIndicators, but is an instance of class Doctrine\ORM\PersistentCollection. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class Doctrine\ORM\PersistentCollection to an instance of App\Entity\UsersIndicators."

Ce que je comprends parfaitement car dans ma variable $user qui contient l’objet User, $userIndicators représente un ArrayCollection de UsersIndicators (pas encore corrigé le pluriel :p ).

Quand je reviens vers ta première réponse

Tu utilises un champ non-mappé EntityType (choix multiple) dans ton formulaire afin de permettre de choisir les indicateurs, et dans le contrôleur ou dans l’entité User elle-même tu boucles sur les indicateurs choisis et créés le UsersIndicators correspondant ;

J’avoue ne pas trop saisir :/

Pour ton premier souci, c’est simplement parce qu’il te faut une collection de UsersIndicatorsFormType, pas un seul. Ta relation, c’est une One(User)ToMany(UsersIndicators), donc ce champ de formulaire doit soit être un CollectionType, soit un EntityType avec 'multiple' => true. Ce sont les deux seuls types natifs qui retournent une liste d’objets.

Quant à ta seconde "question" : vu que tu n’as pas de liaison directe vers Indicator dans ton entité User (aucun mapping direct), mais que tu veux pouvoir choisir parmi ces entités Indicator, le champ qui les propose est forcément un EntityType, mais ne peut pas être lié à ton entité, il faut "désolidariser le champ de l’entité sous-jacente. Cela évitera que Symfony tente d’enregistrer ces choix (une liste d’entités Indicator) dans un objet User. Mais du coup, c’est à toi de reprendre cette liste et d’en faire les UsersIndicators nécessaires.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte