Symfony1 et les formulaires imbriqués avec Doctrine : Différence entre versions
(→Une relation many-to-one, plusieurs objets liés) |
(→Essayons et constatons les difficultés) |
||
Ligne 163 : | Ligne 163 : | ||
* Le groupe est bien sauvegardé | * Le groupe est bien sauvegardé | ||
* Le lien entre les deux est perdu | * Le lien entre les deux est perdu | ||
+ | |||
+ | |||
+ | === Une relation one-to-many, entre des "YOB" membres de la famille d'un Contact === | ||
+ | |||
+ | '''YOB''' signifie en fait ici '''Year-Of-Birth'''. C'est une table qui a évoluée de la définition d'années de naissance en une table définissant les membres d'une famille, avec leur date de naissance complète. Voilà l'explication quant au nom. | ||
+ | |||
+ | Nous allons, dans le formulaire correspondant au modèle Contact, embarquer les sous-objets YOB : | ||
+ | |||
+ | class ContactForm extends BaseContactForm | ||
+ | { | ||
+ | // ... | ||
+ | public function configure() | ||
+ | { | ||
+ | // ... | ||
+ | $this->getObject()->YOBs[] = new YOB; | ||
+ | $this->embedRelation('YOBs'); | ||
+ | |||
+ | // ... | ||
+ | parent::configure(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | D'après nos essais, ça marche à une condition : toujours ajouter un YOB au moment de la mise à jour d'un Contact. De la même manière, impossible d'en supprimer un. | ||
== Solutionnons tout cela ... == | == Solutionnons tout cela ... == |
Version du 4 juin 2013 à 10:40
Symfony, quand il est adossé à Doctrine pour l'accès aux données, permet d'accéder à une fonctionnalité très pratique lorsque l'on veut avoir un seul et même formulaire pour un objet et une ou plusieurs dépendances :
sfFormDoctrine::embedRelation()
La difficulté est que la documentation n'est pas satisfaisante à mes yeux. Voici donc un complément d'information :
Sommaire
Posons deux contextes différents
Un seul objet lié, one-to-one
Nous avons alors un schéma de ce type :
Picture: columns: name: type: string notnull: true notblank: true type: type: string(255) notnull: true notblank: true content: type: blob notnull: true width: integer height: integer Group: columns: name: type: string unique: true notblank: true notnull: true picture_id: type: integer unique: true relations Picture: foreignAlias: Groups onDelete: SET NULL onUpdate: CASCADE
Des groupes étant illustrés ou non par une image, issue d'un modèle extérieur au groupes de manière à pouvoir être réutilisé par ailleurs... pas très complexe en soit. La plus grosse particularité est que la méthode sfFormDoctrine::embedRelation() est prévue pour partir d'un objet Picture alors que là nous partirons d'un objet Group...
Une relation one-to-many, plusieurs objets liés
YOB: columns: year: type: integer notnull: true month: integer day: integer name: string(255) contact_id: integer relations: Contact: foreignAlias: YOBs onDelete: CASCADE
Nous ne précisons pas le modèle Contact car cela a peu d'intérêt.
Une relation many-to-many, relations multiples
À venir ...
Essayons et constatons les difficultés
one-to-one, avec des images et des groupes
La particularité est que nous avons un formulaire de téléchargement de fichier à partir duquel la majorité des champs seront précisés. Essayons pour voir :
// lib/form/doctrine/PictureForm.class.php class PictureForm extends BasePictureForm { // a hack for blob content which was erased on form initialization public function __construct(Picture $object = NULL) { if (!( $object instanceof Picture )) return parent::__construct(); $buf = $object->content; $r = parent::__construct($object); if ( $object->content !== $buf ) $object->content = $buf; return $r; } public function configure() { unset($this->widgetSchema['content'],$this->validatorSchema['content']); $this->widgetSchema ['content_file'] = new sfWidgetFormInputFile(); $this->validatorSchema['content_file'] = new sfValidatorFile(array( 'mime_types' => array('image/gif', 'image/jpg', 'image/png', 'image/jpeg'), )); $this->validatorSchema['type']->setOption('required',false); $this->validatorSchema['name']->setOption('required',false); } public function doSave($con = NULL) { $this->translateValues(); return parent::doSave($con); } // transforming the sfValidatedFile into Picture's properties public function translateValues() { $this->values['content'] = base64_encode(file_get_contents($this->values['content_file']->getTempName())); $this->values['name'] = $this->values['content_file']->getOriginalName(); $this->values['type'] = $this->values['content_file']->getType(); unset($this->values['content_file']); } }
Nous testons ce formulaire dans un module ./symfony doctrine:generate-admin XXX Picture et tout fonctionne très bien. Passons maintenant à sa version "embarquée" dans le sfFormDoctrine du modèle Group :
// lib/forms/doctrine/GroupForm.class.php class GroupForm extends BaseGroupForm { // ... public function doSave($con = NULL) { $picform_name = 'Picture'; $file = $this->values[$picform_name]['content_file']; unset($this->values[$picform_name]['content_file']); if (!( $file instanceof sfValidatedFile )) unset($this->embeddedForms[$picform_name]); else { // data translation $this->values[$picform_name]['content'] = base64_encode(file_get_contents($file->getTempName())); $this->values[$picform_name]['name'] = $file->getOriginalName(); $this->values[$picform_name]['type'] = $file->getType(); $this->values[$picform_name]['width'] = 24; $this->values[$picform_name]['height'] = 16; } return parent::doSave($con); } // ... public function configure() { // ... $this->embedRelation('Picture'); foreach ( array('name', 'type', 'version', 'height', 'width',) as $fieldName ) unset($this->widgetSchema['Picture'][$fieldName], $this->validatorSchema['Picture'][$fieldName]); $this->validatorSchema['Picture']['content_file']->setOption('required',false); // ... } }
Puisque tous les champs sont définis par un fichier à télécharger, nous procédons à un unset des champs inutiles. Parce qu'un Group peut avoir ou non une Picture, on précise que le fichier téléchargé n'est pas requis. La fonction doSave() surchargée permet d'utiliser, comme dans le PictureForm le fichier uploadé comme point de départ pour les données à enregistrer en base de données.
Si on essaie le formulaire en l'état on s'aperçoit :
- La Picture est bien créée
- Le groupe est bien sauvegardé
- Le lien entre les deux est perdu
Une relation one-to-many, entre des "YOB" membres de la famille d'un Contact
YOB signifie en fait ici Year-Of-Birth. C'est une table qui a évoluée de la définition d'années de naissance en une table définissant les membres d'une famille, avec leur date de naissance complète. Voilà l'explication quant au nom.
Nous allons, dans le formulaire correspondant au modèle Contact, embarquer les sous-objets YOB :
class ContactForm extends BaseContactForm { // ... public function configure() { // ... $this->getObject()->YOBs[] = new YOB; $this->embedRelation('YOBs'); // ... parent::configure(); } }
D'après nos essais, ça marche à une condition : toujours ajouter un YOB au moment de la mise à jour d'un Contact. De la même manière, impossible d'en supprimer un.
Solutionnons tout cela ...
one-to-many, avec des images et des groupes
Le problème est tout simple :
- apparemment, la méthode sfFormDoctrine::embedRelation() considère les relations dans le sens Picture → Group et non l'inverse ; autrement dit de l'objet unique vers ses multiples objets liés, disposant donc de la clé étrangère référençant l'objet initial.
- l'objet Group dont le formulaire courant est issu a une propriété picture_id qui vient écraser le lien qui est fait par le $this->embedRelation('Picture');
- nous allons donc supprimer les widget et validator picture_id du formulaire, pour laisser seule la méthode sfFormDoctrine::embedRelation() gérer ce lien
// lib/forms/doctrine/GroupForm.class.php class GroupForm extends BaseGroupForm { // ... public function configure() { // ... $this->embedRelation('Picture'); foreach ( array('name', 'type', 'version', 'height', 'width',) as $fieldName ) unset($this->widgetSchema['Picture'][$fieldName], $this->validatorSchema['Picture'][$fieldName]); $this->validatorSchema['Picture']['content_file']->setOption('required',false); unset($this->widgetSchema['picture_id'], $this->validatorSchema['picture_id']); // RETRAIT DES WIDGET ET VALIDATOR // ... } }
Et le tour est joué...