Symfony: Relation many-to-many sur la meme table avec Propel
Beaucoup de boulot pour cette courte semaine (je suis en week end, ce soir :D) du coup, je n’ai pas eu le temps de faire le billet Symfony du mardi! Ca tombait bien en même temps, car je n’avais pas énormément d’idées.
Finalement, j’ai trouvé un petit quelque chose pour subvenir à vos besoins de nouveautés permanents. On va parler d’une petite faiblesse dans Propel, que je n’ai pas encore pris le temps d’analyser dans toute sa profondeur mais qui reste assez gênant: La gestion des relations many-to-many sur une même table.
En gros, une table d’association dont le couple serait deux clé étrangères sur la même table du genre:
cross_selling:
_attributes: { phpName: CrossSelling }
pid: { type: SMALLINT, size: '5', primaryKey: true, required: true, foreignTable: produits, foreignReference: pid, onDelete: CASCADE, onUpdate: CASCADE }
csid: { type: SMALLINT, size: '5', primaryKey: true, required: true, foreignTable: produits, foreignReference: pid, onDelete: CASCADE, onUpdate: CASCADE }Autant dans la partie model, aucun souci, on est capable de récupérer chacun des éléments par la table « Produits » (la table où pointe les 2 clés étrangères pour ceux qui se sont perdus en route), autant dans la partie form, aucune trace du widget pour matérialiser cette relation, rien, nada. Alors qu’au départ, si on définie notre relation, c’est qu’on en a un peu besoin en fait.
On va donc tâcher de combler ce vide, en rajoutant ce dont a besoin notre formulaire pour reproduire notre relation dans le fichier lib/form/ProduitsForm.class.php
class ProduitsForm extends BaseProduitsForm { public function configure() { parent::configure(); // Ajout Cross Selling $this->widgetSchema['cross_selling_list'] = new sfWidgetFormPropelChoiceMany(array( 'model' => 'Produits' )); $this->validatorSchema['cross_selling_list'] = new sfValidatorPropelChoiceMany(array('model' => 'Produits', 'required' => false)); }
On crée ensuite la méthode qui va permettre d’enregistrer le résultat de notre choix dans notre widget de liste (en fait, il suffit d’adapter le code généré pour une relation many-to-many qui fonctionne):
public function saveCrossSellingList($con = null) { if (!$this->isValid()) { throw $this->getErrorSchema(); } if (!isset($this->widgetSchema['cross_selling_list'])) { // somebody has unset this widget return; } if (is_null($con)) { $con = $this->getConnection(); } $c = new Criteria(); $c->add(CrossSellingPeer::PID, $this->object->getPrimaryKey()); CrossSellingPeer::doDelete($c, $con); $values = $this->getValue('cross_selling_list'); if (is_array($values)) { foreach ($values as $value) { $obj = new CrossSelling(); $obj->setPid($this->object->getPrimaryKey()); $obj->setCsid($value); $obj->save(); } } }
Puis on surcharge le doSave pour ajouter cette fonction:
protected function doSave($con = null) { parent::doSave($con); $this->saveCrossSellingList($con); }
et pour finir on surcharge la méthode updateDefaultsFromObject pour charger le résultat une fois sauvegardé:
public function updateDefaultsFromObject() { parent::updateDefaultsFromObject(); if (isset($this->widgetSchema['cross_selling_list'])) { $values = array(); foreach ($this->object->getCrossSellingsRelatedByPid() as $obj) { $values[] = $obj->getCsid(); } $this->setDefault('cross_selling_list', $values); } }
Et voilà, Propel n’a qu’a bien se tenir, on peut se passer de lui! Je pense qu’avec un peu plus de temps, je me plongerai bien dans le détail pour voir où ça coince, à moins que j’ai loupé encore une fois un épisode.
Moralité, Doctrine vaincra?
Tags: form, mysql, propel, Symfony
