Embauchez-moi

Je suis freelance ! Besoin d'un professionnel du développement web ? Pourquoi ne pas me passer un coup de fil ?

Plus d'infos sur… http://thibault.jouannic.fr

mots-cles : Ingénieur web freelance Symfony eZ Publish Solr

Symfony 1.2, doctrine, héritage, et relations many-to-many

rosier times
Creative Commons License photo credit : mugley

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: '<?php echo date("Y-m-d"); ?>'
    end_date: '2008-11-25'
 
  l2:
    User: garcin
    Media: aikido
    start_date: '<?php echo date("Y-m-d"); ?>'
    end_date: '2008-12-30'
 
  l3:
    User: garcin
    Media: symfobook
    start_date: '<?php echo date("Y-m-d"); ?>'
    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 à++ ;


10 Commentaires

  1. Fredlab
    Posté le 04/12/2008 à 20:48 | Permalien

    Enfin un blog en Français sympa qui parle de symfony !!!

    A quand la suite du tutoriel ! Cela promet ! Propel a été choisi pour le tuto Jobeet de symfony 1.2 au lieu de Doctrine.

    Je suis impatient de voir comme l’héritage va être géré par symfony.

  2. Fredlab
    Posté le 09/12/2008 à 19:41 | Permalien

    A quand la suite !!!

  3. Fredlab
    Posté le 14/12/2008 à 11:40 | Permalien

    Toujours rien ! Bouuuuu !!! Pas grave, je reviendrai plus tard !!!

  4. Posté le 30/12/2008 à 12:27 | Permalien

    Fredlab > une version de Jobeet pour Doctrine est également publiée et adaptée depuis peu :)

    bonnes fêtes

  5. Fredlab
    Posté le 30/12/2008 à 14:55 | Permalien

    Oui, j’ai lu tout le tutorial. Il est tout simplement excellent. Il manque le traitement de l’héritage des tables et voir comment cela est pris en compte dans symfony avec Doctrine.

    Depuis la version 1.0, j’attendais plus de symfony, notamment le framework de formulaire et le nouvel admin generator.

    La 1.2 est tout simplement géniale.

    En tant qu’utilisateur avec aucune formation en développement, le modèle MVC m’apporte de la simplicité et une approche construite pour développer rapidement une petite application sécurisée au mieux.

    En espérant une suite à ce tutorial,

    Bonnes Fêtes !

  6. Posté le 16/03/2009 à 14:41 | Permalien

    Pour ceux qui auraient des pbs avec ce tuto sur les types de colonne (au moment du build-all-load :
    Validation failed in class User

    3 fields had validation errors :

    * 1 validator failed on name (type)
    * 1 validator failed on email (type)
    * 1 validator failed on phone (type)

    Sachez que j’ai corrigé le pb de mon coté en changeant «  varchar  » en «  string  » dans le fixtures.yml

  7. fr3d0m
    Posté le 28/07/2009 à 11:52 | Permalien

    Merci seb, effectivement cela posait problème.

  8. Posté le 23/09/2009 à 08:39 | Permalien

    «    »"Sachez que j’ai corrigé le pb de mon coté en changeant « varchar » en « string » dans le fixtures.yml  »"

    tu veux dire dans le schema.yml je suppose ?

  9. Posté le 23/09/2009 à 09:04 | Permalien

    => pablo : oui, autant pour moi ;)

  10. Posté le 04/11/2009 à 16:12 | Permalien

    Il ne manque plus que JOINED inheritance comme dans hibernate pour avoir une base plus «  objet  » :-)