Symfony2 - relation ManyToMany avec attribut
J'ai commencé à migrer l'un de mes projets vers Symfony2 - en fait, je le réécris from scratch car le modèle de données n'était plus très bon :)
Pour me former, j'ai suivi quelques tutos, mais ma référence reste incontestablement celui de Winzou sur le Site du Zéro, très très complet et didactique. Pour la suite de ce que j'écris, je présuppose que vous connaissez SF2 au minimum.
Entre autres entités, j'ai une entité Produit, une entité Panier et une entité Vente qui correspond à une ligne de mon panier avec un paramètre . L'entité Produit est entièrement paramétrée par le "super utilisateur" - c'est lui qui crée/édite/supprime les produits. Mon but était que, lors de la création d'un panier, tous les produits soient présents dans le panier (avec une quantité de 0 en cas de non achat)
Pour faire court, voici les attributs des relations de mes entités :
- Produit : rien puisque pas de relation :)
- Vente : 2 relations, l'une vers Produit (une ManyToOne classique) et l'autre vers Panier (une ManyToOne, mais avec l'attribut "inversedBy" égal à "ventes" - le but étant d'avoir la bidirectionnalité) J'ai ajouté aussi pour la deuxième relation un "JoinColumn(nullable=false)"
- Panier : 1 relation OneToMany vers Vente, mappée par l'attribut qui va bien dans Vente. (merci à Zazou pour cette suggestion :))
Ensuite, dans mon contrôleur PanierController.php, pour la méthode "addAction", je récupère la liste des Produits, je crée systématiquement un objet Vente que j'ajoute à mon Panier.
J'ai externalisé la définition de mes formulaires - j'ai donc un "PanierType.php" avec une méthode buildForm ayant pour paramètre $builder. Je fais
$builder->add('ventes','collection', array('type' => new VenteType()));
Il me faut donc créer VenteType.php, avec la méthode buildForm. C'est là que ça devient intéressant : mon but est que seule la quantité soit modifiée, et que le "label" correspondant soit le nom du produit.
Pour cela, j'ai suivi le Cookbook How to Dynamically Modify Forms Using Form Events et cette réponse de StackOverflow Access entity data inside FormType for a child in a collection in Symfony2 que j'ai adaptés à mes besoins.
En l'occurrence, j'ai effectivement créé un "EventListener" addNomProduitSubscriber - la seule partie qui diffère du Cookbook réside dans la méthode preSetData, après la condition if (null==$data), j'ai mis :
if ($data instanceof Vente)
{
$label = $data->getProduit()->getNom() . " :";
$form->add($this->factory->createNamed('nombre','number', null,array('label' => $label)));
}
Enfin, dans la méthode buildForm de VenteType, j'ai simplement :
$subscriber = new AddNomProduitSubscriber($builder->getFormFactory());
$builder->addEventSubscriber($subscriber);
Il ne faut pas oublier d'importer l'EventListener (use ...\addNomProduitSubscriber)
Le pire, c'est qu'apparemment il faudra que je modifie ce code lorsque Symfony 2.2 sortira - je suis sur la version 2.1.7... Ceci dit, apparemment, ça sera plus concis :)
[EDIT du 16/02/2013] Pfouu, pour persister mes entités, j'ai du procéder à quelques modifications :
pour l'entité Panier, dans l'attributs ventes, ne pas oublier d'ajouter
cascade={"persist"}
toujours pour l'entité Panier, dans le constructeur __construct, ne pas oublier d'ajouter
$this->ventes = new \Doctrine\Common\Collections\ArrayCollection();
- dans le doute (pas sûr que ça serve à rien), pour l'entité Vente, j'ai aussi ajouté "persist" dans l'attribut panier
dans le contrôleur PanierController, dans la méthode addAction(), lorsque le formulaire est validé, je "supprime" toutes les références à mes ventes AVANT de persister le panier (en faisant
$panier->removeVente($vente)
pour chaque vente), ensuite je persiste le panier, je fais un
$em->flush();
pour sauver le panier et récupérer son identifiant, et enfin, je persiste chacune des ventes - tordu mais logique puisque j'ai besoin de la référence de mon panier pour sauver chacune des ventes.