Symfony, les forms et les forms embarqués
Tout d’abord admirez, dans le titre, cette traduction d’embedded forms , qui est digne d’un Nelson (ou d’un ubermuda) en pleine forme.
Maintenant que c’est fait, nous allons parler ici d’un des aspects de symfony les plus intéressant, mais aussi d’un des moins bien documenté. Pas que les forms « basiques » ne soient pas documentés, mais dès que vous voulez faire autre chose qu’un form qui représente tel quel (ou presque) un objet de la base de données, ça se complique un peu.
Je vais essayer de vous faire un petit tour d’horizon de ce qu’on peut faire avec les embedded forms.
Les forms, c’est de la POO
Vous allez me dire que tout symfony est de la POO (Programmation Orientée Objet), et vous avez raison. Mais on peut utiliser symfony sans être un expert de la POO (voire sans vraiment savoir ce que c’est), en suivant Jobeet et en comprenant que executeIndex inclut le template indexSuccess.php en cas de succès. À partir de là, on est le roi du monde.
Enfin, du monde, moins les forms. Car les forms sont – à mon sens – la partie de symfony qui est à la fois utilisée par tous les utilisateurs de symfony, et à la fois très orientée objet (les factories sont par exemple beaucoup moins utilisées …). Du coup, ça mérite quelques explications.
Tout le monde n’est pas doué en POO
Certes tout le monde n’est pas doué en POO, professionnel ou pas d’ailleurs ;) À l’inverse de JAVA où les programmeurs n’ont pas vraiment le choix, la POO est récente dans PHP et certains l’ont découvert comme un cheveu sur la soupe.
L’impression que l’on a quand on essaye de changer le comportement des embed form c’est : mais où est-ce que je peux bien faire ce que j’ai envie de faire? doBind, doUpdateObject, saveEmbeddedForms, … les choix sont multiples.
Et pour cause, les forms utilisent énormément l’héritage, et disposent donc de moultes méthodes que l’on peut surcharger, sans que ce ne soit vraiment documenté autre part que dans l’API. Si on ne comprend pas les principes de base de la POO, les forms sont une vraie galère (déjà que quand on les comprend …).
Petit rappel sur la POO sur developpez.com : Consultez au moins la partie sur l’héritage pour ceux qui sont largués.
Principales étapes de gestion des formulaires
Il faut quand même que vous ayez quelque chose à l’esprit, il y a pour moi deux principales étapes quand vous souhaitez traiter un formulaire qui vient d’être soumis :
- 1 ère étape : lier les valeurs qui sont récupérées dans la requête à l’objet Form (c’est à dire les recopier en s’assurant qu’elles sont valides et nettoyées)
- 2 ème étape : Une fois les valeurs recopiés dans le $values du formulaire (qui n’est en fait qu’un tableau de valeurs nettoyées), réaliser un traitement dessus ou sur l’objet qui en découlera (nous verrons cela par la suite)
Il est toujours bon de se demander ce que l’on veut faire exactement : agir sur les valeurs qui seront recopiées et validées dans notre form, ou alors agir sur les valeurs nettoyées et recopiées, avant qu’elles ne soient sauvegardées en base.
Première étape
Lors de la première étape de liaison/validation/recopie des valeurs de la requête dans votre formulaire (votre objet Form), vous pourrez agir sur les données avant qu’elles ne soient passées dans les validateurs, ou alors une fois qu’elles sont passées dans les validateurs.
Vous pourrez faire votre bourrin : enlever des parties de votre formulaire de la requête en vous basant sur certaines valeurs soumises.
Par exemple enlever tout un formulaire embarqué, si certaines valeurs ne sont pas saisies (c’est l’exemple qu’on trouve pas mal sur le web d’ailleurs).
Petit souci : les valeurs de la requête n’auront pas été validées/nettoyées par les validateurs, vous vous exposez donc à quelques soucis (dans le style le gars qui n’aura rempli que des espaces dans un champ texte, faudra penser à faire vous même le trim avant de vérifier la valeur …).
Deuxième étape
Lors de la deuxième étape, c’est ici que vous devrez effectuer les traitements concernant votre logique métier à proprement parler. La première étape s’est occupée de vous donner accès aux valeurs dans votre formulaire (via le tableau $values), maintenant que c’est fait vous pouvez jouer avec.
Vous voulez mettre à jour un objet qui dépend de celui que vous allez sauvegarder automatiquement, vous souhaitez insérer votre objet dans un nested set, etc etc … C’est dans cette étape que vous ferez ça.
Je sépare sciemment le processus en deux étapes, après vous pouvez le voir autrement. Les étapes étant chaînées, vous pouvez les mélanger et faire un peu de business logic à la fin de l’étape 1 par exemple, mais essayez de rester constant partout dans votre code, où vous ne saurez plus où aller voir quand il y a un souci.
Étude de cas
On va prendre ici un cas tout simple, celui d’un formulaire auto-généré par doctrine à partir d’une table de votre modèle.
Appelons notre modèle PetitSuisse. On aura donc une classe nommée PetitSuisseForm.class.php dans lib/form/doctrine/
On va vite fait faire le parcours d’héritage de cette classe :
PetitSuisseForm => BasePetitSuisseForm => BaseFormDoctrine => sfFormDoctrine => sfFormObject => BaseForm => sfFormSymfony => sfForm (ouf)
Même si quelques unes de ces classes sont vides, on comprend que ça puisse devenir un peu dur de savoir où chercher. Alors oui l’abstraction c’est bon mangez en, mais ça engendre une complexité de lecture du code non négligeable.
Les classes qui sont « à vous » et donc dans lesquelles vous pourrez surcharger des méthodes sont PetitSuisseForm et BaseFormDoctrine.
La première vous permettra de surcharger une méthode relative au formulaire du modèle PetitSuisse, la deuxième vous permettra de surcharger de manière plus globale (chacun des forms auto-générés par doctrine héritant de BaseFormDoctrine). Je parle ici de symfony 1.3+, vous n’aurez pas toutes ces classes dans les versions précédentes.
On va maintenant parler des principales méthodes que vous aurez (peut être) à surcharger. Il en existe d’autres, si celles ci ne vous vont pas, lisez le code de symfony ;-)
1 ère étape (liaison)
doBind(array $values)
C’est la méthode qui va faire appel aux validateurs pour nettoyer les valeurs de la requête avant de les placer dans le tableau des valeurs. Ce sont ensuite sur ces valeurs nettoyées que nous travaillerons. Vous pouvez ici toucher aux données brut de pomme qui sortent directement du formulaire envoyé, sans traitement préalable.
2 ème étape (sauvegarde)
doSave($con = null)
C’est cette méthode qui sera appelée (par save() ) lorsque vous demandez la sauvegarde de votre formulaire. Elle se charge de mettre à jour votre objet avec les valeurs du form en appelant updateObject (qui appelera doUpdateObject que nous verrons par la suite).
Si vous souhaitez changer le processus de sauvegarde, ajouter par exemple l’appel d’une vos méthodes à chaque sauvegarde du formulaire (pourquoi pas garder trace dans un fichier de toutes les sauvegardes de vos formulaires), c’est ici que vous devez le faire.
processValues($values)
C’est ici que vous pouvez toucher les valeurs qui ont été nettoyées par les validateurs, avant qu’elles ne soient passées à la méthode updateObject (que nous verrons par la suite). Je n’ai pas de traitement particulier en tête, mais l’idée est là : modifier des valeurs avant qu’elles ne soient utilisées pour mettre votre objet à jour.
doUpdateObject($values)
C’est cette méthode que vous devrez surcharger si vous voulez réaliser une opération spéciale sur votre objet avant qu’il ne soit enregistré dans la base.
Par exemple, si vous voulez insérer cet objet à la fin d’un NestedSet, vous ferez ça ici. Le $values passé en paramètre est un tableau contenant les valeurs du formulaire, une fois qu’elles ont été nettoyées/vérifiées par les validateurs.
updateObjectEmbeddedForm($values)
Je pense qu’elle veut bien dire ce qu’elle veut dire. Elle va se charger d’appeler la fonction updateObject de chaque formulaire embarqué. Vous voulez agir sur le traitement des données par vos forms embarqués ? C’est par ici !
saveEmbeddedForms($con = null, $forms = null)
Allez la petite dernière pour la route : elle se charge de la sauvegarde de chacun des objets de vos forms embarqués. Fabien Potencier en donne un exemple de surcharge dans le livre « More with symfony »
Exemple d’utilisation
C’est bien beau de parler, mais un peu de concret ne fait pas de mal.
Vous trouverez un bon exemple pour comprendre le comportement des forms sur le blog de n1k0 dans son article Embedding Relations in Forms with Symfony 1.3 and Doctrine. Certes c’est en anglais, mais le code est universel ! Si vous avez des bons articles en français je suis preneur aussi.
Il existe maintenant la méthode embedRelation() et le plugin de Daniel Lohse ahDoctrineEasyEmbeddedRelationsPlugin qui font ça tout seul. Mais c’est toujours bien de comprendre ce que l’on fait ;-)
Conclusion
Je vous ai fait un petit tour d’horizon des principales méthodes que vous pouvez surcharger dans votre formulaire. Comme je l’ai dit plus haut, ce n’est pas une liste exhaustive, mais vous devriez avoir de quoi vous amuser.
J’ai juste essayé de voir tout cela d’une manière un peu plus globale et de ne pas proposer un n-ième exemple de code pour les formulaires embarqués.
Mais avec tout cet attirail, gardez à l’esprit que votre code d’action ne doit pas dépasser le classique Bind > Save! Ce qui est au traitement de votre formulaire, reste dans votre formulaire! Enjoy!
Crédit photo: http://www.flickr.com/photos/fromeyetopixel/2470999873/
Tags: conception, embed, form, poo, Symfony, Tutorials
15 Réponses
Laisser un message

Très intéressant !
Petite question, pourquoi tu préconises la surcharge du « doSave » et pas celle du « save » plutôt ?
Idem sur les objets, je sais jamais si c’est mieux de surcharger « save » ou « doSave », vu que doSave est en protected j’ai plus pris l’habitude de surcharger « save ».
Tout simplement parce que symfony lui même utilise doSave pour la « business » logic (la mise a jour de l’objet en l’occurrence).
Surcharger save n’a de sens que si tu veux changer le processus de sauvegarde en base (l’obtention de la connexion, la gestion des transactions …)
Ha merci beaucoup !
C’est toujours un peu flou j’avoue ces formulaires :p
Si ya un point pas clair ou que t’aimerai voir développer Sacri, hésite pas hein! On est toujours preneur d’idées et d’avis ;)
Oui, merci Vince, et effectivement c’est aussi le cas pour les objets. Quoi qu’il met à jour certains champs comme les created_at/updated_at, dans le save et pas dans le doSave.
bonjour,
j’ai un projet avec symfony , j’ai deux objet A et B avec une relation one-many ( clé de A dans B)
j’ai une pblm dans embededform dans la class A
au départ ça exécute bien , formulaire de A intégrant formulaire de B mais lors de validation du formulaire j’aurais une erreur : dans la partie formulaire de B : indiquant que id_A: required
avez vs une idée sur ce pblm
Fedora : Il va falloir que tu donnes ton code. Mais pour ça je te conseille de venir sur irc : irc.freenode.fr sur le channel #symfony-fr on se fera un plaisir de t’aider :)
[...] Symfony et les forms. J'avais oublié ce liens la semaine [...]
[...] bien trouvé quelques explications sur Amicalement Web, dans un article très bien fait, mais je n’ai pas encore tout a fait fini de [...]
[...] bien trouvé quelques explications sur Amicalement Web, dans un article très bien fait, mais je n’ai pas encore tout a fait fini de [...]
Merci pour l’article, je bosse actuellement sur un projet symfony, et j’ai un petit soucis avec mes forms, en gros, j’aimerai juste savoir comment vous aprehendrez ce problem, je vous explique : j’ai un modèle avec beaucoup de dependance , mon formulaire ne devra donc pas modifier ou mettre à jour un seul object mais 4 correspondant à 4 entités differentes de mon modèle. ou es-ce que je dois traiter cela
sachant que mon form herite directement de sfForm
et qu’il ne passe par une classe Base. c’est comme si j’avais
petitSuisseForm => sfForm (ouf ^^) (aurai-je fais une erreur en le pensant ainsi mon form) j’imagine que oui, puisque je ne dispose d’aucun moyen d’inclure une quelconque methode save() qui se chargerai de rajouter les bons objects à mon modèle.
Si vous avez une idée ou une suggestion ça m’aiderai ennormement,
Plusieurs solutions :
Si les 4 objets sont liés par des relations à un objet « parent », le form principal doit être celui de cet objet parent, et les 4 autres des embed forms.
Si, comme j’ai déjà eu le cas, les 4 forms sont indépendants mais doivent être sauvés en même temps, vous pouvez utiliser mon plugin : http://github.com/vjousse/doDoctrineCollectionFormPlugin
Il va permettre de disposer des méthodes save et embedForm (notamment) dans le form principal, même si il n’est pas lié à un objet.
Ici un article sur les embedded forms, dispo en français et anglais:
http://tech.cibul.org/fr/formulaires-integres-avec-symfony-1-4-et-jquery/
hey everyone
i used embed form in my project and it works fine for add new object ( the validation also works fine) but for updation i have a problem : the validation is not applied and throw an exception , but if i update the object as it is it works , the problem comes only if an object didn’t match the validation.
any idea ?
J’ai un petit soucis au niveau des embedForms.
J’ai un formulaire ‘demande’ avec un formulaire embarqué ‘contact’. Et j’aimerai que si le contact existe, il ne reprend que l’id et update l’ancien au lieu d’en rajouter…
J’ai tous tenté, mais je ne sais pas c’est à quel niveau que je dois vérifier l’existence, et comment faire l’update…
Merci d’avance ! :)