Générer un schéma n:n

Print Friendly, PDF & Email

[ A+ ] /[ A- ]

Derrière ce nom barbare se trouve un billet (réécrit) de mon blog – le seul traduit en français d’ailleurs puisque l’original était en anglais (pour information, je publie dans la langue de Shakespeare). Bien qu’il soit intéressant de comprendre les relations 1-1, 1-n, n-1, les n-n sont les plus compliquées à mettre en oeuvre pour le débutant, notamment quand se profile déjà le spectre des formulaires.

Donc, les many-to-many (plusieurs-à-plusieurs pour les puristes) apparaissent comme un défi, surtout quand vous commencez par ce que vous ne devez pas faire (sauf si vous n’avez pas le choix), c’est-à-dire, créer votre base de données et générer le fichier schema.yml à partir de là. Aussi, jouez le jeu, écrivez votre schéma à la main et non en copier/coller.

Le projet expliqué en 5 minutes

Je vais expliquer très rapidement le projet: nous allons faire une sorte de base de données de jeux, un peu à la Neoseeker ou à la Mobygames. En fait, peu importe ce qui va se passer pour l’utilisateur: il y aura trois catégories de membres privilégiés par exemple, la connexion se passe par une authentification du frontend, etc. etc..

La situation se présente comme telle: nous allons faire un site en anglais uniquement (l’internationalisation viendra dans un autre temps).

L’élément central est le jeu ayant comme caractéristiques:

  • Un nom (nous utiliserons le nom anglais)
  • Une date de sortie (bien qu’il y a plusieurs dates de sortie selon les plateformes, pour nous simplifier la vie, nous considérerons la plus ancienne – les prochains défis que je me fixe pour symfony seront justement l’amélioration de tout cela)
  • Un éditeur (comme pour la date sortie, il s’agira d’un seul éditeur)
  • Un développeur (id.)
  • Une description (un background en anglais)
  • Plusieurs genres
  • Plusieurs plateformes

Bien évidemment, vous voyez déjà la relation n-n pointer le bout de son nez: un genre peut regrouper plusieurs jeux, tout comme une plateforme est alimentée par une librairie de jeux.

Pour continuer dans la description de notre projet, un genre a un nom, une plateforme également mais elle aura en plus un acronyme.

Voici donc les futures colonnes de nos tables, en excluant les ids qui seront générés par Doctrine. Notez que vous pouvez définir vos propres ids si vous le voulez, même si cela revient à écrire:

id:
  type: int(11)
  autoincrement: true

Faites attention néanmoins si vous avez des clés à déclarer: Doctrine génère les id en bigint(20), donc, veillez à respecter cette contrainte. Si vous déclarez les clés étrangères dans la table associative comme un int(4) par exemple, vous aurez un message d’erreur désagréable, sans pour autant que votre application soit bloquée. Mais avec l’utilisation de certaines commandes, vous risqueriez de devoir tout pas à pas.

En utilisant MySQL Workbench, voici le modèle de notre base de données:

Modèle de la base de données

Générer son schema.yml

Pour rappel, la syntaxe YAML est dépendante de l’indentation, donc, il est hors de question d’avoir une présentation du fichier de ce type:
id:
type: int(11)
primary: yes

Vous trouverez le fichier schema.yml dans le dossier config/doctrine.

Commençons par notre table centrale, la table Game:

Game:
  columns:
    name_game:
      type: string(255)
      notnull: true
    developer:
      type: string(100)
      default: Inconnu
    publisher:
      type: string(100)
      default: Inconnu
    background:
      type: string()
    release_date:
      type: date(25)

Bien que Game a une relation n-n avec Genre, je ne la précise pas dans ma table. Il suffit que je l’écrive dans la définition de la table Genre pour que Doctrine fasse le lien automatiquement.

Notez que j’assigne une valeur par défaut à Publisher et Developer et que le background correspond à une zone de texte.

Par contre, je ne mets pas de colonne created_at ou updated_at. La syntaxe pour ces dernières serait simplement d’ajouter:

Game:
    actAs: { Timestampable: ~ }
   colums:
     ....

Mais dans notre projet, vous ne devrez pas vous inquiétez de tout cela.

Passons maintenant à notre table Genre.

Genre:
  columns:
    name_genre:
      type: string(50)
      notnull: true

Jusqu’ici, rien de bien méchant et d’inconnu n’est apparu. C’est donc à la suite de ce code que vous pouvez ajouter la partie « relations ».

Genre:
  columns:
    name_genre:
      type: string(50)
      notnull: true
  relations:
    Games:
      foreignAlias: Genres
      class: Game
      refClass: GameGenre
      local: genre_id
      foreign: game_id

Donc, comment comprendre cette partie?

Je donne le nom de la relation en utilisant le nom de la table avec laquelle Genre est lié (Game au pluriel). Ensuite, je lui donne un alias optionnel qui correspond cette fois à ma table courante (à nouveau le nom au pluriel).

Class représente en fait la classe (la table) qui est liée à Genre.

Nous savons déjà que la représentation d’une relation n:n se fait traditionnellement avec une table intermédiaire (faites l’expérience dans MySQL Workbench et vous verrez la naissance de la table de liaison) où les relations seront stockées. Donc, nous indiquons dans notre schéma quelle table utiliser dans ce cas-ci: Doctrine saura que GameGenre stocke les clés primaires des jeux et des genres. Mais nous avons besoin d’un autre paramètre que nous verrons plus tard.

Local se réfère à l’objet courant, ici Genre, en donnant le nom de la colonne le concernant dans GameGenre (puisque refClasse est GameGenre). Foreign fait la même chose mais avec l’autre table.

Définissons maintenant GameGenre qui doit impérativement se trouver dans votre schéma, sinon,
Doctrine ne saura pas quoi faire.

GameGenre:
  columns:
    game_id:
      type: bigint(20)
      primary: true
    genre_id:
      type: bigint(20)
      primary: true
  relations:
    Genre:
      onDelete: cascade
      local: genre_id
      foreign: id
      foreignAlias: GameGenres
    Game:
      onDelete: cascade
      local: game_id
      foreign: id
      foreignAlias: GameGenres

Les explications sont similaires par rapport à Genre. Il y a trois différences:

  • onDelete: cascade, qui est notre paramètre à donner afin de supprimer les lignes concernant soit un genre soit un jeu si celui-ci est supprimé.
  • Le nom des tables concernées est le nom de la relation.
  • Local représente toujours notre colonne de la table concernée mais Foreign ici prend le nom de la colonne dans la table conservée. C’est ainsi que si les clés primaires ont un nom autre que id, Doctrine sait faire le lien.

Une fois que vous savez cela, vous pourrez définir la relation entre Game et Platform:

Game:
  columns:
    name_game:
      type: string(255)
      notnull: true
    developer:
      type: string(100)
      default: Inconnu
    publisher:
      type: string(100)
      default: Inconnu
    background:
      type: string()
    release_date:
      type: date(25)

Genre:
  columns:
    name_genre:
      type: string(50)
      notnull: true
  relations:
    Games:
      foreignAlias: Genres
      class: Game
      refClass: GameGenre
      local: genre_id
      foreign: game_id

GameGenre:
  columns:
    game_id:
      type: bigint(20)
      primary: true
    genre_id:
      type: bigint(20)
      primary: true
  relations:
    Genre:
      onDelete: cascade
      local: genre_id
      foreign: id
      foreignAlias: GameGenres
    Game:
      onDelete: cascade
      local: game_id
      foreign: id
      foreignAlias: GameGenres

Platform:
  columns:
    name_platform:
      type: string(50)
      notnull: true
    acronyme:
      type: string(10)
      notnull: true
  relations:
    Games:
      foreignAlias: Platforms
      class: Game
      refClass: GamePlatform
      local: platform_id
      foreign: game_id

GamePlatform:
  columns:
    game_id:
      type: bigint(20)
      primary: true
    platform_id:
      type: bigint(20)
      primary: true
  relations:
    Platform:
      onDelete: cascade
      local: platform_id
      foreign: id
      foreignAlias: GamePlatforms
    Game:
      onDelete: cascade
      local: game_id
      foreign: id
      foreignAlias: GamePlatforms

Avec ce schéma, vous pouvez générer des fichiers et des formulaires qui vont vous faciliter la vie, notamment dans l’insertion de données sans que vous ayez à requêter vous-même.

En savoir plus:
Doctrine 1.2
Symfony with Doctrine saving many-to-many relations


Vous avez trouvé l'article intéressant? Partagez-le!

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

3 + vingt =