21
2010
Symfony : modèle de données et module
Maintenant que nous venons d'initialiser notre projet Symfony, nous pouvons à présent passer à la première étape de la création de notre application de gestion de tâches Toudoo, à savoir la création d'un module.
Définition du modèle de données
Pour commencer, nous allons définir notre modèle de données. Sous Symfony, bien plus qu'en développement PHP standard (comprendre : sans framework), il est très important de bien concevoir au début sa base de données. Passez donc un peu plus de temps que d'habitude pour bien réfléchir à toutes les relations entre vos différentes tables. Toute la partie rébarbative sera réalisée toute seule par ce merveilleux framework. ;)
Pour notre application de tâches, celle-ci sera relativement basique. Nous aurons donc deux tables : une table member et une table task. Avec un bout de papier et un crayon (ou, dans mon cas, avec Paint.Net), voici le modèle que nous pouvons générer.
On remarquera au passage mes talents extraordinaires de graphiste. Je me suis trompé de voie visiblement... :p
Utilisation de Doctrine
On peut donc le traduire en YAML. Il s'agit d'un langage (de type clefs/valeurs) permettant de décrire sous une forme humainement compréhensible un modèle de données. Symfony utilisera les fichiers YAML pour construire la base de données et éventuellement la remplir, comme nous allons le voir dans peu de temps.
Petite remarque importante avant de commencer : ne mettez jamais de tabulations dans vos fichiers YAML. Sinon, ceux-ci ne seront pas valides. ;)
On traduit donc notre schéma en langage YAML, en éditant le fichier /config/doctrine/schema.yml.
member:
actAs: { Timestampable: ~ }
columns:
firstName: { type: string(255), notnull: true }
lastName: { type: string(255), notnull: true }
email: { type: string(255), notnull: true, unique: true }
password: { type: string(255), notnull: true }
task:
columns:
member_id: { type: integer, notnull: true }
task: { type: string(255), notnull: true }
expires_at: { type: timestamp }
relations:
member: { onDelete: CASCADE, local: member_id, foreign: id }
La première instruction de chaque bloc sera le nom de la table correspondante. Par convention, on nommera ces tables au singulier.
Dans la table member, on remarque la première ligne, avec actAs. Il s'agit ici d'un comportement particulier. Ainsi, la table member implémentera le comportement Timestampable, qui ajoutera (et gérera) automatiquement les champs created_at et updated_at. Cela explique notamment pourquoi on ne les retrouve pas dans la description de chacune des colonnes, situées sous le label columns.
Dans celui-ci, on retrouve le type des colonnes (ici, que des chaînes de caractères), ainsi que l'interdiction pour celles-ci d'être nulles. C'est important, dans la mesure où Symfony s'en servira lors de la génération automatique des formulaires pour la validation : si l'utilisateur oublie de remplir un champ marqué notnull (donc obligatoire), alors un message d'erreur apparaîtra. Enfin, on spécifie également l'unicité de l'adresse e-mail (afin d'éviter qu'un même membre se réinscrive par mégarde) grâce à l'option unique.
On remarque également que nous n'avons pas mis l'id dans les descriptions des colonnes. Celui-ci sera automatiquement inséré.
Enfin, l'attribut relations permet de mettre en place la contrainte de clef étrangère (la flèche bleue de notre schéma). On lie donc la table task à la table member, dont la clef étrangère member_id (clef local) référencie la clef primaire id (clef foreign). Enfin, si on supprime un membre, on supprime également toutes les tâches qui lui sont associés, ce qui est indiqué par onDelete: CASCADE.
Nous pouvons donc à présent faire appel à Doctrine. Doctrine est un ORM (Object Relational Mapping). Un ORM permet de manipuler des bases de données grâce à des objets. Ainsi, dans ce cas, Doctrine créera des classes PHP pour interagir avec nos tables MySQL.
Pour ce faire, commençons par configurer les accès à notre base de données :
php symfony configure:database "mysql:host=localhost;dbname=toudoo" root root
L'ordre des arguments de configure:database est la chaîne de connexion à la base de données (ici, on se sert de MySQL pour se connecter en local à la base toudoo), le nom d'utilisateur et le mot de passe. Si tout se passe bien, vous devriez retrouver ces réglages dans le fichier /config/databases.yml :
all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: 'mysql:host=localhost;dbname=toudoo'
username: root
password: root
Une fois ceci configuré, nous pouvons demander à Doctrine de créer les tables dans notre base ainsi que les fichiers modèles qui nous permettront d'interagir facilement avec la base. Doctrine créera également les formulaires et les filtres de formulaires, sur lesquels nous reviendrons plus tard.
php symfony doctrine:build --all --no-confirmation
Si tout se passe bien, vous devriez voir une sortie semblable à :
>> doctrine Dropping "doctrine" database >> doctrine Creating "dev" environment "doctrine" database >> doctrine generating model classes >> file+ C:UsersJonathan PetitcolasAp...Temp/doctrine_schema_54820.yml >> tokens D:/Sites/toudoo/lib/model/doctrine/base/Basemember.class.php >> tokens D:/Sites/toudoo/lib/model/doctrine/base/Basemembers.class.php >> tokens D:/Sites/toudoo/lib/model/doctrine/base/Basetask.class.php >> tokens D:/Sites/toudoo/lib/model/doctrine/base/Basetasks.class.php >> autoload Resetting application autoloaders >> file- D:/Sites/toudoo/cache/frontend/.../config/config_autoload.yml.php >> doctrine generating form classes >> tokens D:/Sites/toudoo/lib/form/BaseForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/base/BasememberForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/base/BasemembersForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/base/BasetaskForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/base/BasetasksForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/BaseFormDoctrine.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/memberForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/membersForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/taskForm.class.php >> tokens D:/Sites/toudoo/lib/form/doctrine/tasksForm.class.php >> autoload Resetting application autoloaders >> file- D:/Sites/toudoo/cache/frontend/.../config/config_autoload.yml.php >> doctrine generating filter form classes >> tokens D:/Sites/toudoo/lib/filter/doct.../BasememberFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doct...BasemembersFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doct...se/BasetaskFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doct...e/BasetasksFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doct...aseFormFilterDoctrine.class.php >> tokens D:/Sites/toudoo/lib/filter/doctrine/memberFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doctrine/membersFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doctrine/taskFormFilter.class.php >> tokens D:/Sites/toudoo/lib/filter/doctrine/tasksFormFilter.class.php >> autoload Resetting application autoloaders >> file- D:/Sites/toudoo/cache/frontend/.../config/config_autoload.yml.php >> doctrine generating sql for models >> doctrine Generated SQL successfully for models >> doctrine creating tables >> doctrine created tables successfully
On peut ainsi voir que Doctrine crée les modèles dans le dossier /lib/model/doctrine, les formulaires dans /lib/form/doctrine, les filtres de formulaires dans /lib/filter/doctrine/ et les instructions SQL optimisées pour le serveur sous-jacent dans /data/sql/.
Tant que nous y sommes, nous pouvons jeter un oeil dans notre base de données, afin de s'assurer que tout y a bien été créé.
Dans le but d'avoir un aperçu rapide du fonctionnement de notre application, nous allons déjà peupler la base de données de quelques enregistrements. On utilisera encore un fichier YAML. Les données à insérer sont dans le dossier /data/fixtures/.
On crée un premier fichier pour les membres, members.yml.
member:
Jonathan:
firstName: Jonathan
lastName: Petitcolas
email: contact@jonathan-petitcolas.com
password: password
Tim:
firstName: Tim
lastName: Speek
email: tim.speek@skype.com
password: voipIsgr3aT
Puis, dans le fichier tasks.yml :
task:
world:
member: Jonathan
task: Conquer the World!
wealth:
member: Jonathan
task: Become rich and famous. Arf... I've forgotten I was already! :p
boobs:
member: Tim
task: Put words "boobs", "ass", "sex" and "pussy" to improve SEO.
On remarque que chaque enregistrement dispose d'une étiquette. Celle-ci, obligatoire (pour bien différencier chaque enregistrement), permet également de simplifier l'utilisation des clefs étrangères. Ainsi, la tâche boobs est associé à Tim.
A présent, remplissons notre base de données avec ces enregistrements grâce à la commande suivante :
php symfony doctrine:data-load
On pourra s'assurer du bon fonctionnement de celle-ci avec le retour suivant :
>> doctrine Loading data fixtures from "D:Sitestoudoodata/fixtures" >> doctrine Data was successfully loaded
Pour les plus maniaques (dont je fais partie... :$), vous pouvez supprimer le fichier fixtures.yml, qui n'est là qu'à titre d'exemple et n'a donc aucune utilité.
Si on jette un coup d'oeil sur nos tables, on s'aperçoit qu'elles sont à dorénavant remplies.
Maintenant que tout cela est fait, créons les modules correspondant à nos deux entités.
Définition d'un module
Qu'est-ce qu'un module ? Afin de mieux comprendre, reprenons depuis le début, du plus général au plus précis.
Tout d'abord, nous avons notre projet. Celui-ci est définit (pour faire simple) par une conception particulière, un but final général. Par exemple, un gestionnaire de tâches.
Puis, nous avons les applications. Les applications ne partagent entre elles que les modèles de données. Elles s'appuieront donc toutes sur les mêmes données, mais c'est tout. Rien d'autre ne leur sera commun. Ainsi, dans notre cas, nous aurons la partie frontend (qui sera visible par les utilisateurs) et la partie backend, qui elle ne sera visible que par les administrateurs. Mais nous verrons cela bien plus tard. ;)
Au niveau en-dessous des applications, on trouve les modules. Ceux-ci représentent une partie majeure de l'application. On découpe en fait en morceaux l'application, ces morceaux étant appelés modules. Ainsi, dans notre cas, on aura un module members pour gérer nos utilisateurs et un module tasks pour gérer les tâches.
Enfin, si nous voulons vraiment peaufiner, nous avons les actions. Les actions sont simplement les briques unitaires de notre module. On peut citer pour l'exemple les actions par défaut d'un module : l'ajout, la suppression, le listing ou encore l'édition.
Si ce n'est pas très clair, pas de panique. Cela viendra petit à petit avec votre utilisation de Symfony. :)
Création du module member
Nous allons commencer par le commencement, à savoir par la création du module des membres. Retournez à votre ligne de commande, et exécutez l'instruction suivante :
php symfony doctrine:generate-module --with-show --non-verbose-templates frontend members member
On génère un module comme vous pourrez le constater. Mais également quelques arguments, dont voici la signification :
- --with-show : donne une méthode de visualisation de l'entité,
- --non-verbose-templates : permet une manipulation plus orientée objet des formulaires... Nous y reviendrons plus tard.
- frontend : application dans laquelle le module devra se trouver,
- members : nom du module,
- member : nom de la table associée.
Nous avons une sortie à laquelle nous commençons à être habitués :
>> dir+ D:Sitestoudooappsfrontendmodules/membersactions >> file+ D:Sitestoudooappsfrontendm...mbersactions/actions.class.php >> dir+ D:Sitestoudooappsfrontendmodules/memberstemplates >> file+ D:Sitestoudooappsfrontendm...mberstemplates/editSuccess.php >> file+ D:Sitestoudooappsfrontendm...berstemplates/indexSuccess.php >> file+ D:Sitestoudooappsfrontendm...emberstemplates/newSuccess.php >> file+ D:Sitestoudooappsfrontendm...mberstemplates/showSuccess.php >> file+ D:Sitestoudooappsfrontendm...les/memberstemplates/_form.php >> tokens D:/Sites/toudoo/apps/frontend/m...mbers/actions/actions.class.php >> tokens D:/Sites/toudoo/apps/frontend/m...mbers/templates/editSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...bers/templates/indexSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...embers/templates/newSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...mbers/templates/showSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...les/members/templates/_form.php >> tokens D:/Sites/toudoo/apps/frontend/m...mbers/actions/actions.class.php >> tokens D:/Sites/toudoo/apps/frontend/m...mbers/templates/editSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...bers/templates/indexSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...embers/templates/newSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...mbers/templates/showSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...les/members/templates/_form.php >> file+ D:SitestoudootestfunctionalfrontendmembersActionsTest.php >> tokens D:SitestoudootestfunctionalfrontendmembersActionsTest.php >> file- D:/Sites/toudoo/cache/tmp/87952...autoMembers/templates/_form.php >> file- D:/Sites/toudoo/cache/tmp/87952...mbers/templates/showSuccess.php >> file- D:/Sites/toudoo/cache/tmp/87952...embers/templates/newSuccess.php >> file- D:/Sites/toudoo/cache/tmp/87952...bers/templates/indexSuccess.php >> file- D:/Sites/toudoo/cache/tmp/87952...mbers/templates/editSuccess.php >> dir- D:/Sites/toudoo/cache/tmp/87952...eda40aa36/autoMembers/templates >> file- D:/Sites/toudoo/cache/tmp/87952...mbers/actions/actions.class.php >> dir- D:/Sites/toudoo/cache/tmp/87952...caeda40aa36/autoMembers/actions >> dir- D:/Sites/toudoo/cache/tmp/87952...6c250993caeda40aa36/autoMembers
Essentiellement, cette ligne de commande a généré différents templates et actions dans notre application frontend, dans le dossier de modules members.
Nous avons fini pour la mise en place du module de membres. Vous pouvez à présent vous connecter à l'adresse du module, en environnement de développement (celui-ci n'est en effet pas encore accessible en production) : http://toudoo/frontend_dev.php/members.
On remarque alors que plusieurs pages ont été créées, permettant la visualisation, l'édition, l'ajout et la suppression de tous les enregistrements des membres. Magique, non ? :)
Comme on peut le constater, il n'y a pas de CSS. C'est tout à fait normal : nous verrons comment personnaliser l'affichage de notre site dans le billet suivant.

Quelque chose peut choquer : la saisie manuelle des champs created_at et updated_at. Rassurez-vous, nous verrons, lors de l'étude des formulaires Symfony, comment faire disparaître ceux-ci.
Après s'être amusé avec les membres, nous allons créer le module des tâches.
php symfony doctrine:generate-module --with-show --non-verbose-templates frontend tasks task
Ce qui donne, encore et toujours :
>> dir+ D:Sitestoudooappsfrontendmodules/tasksactions >> file+ D:Sitestoudooappsfrontendm...tasksactions/actions.class.php >> dir+ D:Sitestoudooappsfrontendmodules/taskstemplates >> file+ D:Sitestoudooappsfrontendm...taskstemplates/editSuccess.php >> file+ D:Sitestoudooappsfrontendm...askstemplates/indexSuccess.php >> file+ D:Sitestoudooappsfrontendm.../taskstemplates/newSuccess.php >> file+ D:Sitestoudooappsfrontendm...taskstemplates/showSuccess.php >> file+ D:Sitestoudooappsfrontendmodules/taskstemplates/_form.php >> tokens D:/Sites/toudoo/apps/frontend/m...tasks/actions/actions.class.php >> tokens D:/Sites/toudoo/apps/frontend/m...tasks/templates/editSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...asks/templates/indexSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m.../tasks/templates/newSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...tasks/templates/showSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/modules/tasks/templates/_form.php >> tokens D:/Sites/toudoo/apps/frontend/m...tasks/actions/actions.class.php >> tokens D:/Sites/toudoo/apps/frontend/m...tasks/templates/editSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...asks/templates/indexSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m.../tasks/templates/newSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/m...tasks/templates/showSuccess.php >> tokens D:/Sites/toudoo/apps/frontend/modules/tasks/templates/_form.php >> file+ D:SitestoudootestfunctionalfrontendtasksActionsTest.php >> tokens D:SitestoudootestfunctionalfrontendtasksActionsTest.php >> file- D:/Sites/toudoo/cache/tmp/a1c6e...9/autoTasks/templates/_form.php >> file- D:/Sites/toudoo/cache/tmp/a1c6e...Tasks/templates/showSuccess.php >> file- D:/Sites/toudoo/cache/tmp/a1c6e...oTasks/templates/newSuccess.php >> file- D:/Sites/toudoo/cache/tmp/a1c6e...asks/templates/indexSuccess.php >> file- D:/Sites/toudoo/cache/tmp/a1c6e...Tasks/templates/editSuccess.php >> dir- D:/Sites/toudoo/cache/tmp/a1c6e...2410b844369/autoTasks/templates >> file- D:/Sites/toudoo/cache/tmp/a1c6e...Tasks/actions/actions.class.php >> dir- D:/Sites/toudoo/cache/tmp/a1c6e...e82410b844369/autoTasks/actions >> dir- D:/Sites/toudoo/cache/tmp/a1c6e...4bc2964fe82410b844369/autoTasks
Comme nous pouvons le voir, Symfony nous simplifie énormément la vie. Enfin, presque. Car, il y a un petit défaut avec les membres...

D'après ce formulaire, nous devrions connaître par coeur les identifiants de tous les membres. Imaginez si notre base contient plusieurs milliers de membres... Heureusement, il est possible de modifier ce comportement par défaut (la colonne member_id étant numérique) grâce à la méthode __toString() du modèle Member.
On modifie donc comme suit la classe modèle de notre membre, /lib/model/doctrine/member.class.php :
public function __toString()
{
return $this->getFirstName().' '.$this->getLastName();
}
On récupère donc le prénom et le nom, grâce aux accesseurs de Symfony. Ce qui donnera donc, dans notre nouveau formulaire :

C'est sur cette magie que nous allons nous laisser sur ce deuxième tutorial. Au final, nous n'avons fait qu'appeler quelques commandes et écrire une ligne PHP, et nous avons une application particulièrement importante, qui aurait demandé quelques heures de développement sans un tel framework. Et je vous assure : ce n'est pas fini. :)
Dans le prochain article, nous verrons comment gérer l'aspect graphique de notre site, avec les CSS, les layouts et les slots.




Excellent tutoriel ! Très bien expliqué... Par contre j'ai une question, un peu en parallèle de ce que tu viens de montrer là.
Je ne sais pas si tu t'y connais bien dans tout ce qui concerne format de dates et heures sous symfony, mais j'ai un projet sur lequel j'ai mis en place un form contenant un sfWidgetDateTime, qui ressemble donc à ce que tes created_at et updated_at affiche. Et en validant ce formulaire , je n'arrive pas à sauvegarder la date de l'heure saisie dans ma table.
Je pense que c'est un souci de format ! J'ai pas mal bidouillé dessus... sans succès ! ^o)
@ahmed : Je ne suis pas encore expert en gestion de dates sous Symfony, mais as-tu essayé avec la fonction date_format ?
[...] La suite de cette série de tutoriaux avec le billet Symfony : modèle de données et module. [...]
Re,
Mortel ton article. Bien expliqué.
J'attends la suite avec impatience.
@+
Bonjour,
Merci pour ce tuto (et le précédent). Comme première approche c'est génial.
Une p'tite faute de frappe dans le schema.yml. Il manque un "s" à "Column" (Task)
Et le prochain article :ouiiiin:
a+
Merci @Lo2 de cette petite remarque. C'est corrigé.
Quant au prochain article Symfony, je continue la rédaction cette après-midi. Tu l'auras donc sous peu ! :)
Allez, une autre petite erreur sans conséquence: les tables créées sont au pluriel (members et tasks) dans le printscreen.
Si je dis ça c'est surtout pour savoir quand sera le prochain tuto. J'ai taté un peu le css mais je rame pour bien comprendre les rouages. Bref le démarrage est pas évident.
A bientôt !
Excellent tuto... Je suis en stage et je bosse sur Symfony... J'attends la suite ... Auriez-vous d'autres sources de documentation autour des fichier XML / bdd MySQL et Symfony ?
Merci d'avance !
Bonjour,
Merci pour cette initiative, qui est très utile pour n'importe qui voudrait prendre en main Symfony.
Cependant, j'ai un problème avec la fonction __toString(). Elle me renvoie bien ce que je veux dans le formulaire pour ajouter une tâche, mais lorsque je liste les tâches, elle me renvoie encore l'id du membre, au lieu de son nom. Y a t il une autre fonction à implémenter ? une modification à faire dans indexSuccess.php ?
En tout cas encore merci pour cette série de tuto! A bientôt!
@cayoul : Peux-tu nous poster le code de ton indexSuccess.php que nous puissions t'aider ? :)
Salut Jonathan,
J'ai suivi le tuto du site de symfony et le tien. Par contre, je pense avoir raté un truc : on fait comment dans un layout (ou ailleurs) pour regrouper plusieurs contenus (modules) ? Exemple : sur ma homepage, j'ai un bandeau avec la liste des voitures, un 2ème bandeau contenant une autre liste, etc...
Actuellement je n'arrive à avoir que mon frontend.php/voiture, frontend.php/categorie2...
Quelqu'un pourrait me filer un coup de main ?
Merci !