Mappage des entités avec les annotations de Doctrine.

Logo DoctrinePetit tutoriel sur le mappage des entités (que l’on appel Entity) avec doctrine et son système d’annotation.

On ne verra que quatre types de relation (je ne sais même pas s’il y en existe d’autre) :

Et pour que le choses soit un peu plus graphique et compréhensible, je vous met un exemple pour chaque avec le code SQL des tables créés, les classes PHP en découlant et un schéma Merise basique.

Pourquoi Merise ? Alors qu’il faudrait lui préférer l’UML qui est quand même plus approprié pour les constructions de base de données objets. Simplement parce que les habitudes ont la vie dure et que l’on va encore manger du Merise pendant un grand nombre d’années.

Lorsque toutes vos entités seront reliées, il faudra utiliser les commandes de génération de base de données de Doctrine (doctrine:schema:create). Doctrine a sa façon bien à lui de nommer les contraintes.

Ce comportement implique certaines obligations, un exemple concret :

Vous utilisez Power AMC, Mysql Workbench ou tout autre outil de modélisation de données. Vous générez le code SQL de construction de la base de données et l’exécutez dans PhpMyAdmin. Doctrine dispose d’une fonctionnalité permettant de faire de la retro-conception sur votre base de données (doctrine:generate:entities) et ainsi générer les entités qui en découlent (une classe par table). Cependant cette fonctionnalité ne génère pas les relations entres tables. Ce qui nous oblige à les créer à la main. Mais comme Doctrine gère lui même le nom des contraintes (exemple : IDX_DD5B6138975D4D4C) il faut régénérer le code SQL de la base de données pour que les spécificités de Doctrine s’applique.

Dans un premier temps, le lien vers la page de la documentation de Doctrine en ce qui concerne les annotations : http://www.doctrine-project.org/docs/orm/2.1/en/reference/annotations-reference.html

Pour ceux qui ne sont pas familiarisé avec les annotations, cette technique nous vient du Java où cette méthode de configuration est intégrée au spécification du langage (contrairement à PHP).

Donc les annotations sont bêtement des commentaires construit de manière rigoureuse qui vont servir à la configuration. Cela remplace un fichier XML ou YAML.

Ce commentaire spécial est construit comme suit :

/**
 * Les deux étoiles après le premier slash sont primordiale, sans ça ne fonctionne pas
 * une annotation commence toujours par un arobase
 * @Entity(repositoryClass="MyProjectUserRepository")
 * pas de guillemet simple « ' », seulement des doubles cottes « " »
 */

Voila pour les bases de l’annotation.

Tout les exemples sont réalisés ici dans l’idée d’être intégrable dans une application utilisant le framework Symfony 2. D’où la présence du préfix « ORM » devant chaque annotation Doctrine.

Pour utiliser les annotations Doctrine dans une classe pensez à importer la classe DoctrineORMMapping de cette manière en début de fichier

use DoctrineORMMapping as ORM;

Première type de relation,

Un client d'une banque possède une et une seule carte de crédit, mais peut aussi ne pas en avoir. Une carte de crédit appartient à un et un seule client.Un client d’une banque possède une et une seule carte de crédit, mais peut aussi ne pas en avoir.
Une carte de crédit appartient à un et un seule client.

Le code SQL pour chaque table :

TABLE `Carte` (
  `idCarte` INT NOT NULL ,
  `type` VARCHAR(45) NULL ,
  PRIMARY KEY (`idCarte`) ) ;

TABLE `Client` (
  `idClient` INT NOT NULL ,
  `nom` VARCHAR(45) NULL ,
 ...
  `Carte_idCarte` INT NOT NULL ,
  PRIMARY KEY (`idClient`, `Carte_idCarte`) ,
  INDEX `fk_Client_Carte` (`Carte_idCarte` ASC) ,
  CONSTRAINT `fk_Client_Carte`
    FOREIGN KEY (`Carte_idCarte` )
    REFERENCES `Carte` (`idCarte` ) ) ;

Cela se concrétise par l’utilisation de deux annotations :

namespace WaldoRelationBundleEntity;

use DoctrineORMMapping as ORM;

/**
* @ORMTable(name="Client")
* @ORMEntity
*/
class Client
{

/**
* @ORMColumn(name="idClient", type="bigint", nullable=false)
* @ORMId
* @ORMGeneratedValue(strategy="IDENTITY")
*/
private $identifiantClient;

/**
* @ORMColumn(name="nom", type="string", length=45, nullable=true)
*/
private $nom;

/**
* @ORMOneToOne(targetEntity="Carte", cascade={"persist", "merge", "remove"})
* @ORMJoinColumn(name="Carte_idCarte", referencedColumnName="idCarte")
*/
private $carte;

/**
* @return int
*/
public function getIdentifiantClient()
{
return $this->identifiantClient;
}

/**
* @param string $nom
*/
public function setNom($nom)
{
$this->nom = $nom;
}

/**
* @return string
*/
public function getNom()
{
return $this->nom;
}

/**
* @param WaldoRelationBundleEntityCarte $carte
*/
public function setCarte(WaldoRelationBundleEntityCarte $carte)
{
$this->carte = $carte;
}

/**
* @return WaldoRelationBundleEntityCarte
*/
public function getCarte()
{
return $this->carte;
}
}
namespace WaldoRelationBundleEntity;

use DoctrineORMMapping as ORM;

/**
* @ORMTable(name="Carte")
* @ORMEntity
*/
class Carte
{

/**
* @ORMColumn(name="idCarte", type="bigint", nullable=false)
* @ORMId
* @ORMGeneratedValue(strategy="IDENTITY")
*/
private $identifiantCarte;

/**
* @ORMColumn(name="type", type="string", length=45, nullable=true)
*/
private $type;

/**
* @return int
*/
public function getIdentifiantCarte()
{
return $this->identifiantCarte;
}

/**
* @param string $type
*/
public function setType($type)
{
$this->type = $type;
}

/**
* @return string
*/
public function getType()
{
return $this->type;
}
}

Avec ce code on pourra faire ce genre de chose :

$client = new Client();
$client->setCarte(new Carte());

$client->setNom("Waldo");
$client->getCarte()->setType("Gold MasterCard")

Explication :

/**
* @ORMColumn(name="idCarte", type="bigint", nullable=false)
* @ORMId
* @ORMGeneratedValue(strategy="IDENTITY")
*/
private $identifiantCarte;

L’annotation Column permet de mapper la propriété $identifiantCarte avec le champ idCarte situé en base de données.
L’attribut name définit le nom du champ dans la table qui est mappé avec la propriété.
L’attribut type définit le type de valeur accepté par la propriété.
L’attribut nullable définit si la propriété peut être nulle ou non.
L’annotation Id définit cette propriété en tant qu’identifiant unique de l’objet/table.
L’annotation GeneratedValue définit quelle stratégie est appliquée à cette propriété (ici IDENTITY = auto incrémentation).

/**
* @ORMOneToOne(targetEntity="Carte", cascade={"persist", "merge"})
* @ORMJoinColumn(name="Carte_idCarte", referencedColumnName="idCarte")
*/
private $carte;

targetEntity="Carte" : ce bout de code permet de définir que la propriété $carte est un objet de type Carte représenté par l’entité Carte.
cascade={"persist", "merge"}) : permet de définir le comportement lors de la persistance de l’objet Client. L’option "remove" définit que si l’objet Client est supprimé, la Carte associé aussi.
L’annotation JoinColumn détermine quels champs de la base de données définissent la relation.
L’attribut name est le nom du champs qui permet de faire la relation avec l’entité Carte.
L’attribut referencedColumnName est le nom du champs identifiant de l’entité visé.

Attention les valeur de name et referencedColumnName sont les noms des champs qui vont apparaître dans le SQL, alors que l’attribut targetEntity prend le nom de l’entité (la classe) et non le nom de la table.
Un petit récapitulatif.

/**
*@ORMOneToOne(targetEntity="Nom de la classe visée", cascade={"persist", "merge"})
*@ORMJoinColumn(name="Champ de la table qui fait la relation", referencedColumnName="Nom du champ de la table visée sur lequel repose la relation")
*/
private $carte;

La suite très bientôt !

Publié dans Doctrine, PHP, Symfony2
4 commentaires pour “Mappage des entités avec les annotations de Doctrine.
  1. seb dit :

    Sympa cet article,

    Mais à quand la suite ? :)

  2. Gueno dit :

    Merci pour cette belle explication :)

    cependant j’ai notée une erreur à l’avant-avant-dernièr bout de code, on peut lire :
    /**
    * @ORMColumn(name="idClient", type="bigint", nullable=false)
    * @ORMId
    * @ORMGeneratedValue(strategy="IDENTITY")
    */
    private $identifiantCarte;

    L’annotation « Column » contient « idClient » en nom de colonne, hors on annote l’attribut « identifiantCarte », donc je suppose que le code devrait être corrigé comme suit :
    * @ORMColumn(name="idCarte", type="bigint", nullable=false)
    ou
    private $identifiantClient;

  3. gouala dit :

    Bonjour,
    Quelle est la représentation UML du schéma Merise affiché ?
    Merci d’avance

Laisser un commentaire

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

*