![]() |
|
Practical symfonyJour 3 : Le modèle de données |
|
You are currently reading "Practical symfony" which is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.

|
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. |
Ceux d'entre vous qui brûlent d'ouvrir leur éditeur de texte et de définir un peu de PHP seront heureux d'apprendre que le tutoriel d'aujourd'hui va nous entraîner dans un certain développement. Nous allons définir le modèle de données de Jobeet, utiliser un ORM pour interagir avec la base de données, et construire le premier module de l'application. Mais, comme symfony fait beaucoup de travail pour nous, nous aurons un module web pleinement opérationnel sans trop écrire de code PHP.
Les histoires d'utilisateurs que nous avons écrites hier, décrivent les objets principaux de notre projet : les emplois, les sociétés affiliées et les catégories. Voici le schéma correspondant aux relations entre entités :

En plus des colonnes décrites dans les histoires, nous avons également ajouté un
champ created_at à certaines tables. Symfony reconnaît de tels champs et définit la valeur
avec celle de l'heure actuelle du système quand un enregistrement est créé. C'est la même chose pour
les champs updated_at : leur valeur est définie avec celle de l'heure du système quand l'enregistrement
est mis à jour.
Pour stocker les emplois, les sociétés affiliées et les catégories, il nous faut évidemment une base de données relationnelle.
Mais comme Symfony est un framework orienté-objet, on aime manipuler des objets quand nous le pouvons. Par exemple, au lieu d'écrire des instructions SQL pour récupérer des enregistrements de la base de données, nous préférons utiliser des objets.
Les informations de base de données relationnelle doivent être mappées sur un modèle objet. Cela peut être fait avec un outil ORM et heureusement, symfony est livré avec deux d'entre eux : Propel et Doctrine. Dans ce tutoriel, nous allons utiliser Doctrine.
L'ORM a besoin d'une description des tables et leurs relations pour créer les classes liées. Il y a deux façons de créer ce schéma de description : par introspection d'une base de données existante ou en la créant à la main.
Comme la base de données n'existe pas encore et que nous voulons garder agnostique
la base de données Jobeet, nous allons créer le fichier du schéma à la main en éditant le fichier vide
config/doctrine/schema.yml :
# config/doctrine/schema.yml
JobeetCategory:
actAs: { Timestampable: ~ }
columns:
name: { type: string(255), notnull: true, unique: true }
JobeetJob:
actAs: { Timestampable: ~ }
columns:
category_id: { type: integer, notnull: true }
type: { type: string(255) }
company: { type: string(255), notnull: true }
logo: { type: string(255) }
url: { type: string(255) }
position: { type: string(255), notnull: true }
location: { type: string(255), notnull: true }
description: { type: string(4000), notnull: true }
how_to_apply: { type: string(4000), notnull: true }
token: { type: string(255), notnull: true, unique: true }
is_public: { type: boolean, notnull: true, default: 1 }
is_activated: { type: boolean, notnull: true, default: 0 }
email: { type: string(255), notnull: true }
expires_at: { type: timestamp, notnull: true }
relations:
JobeetCategory: { onDelete: CASCADE, local: category_id, foreign: id, foreignAlias: JobeetJobs }
JobeetAffiliate:
actAs: { Timestampable: ~ }
columns:
url: { type: string(255), notnull: true }
email: { type: string(255), notnull: true, unique: true }
token: { type: string(255), notnull: true }
is_active: { type: boolean, notnull: true, default: 0 }
relations:
JobeetCategories:
class: JobeetCategory
refClass: JobeetCategoryAffiliate
local: affiliate_id
foreign: category_id
foreignAlias: JobeetAffiliates
JobeetCategoryAffiliate:
columns:
category_id: { type: integer, primary: true }
affiliate_id: { type: integer, primary: true }
relations:
JobeetCategory: { onDelete: CASCADE, local: category_id, foreign: id }
JobeetAffiliate: { onDelete: CASCADE, local: affiliate_id, foreign: id }
Si vous avez décidé de créer les tables en écrivant des instructions SQL, vous pouvez générer le fichier de configuration correspondant
schema.ymlen exécutant la tâchedoctrine:build-schema:$ php symfony doctrine:build-schemaLa tâche ci-dessus suppose que vous ayez une base de données configurées dans
databases.yml. Nous vous montrerons comment configurer la base de données dans une étape suivante. Si vous essayez et exécutez cette tâche maintenant, elle ne fonctionnera pas car elle ne connait pas la base de données pour construire le schéma.
Le schéma est la traduction directe du diagramme de relations des entités dans le format YAML.
Le format YAML
Selon le site officiel YAML, YAML est "un standard de sérialisation de données compréhensibles par un humain, quel que soit le langage de programmation".
Autrement dit, YAML est un langage simple pour décrire les données (des chaînes, des entiers, des dates, des tableaux et des séries).
En YAML, la structure est présentée à travers l'indentation, la séquence des éléments sont indiqués par un tiret, et les paires clé/valeur dans le fichier sont séparés par deux points. YAML possède également une syntaxe abrégée pour décrire la même structure avec moins de lignes, où les tableaux sont explicitement indiqués avec
[]et les séries avec{}.Si vous n'êtes pas encore familiarisé avec YAML, il est temps de commencer car le framework symfony l'utilise intensivement pour ses fichiers de configuration. Un bon point de départ est la partie de la documentation de symfony pour YAML.
Il y a une chose importante que vous devez retenir lors de l'édition d'un fichier YAML : l'indentation doit être faite avec un ou plusieurs espaces, mais jamais avec les tabulations.
Le fichier schema.yml contient la description de toutes les tables et leurs colonnes.
Chaque colonne est décrite avec les informations suivantes :
type: Le type de colonne (boolean, integer, float, decimal,
string, array, object, blob, clob, timestamp,
time, date, enum, gzip)notnull: Réglez-le à true si vous souhaitez que la colonne soit requiseunique: Réglez-le à true si vous souhaitez créer un index unique pour la colonne.L'attribut
onDeletedéfinit le comportementON DELETEdes clés étrangères, et Doctrine supporteCASCADE,SETNULL, etRESTRICT. Par exemple, quand un enregistrementjobest supprimé, tous les enregistrements connexesjobeet_category_affiliateseront automatiquement supprimés par la base de données.
Le framework symfony supporte toutes les PDO-supporté par les bases de données (MySQL, PostgreSQL, SQLite, Oracle, MSSQL, ...). PDO est la couche d'abstraction de la base de données intégrée à PHP.
Utilisons MySQL pour ce tutoriel:
$ mysqladmin -uroot -p create jobeet
Enter password: mYsEcret ## The password will echo as ********
N'hésitez pas à choisir un autre moteur de base de données si vous le voulez. Il ne sera pas difficile d'adapter le code que nous allons écrire car nous allons utiliser l'ORM qui va écrire le code SQL pour nous.
Nous devons dire à symfony d'utiliser cette base de données pour le projet Jobeet :
$ php symfony configure:database "mysql:host=localhost;dbname=jobeet" root mYsEcret
La tâche configure:database prend trois arguments: le
PDO DSN, le nom d'utilisateur et
le mot de passe pour accéder à la base de données. Si vous n'avez pas besoin d'un mot de passe pour accéder
à votre base de données sur le serveur de développement, omettez simplement le troisième argument.
La tâche
configure:databasestocke la configuration de la base de données dans le fichier de configuration deconfig/databases.yml. Au lieu d'utiliser la tâche, vous pouvez éditer ce fichier à la main.
En passant le mot de passe base de données sur la ligne de commande est pratique mais peu sûr. En fonction de qui a accès à votre environnement, il pourrait être préférable de modifier le fichier
config/databases.ymlpour changer le mot de passe. Bien sûr, pour garder le mot de passe sûr, le mode d'accès du fichier de configuration devraient également être limités.
Grâce à la description de base de données à partir du fichier schema.yml, nous pouvons utiliser
les tâches intégrées Doctrine qui génèrent les instructions SQL nécessaires pour créer les
tables de la base de données :
D'abord afin de générer le code SQL, vous devez construire vos modèles à partir de vos fichiers du schéma.
$ php symfony doctrine:build --model
Maintenant que vos modèles sont présents, vous pouvez générer et insérer le code SQL.
$ php symfony doctrine:build --sql
La tâche doctrine:build --sql génère les instructions SQL dans le répertoire
data/sql/, optimisées pour le moteur de la base de données que nous avons configuré :
# snippet from data/sql/schema.sql CREATE TABLE jobeet_category (id BIGINT AUTO_INCREMENT, name VARCHAR(255) NOT NULL COMMENT 'test', created_at DATETIME, updated_at DATETIME, slug VARCHAR(255), UNIQUE INDEX sluggable_idx (slug), PRIMARY KEY(id)) ENGINE = INNODB;
Pour créer les tables dans la base de données, vous devez exécuter la
tâche doctrine:insert-sql :
$ php symfony doctrine:insert-sql
Comme pour tout outil en ligne de commande, les tâches de symfony peuvent prendre des arguments et des options. Chaque tâche est livrée avec un message d'aide intégré qui peut être affiché en exécutant la tâche
help:$ php symfony help doctrine:insert-sqlLe message d'aide liste tous les arguments et les options possibles, il donne les valeurs par défaut pour chacune d'elles, et fournit quelques exemples d'utilisation utile.
L'ORM génère également des classes PHP qui met les enregistrements de la table en objets :
$ php symfony doctrine:build --model
La tâche doctrine:build --model crée des fichiers PHP dans le répertoire lib/model/
qui peut être utilisé pour interagir avec la base de données.
En naviguant dans les fichiers générés, vous avez probablement remarqué que Doctrine
génère trois classes par table. Pour la table jobeet_job :
JobeetJob: Un objet de cette classe représente un seul enregistrement de la
table de jobeet_job. La classe est vide par défaut.BaseJobeetJob: La classe parent de JobeetJob. Chaque fois que vous exécutez
doctrine:build --model, cette classe est écrasée, ainsi toutes les
personnalisations doivent être faites dans la
classe JobeetJob.
JobeetJobTable: La classe définit des méthodes qui renvoient surtout des
collections d'objets de JobeetJob. La classe est vide par
défaut.
Les valeurs des colonnes d'un enregistrement peuvent être manipulées avec un objet modèle en utilisant des accesseurs (des méthodes get()) et des mutateurs (des méthodes set()) :
$job = new JobeetJob(); $job->setPosition('Web developer'); $job->save(); echo $job->getPosition(); $job->delete();
Vous pouvez également définir des clés étrangères en reliant directement les objets entre eux :
$category = new JobeetCategory(); $category->setName('Programming'); $job = new JobeetJob(); $job->setCategory($category);
La tâche doctrine:build --all est un raccourci des tâches que nous avons exécuté dans cette
section et plus encore. Aussi, exécutez cette tâche maintenant pour génèrer les formulaires et les validateurs
pour les classes modèle de Jobeet :
$ php symfony doctrine:build --all --no-confirmation
Vous pourrez voir les validateurs à l'oeuvre à la fin de la journée et les formulaires seront bien expliquées en détail le jour 10.
Les tables ont été créées dans la base de données mais elles n'ont pas de données. Pour toute application web, il existe trois types de données :
Les données initiales : Les données initiales sont nécessaires à l'application pour travailler. Par exemple, Jobeet a besoin de quelques catégories initiales. Sinon, personne ne sera en mesure de soumettre un emploi. Nous avons également besoin d'un utilisateur administrateur pour être capable de se connecter au backend.
Les données de test : Les données de test sont nécessaires pour l'application à tester. En tant que développeur, vous devrez écrire des tests pour s'assurer que Jobeet se comporte comme décrit dans les histoires d'utilisateur, et la meilleure façon est d'écrire des tests automatisés. Donc, chaque fois que vous exécutez vos tests, vous avez besoin d'une base de données propre avec quelques nouvelles données pour tester tout de suite.
Les données utilisateurs : Les données utilisateurs sont créés par des utilisateurs pendant la durée de vie normale de l'application.
Chaque fois que symfony crée les tables dans la base de données, toutes les données sont perdues.
Pour peupler la base de données avec des données initiales, nous pourrions créer un script PHP,
ou exécuter des instructions SQL avec le programme mysql. Mais comme le besoin est
assez fréquent, il y a une meilleure façon avec symfony : créer des fichiers YAML dans le
répertoire data/fixtures/ et utiliser la tâche doctrine:data-load pour les charger
dans la base de données.
Premièrement, créer les fichiers de jeu de test suivants :
# data/fixtures/categories.yml
JobeetCategory:
design:
name: Design
programming:
name: Programming
manager:
name: Manager
administrator:
name: Administrator
# data/fixtures/jobs.yml
JobeetJob:
job_sensio_labs:
JobeetCategory: programming
type: full-time
company: Sensio Labs
logo: sensio-labs.gif
url: http://www.sensiolabs.com/
position: Web Developer
location: Paris, France
description: |
You've already developed websites with symfony and you want to work
with Open-Source technologies. You have a minimum of 3 years
experience in web development with PHP or Java and you wish to
participate to development of Web 2.0 sites using the best
frameworks available.
how_to_apply: |
Send your resume to fabien.potencier [at] sensio.com
is_public: true
is_activated: true
token: job_sensio_labs
email: job@example.com
expires_at: '2010-10-10'
job_extreme_sensio:
JobeetCategory: design
type: part-time
company: Extreme Sensio
logo: extreme-sensio.gif
url: http://www.extreme-sensio.com/
position: Web Designer
location: Paris, France
description: |
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in.
Voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa
qui officia deserunt mollit anim id est laborum.
how_to_apply: |
Send your resume to fabien.potencier [at] sensio.com
is_public: true
is_activated: true
token: job_extreme_sensio
email: job@example.com
expires_at: '2010-10-10'
Le fichier de jeu de test des emplois fait référence à deux images. Vous pouvez les télécharger (http://www.symfony-project.org/get/jobeet/sensio-labs.gif, http://www.symfony-project.org/get/jobeet/extreme-sensio.gif) et les mettre sous le répertoire
web/uploads/jobs/.
Un fichier de jeu de test est écrit en YAML, et définit les objets du modèle, étiquetés avec un
nom unique (par exemple, nous avons défini deux emplois étiquetés job_sensio_labs
et job_extreme_sensio). Cette étiquette est d'une grande utilitée pour relier les objets liés,
sans avoir à définir les clés primaires (qui sont souvent
auto-incrémenté et ne peuvent pas être attribuées). Par exemple, la catégorie d'emploi de
job_sensio_labs est programming, qui est l'étiquette donnée à la catégorie
'Programming'.
Dans un fichier YAML, quand une chaîne contient des sauts de ligne (comme la colonne
descriptiondans le fichier de jeu de test d'emploi), vous pouvez utiliser le pipe (|) pour indiquer que la chaîne va continuer sur plusieurs lignes.
Bien qu'un fichier de jeu de test peut contenir des objets d'un ou de plusieurs modèles, nous avons décidé de créer un fichier par modèle pour les jeux de test de Jobeet.
Propel exige que les fichiers de jeux de test soient préfixés avec un nombre pour déterminer l'ordre dans lequel les fichiers seront chargés. Avec Doctrine, ce n'est pas nécessaire car tous les jeux de test seront chargés et enregistrés dans le bon ordre afin de s'assurer que les clés étrangères soient définies correctement.
Dans un fichier de jeu de test, vous n'avez pas besoin de définir toutes les valeurs des colonnes. Sinon,
symfony va utiliser la valeur par défaut définie dans le schéma de base de données. Et comme
symfony utilise Doctrine pour charger les données dans la base de données, tous les
comportements intégrés (comme le paramètrage automatique des colonnes
created_at ou updated_at) et les comportements personnalisés que vous pourrez ajouter dans
les classes du modèle qui sont activés.
Le chargement des données initiales dans la base de données est aussi simple que de
lancer la tâche doctrine:data-load :
$ php symfony doctrine:data-load
La tâche
doctrine:build --all --and-loadest un raccourci pour la tâchedoctrine:build --allsuivie par la tâchedoctrine:data-load.
Exécutez la tâche doctrine:build --all --and-load pour s'assurer que tout est généré
à partir de votre schéma. Cela va générer vos formulaires, vos filtres, vos modèles, supprimer
votre base de données et re-créer toutes les tables.
$ php symfony doctrine:build --all --and-load
Nous avons beaucoup utilisé l'interface en ligne de commande, mais ce n'est pas vraiment excitant, surtout pour un projet web. Nous avons maintenant tout ce qu'il faut pour créer des pages web qui interagissent avec la base de données.
Voyons comment afficher la liste des emplois, comment modifier un emploi existant et la façon de supprimer un emploi. Comme expliqué au cours du jour 1, un projet symfony est composé d'applications. Chaque application est ensuite divisé en modules. Un module est un ensemble autonome de code PHP qui représente une caractéristique de l'application (le module API par exemple), ou un ensemble de manipulations, l'utilisateur peut le faire sur un modèle d'objet (un module emploi par exemple).
Symfony est capable de générer automatiquement un module pour un modèle donné qui fournit des fonctions de manipulation de base :
$ php symfony doctrine:generate-module --with-show --non-verbose-templates frontend job JobeetJob
La doctrine:generate-module génère un module job dans l'application
frontend pour le modèle JobeetJob. Comme pour la plupart des tâches de symfony,
certains fichiers et certains répertoires ont été créés pour vous sous le répertoire
apps/frontend/modules/job/ :
| Répertoire | Description |
|---|---|
actions/ |
Les actions du module |
templates/ |
Les Templates du module |
The actions/actions.class.php file defines all the available
action for the job module:
| Nom de l'action | Description |
|---|---|
index |
Affiche les enregistrements de la table |
show |
Affiche les champs et leurs valeurs pour un enregistrement donné |
new |
Affiche le formulaire pour créer un nouvel enregistrement |
create |
Crée un nouvel enregistrement |
edit |
Affiche le formulaire pour éditer un enregistrement existant |
update |
Met à jour un enregistrement en fonction des valeurs sousmises par l'utilisateur |
delete |
Supprime un enregistrement donné de la table |
Vous pouvez tester le module job dans un navigateur :
http://www.jobeet.com.localhost/frontend_dev.php/job

Si vous essayez de modifier un emploi, vous remarquerez que l'id de la catégorie a une liste de
tous les noms des catégories. La valeur de chaque option est obtenu à partir de la
méthode __toString().
Doctrine va essayer de fournir une méthode de base __toString() en devinant un
nom de colonne descriptif comme title, name, subject, etc. Si vous voulez
quelque chose de personnalisé, vous aurez besoin d'ajouter vos propres méthodes __toString()
comme ci-dessous. Le modèle JobeetCategory est capable de deviner la méthode
__toString() en utilisant le nom de la colonne de la table jobeet_category.
// lib/model/doctrine/JobeetJob.class.php class JobeetJob extends BaseJobeetJob { public function __toString() { return sprintf('%s at %s (%s)', $this->getPosition(), $this->getCompany(), $this->getLocation()); } } // lib/model/doctrine/JobeetAffiliate.class.php class JobeetAffiliate extends BaseJobeetAffiliate { public function __toString() { return $this->getUrl(); } }
Vous pouvez désormais créer et éditer des emplois. Essayez de laisser un champ obligatoire vide, ou essayez d'entrer une date invalide. C'est vrai, symfony a créé des règles de validation de base par introspection du schéma de la base de données.

C'est tout pour ce chapitre. Nous vous avions prévenu en guise introduction. Au cours de ce troisième chapitre, nous avons à peine écrit du code PHP, mais nous avons néanmoins un module web de travail pour le modèle job, prêt à être modifié et personnalisé. Rappelez-vous, pas de code PHP signifie pas de bogues non plus !
Si vous avez encore de l'énergie, n'hésitez pas à lire le code généré pour le module et le modèle et essayer de comprendre comment il fonctionne. Sinon, ne vous inquiétez pas et dormez bien, car demain nous allons parler de l'un des paradigmes le plus utilisé dans les frameworks web, le modèle de conception MVC.
If you find a typo or an error, please register and open a ticket.
If you need support or have a technical question, please post to the official user mailing-list.