Symfony 1.2, doctrine, héritage, et relations many-to-many
Aujourd'hui, nous allons tester la bêta 1.2 de Symfony, et son plugin doctrine. Exit Propel : place aux jeunes ! Pour ce faire, nous allons construire un mini-sigb, système intégré de gestion de bibliothèque.
La gestion de bibliothèque nécessite de mettre en oeuvre quelques concepts intéressants. Par exemple, les relations n à n (many-to-many) : un abonné peut emprunter plusieurs livres, un livre peut être emprunté par plusieurs abonnés. On peut également utiliser l'héritage de tables : un dvd, un cd audio, un livre, un magazine sont des médias empruntables, avec des attributs communs (durée de prêt, date d'achat) et spécifiques (nombre de pistes, nombre de chapitres, mois de parution pour les mensuels, etc.)
L'environnement de développement
Je vous ferai grâce de la procédure d'installation, qui est déjà abondamment commentée. La suite ne change pas : virtual host, génération de projet, rien de nouveau.
Maintenant que notre environnement est en place, désactivons Propel pour activer Doctrine. Pour cela, éditons config/ProjectConfiguration.class.php :
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
$this->enablePlugins(array('sfDoctrinePlugin'));
$this->disablePlugins(array('sfPropelPlugin'));
}
}
Ensuite, configurons notre base de donnée dans config/databases.yml :
all: doctrine: class: sfDoctrineDatabase param: dsn: mysql:dbname=bibli;host=localhost username: root password: encoding: utf8 persistent: true pooling: true
Le modèle
Maintenant, passons aux choses sérieuses avec la création du modèle. Ne faites pas comme moi, ne perdez pas un quart d'heure avant de vous rendre compte que le bon fichier est bien config/*doctrine*/schema.yml :
User: columns: name: varchar(150) email: varchar(150) phone: varchar(20) relations: Media: refClass: Loan local: user_id foreign: media_id Media: columns: title: varchar(150) author: varchar(150) summary: varchar(1000) relations: User: refClass: Loan local: media_id foreign: user_id Book: inheritance: extends: Media type: column_aggregation keyField: type keyValue: 1 columns: number_of_pages: integer DVD: tableName: dvd inheritance: extends: Media type: column_aggregation keyField: type keyValue: 2 columns: duration: integer Loan: actAs: Timestampable columns: media_id: type: integer primary: true user_id: type: integer primary: true start_date: date end_date: date
Remarquez de quelle façon doctrine gère les relations n-n, par l'ajout d'options "relations" dans les tables User et Media. Note : l'attribut "relations" est optionnel dans la table Media : il aurait été rajouté automatiquement.
Jetons un coup d'oeil au code généré (qui soit dit en passant est agréablement plus propre que celui de propel) :
// lib/model/doctrine/base/BaseUser.class.php
abstract class BaseUser extends sfDoctrineRecord
{
public function setTableDefinition()
{
...
}
public function setUp()
{
$this->hasMany('Media', array('refClass' => 'Loan',
'local' => 'user_id',
'foreign' => 'media_id'));
}
}
// lib/model/doctrine/base/BaseMedia.class.php
abstract class BaseMedia extends sfDoctrineRecord
{
public function setTableDefinition()
{
...
}
public function setUp()
{
$this->hasMany('User', array('refClass' => 'Loan',
'local' => 'media_id',
'foreign' => 'user_id'));
}
}
// lib/model/doctrine/base/BaseLoan.class.php
abstract class BaseLoan extends sfDoctrineRecord
{
public function setTableDefinition()
{
$this->hasColumn('media_id', 'integer', null, array('type' => 'integer', 'primary' => true));
$this->hasColumn('user_id', 'integer', null, array('type' => 'integer', 'primary' => true));
...
}
}
Intéressons nous maintenant à l'héritage avec doctrine. L'ORM peut gérer de trois façons distinctes l'héritage :
- Héritage simple : héritage basique, toutes les tables filles partagent exactement les mêmes colonnes que la table mère. En pratique, on a une seule table dans la BD, mais une classe par table.
- Héritage concret : ce type d'héritage permet d'obtenir autant de tables que de classes. Chaque table fille contient tous les attributs, y compris les attributs hérités (c'est à dire ceux de la classe mère).
- Héritage par aggrégation : Cette méthode permet d'obtenir une seule table, qui contient tous les champs possibles de toutes les classes filles.
Dans notre cas, on obtient donc trois tables, dont la table media, qui contient tous les champs déclarés par ses enfants :
mysql> show tables; +-----------------+ | Tables_in_bibli | +-----------------+ | loan | | media | | user | +-----------------+ mysql> desc media; +-----------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-----------------+--------------+------+-----+---------+----------------+ | id | bigint(20) | NO | PRI | NULL | auto_increment | | title | varchar(150) | YES | | NULL | | | author | varchar(150) | YES | | NULL | | | summary | text | YES | | NULL | | | type | varchar(255) | YES | | NULL | | | number_of_pages | bigint(20) | YES | | NULL | | | duration | bigint(20) | YES | | NULL | | +-----------------+--------------+------+-----+---------+----------------+
Je vous laisse consulter le manuel de Doctrine sur l'héritage, et celui sur les relations many-to-many si vous voulez en savoir plus.
Les données de test
Histoire de pouvoir travailler, nous allons remplir le fichier data/fixtures/fixtures.yml de quelques données de test :
User: thibault: name: Thibault Jouannic email: teeboo@gmail.com phone: '+33467453834' garcin: name: Garcin Fony email: garcin@symfony.fr phone: '+33666666666' Book: symfobook: title: Symfony book author: Fabien Potencier summary: Un livre sur symfony number_of_pages: 250 phpbook: title: php avancé author: rasmus summary: un livre sur php number_of_pages: 300 DVD: aikido: title: DVD Symfony author: Fabien Potencier summary: Tutoriaux en directs duration: 90 Loan: l1: User: thibault Media: phpbook start_date: '' end_date: '2008-11-25' l2: User: garcin Media: aikido start_date: '' end_date: '2008-12-30' l3: User: garcin Media: symfobook start_date: '' end_date: '2008-11-05'
Il ne nous reste plus qu'à laisser symfony tout générer :
symfony doctrine:build-all-reload --no-confirmation
Et voilà ! Notre environnement est en place. Tout ceci nous promet de belles réjouissances, mais ce sera pour la prochaine fois. En attendant, couvrez vous bien, et à++;