Aller au menu - Aller au contenu

Icône La couche métier : les entités

Avatar
Mise à jour : 02/05/2012
Difficulté : Intermédiaire Intermédiaire Durée d'étude : 3 heures Creative Commons BY-NC-SA
20 752 visites depuis 7 jours, dont 1 020 sur ce chapitre classé 17/786
Dans ce chapitre, on va explorer la couche Modèle sous Symfony2. Ce modèle sera implémenté en utilisant l’ORM (pour Object Relation Mapper, soit en français Lien Objet-Relation) Doctrine2.

Image utilisateur
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Notions d'ORM : fini les requêtes, utilisons des objets

Définition d'ORM : Object-Relational Mapper


L'objectif d'un ORM est simple : se charger de l'enregistrement de vos données en vous faisant oublier que vous avez une base de données.

Comment ? En s'occupant de tout ! Nous n'allons plus écrire de requêtes, ni créer de tables via phpMyAdmin. Dans notre code PHP, nous allons faire appel à Doctrine2, l'ORM par défaut de Symfony2, pour faire tout cela.

Un rapide exemple pour bien comprendre ? Supposons que vous disposez d'une variable <?php $utilisateur, un objet User qui représente l'un de vos utilisateurs qui vient de s'inscrire sur votre site. Pour sauvegarder cet objet, vous êtes habitué à créer votre propre fonction qui effectue une requête SQL du type INSERT INTO dans la bonne table, etc. Bref, vous devez gérer tout ce qui touche à l'enregistrement en base de données. En utilisant un ORM, vous n'aurez plus qu'à utiliser quelques fonctions de cet ORM, par exemple : <?php $orm->save($utilisateur). Et ce dernier s'occupera de tout ! Vous avez enregistré votre utilisateur en une seule ligne ;) . Bien sûr, ça n'est qu'un exemple, nous verrons les détails pratiques dans la suite de ce chapitre, mais retenez bien l'idée.

Mais l'effort que vous devrez faire pour bien utiliser un ORM, c'est d'oublier votre côté « administrateur de base de données ». Oubliez les requêtes SQL, pensez objet !

Vos données sont des objets


Dans ORM, il y a la lettre O comme Objet. En effet, pour que tout le monde se comprenne, toutes vos données doivent être sous forme d'objets. Concrètement, qu'est-ce que cela implique dans notre code ? Pour reprendre l'exemple de notre utilisateur, quand vous étiez petit, vous utilisiez sûrement un tableau puis vous accédiez à vos attributs via <?php $utilisateur['pseudo'] ou <?php $utilisateur['email'] par exemple. Soit, c'était très courageux de votre part. Mais nous allons aller plus loin, maintenant.

Utiliser des objets n'est pas une grande révolution en soi. Faire <?php $utilisateur->getPseudo() au lieu de <?php $utilisateur['pseudo'], c'est joli, mais limité. Ce qui est une révolution, c'est de coupler cette représentation objet avec l'ORM. Qu'est-ce que vous pensez d'un <?php $utilisateur->getCommentaires() ? Ahah ! Vous ne pouviez pas faire cela avec votre tableau ! Ici, la méthode <?php $utilisateur->getCommentaires() déclencherait la bonne requête, récupérerait tous les commentaires postés par votre utilisateur, et vous retournerait une sorte de tableau d'objets de type Commentaire que vous pourriez afficher sur la page de profil de votre utilisateur, par exemple. Ça commence à devenir intéressant, n'est-ce pas ?

Au niveau du vocabulaire, un objet dont vous confiez l'enregistrement à l'ORM s'appelle une entité (entity en anglais). On dit également persister une entité, plutôt qu'enregistrer une entité. Vous savez, l'informatique et le jargon... :-°

Créer une première entité avec Doctrine2

Une entité, c'est juste un objet


Derrière ce titre se cache la vérité. Une entité, ce que l'ORM va manipuler et enregistrer dans la base de données, ce n'est vraiment rien d'autre qu'un simple objet. Voici ce à quoi pourrait ressembler l'objet Article de notre blog :
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
// src/Sdz/BlogBundle/Entity/Article.php

namespace Sdz\BlogBundle\Entity;

class Article
{
    protected $id;

    protected $date;

    protected $titre;

    protected $contenu;

    // Et bien sûr les getter/setter :

    public function setId($id)
    {
        $this->id = $id;
    }
    public function getId()
    {
        return $this->id;
    }

    public function setDate($date)
    {
        $this->date = $date;
    }
    public function getDate()
    {
        return $this->date;
    }

    public function setTitre($titre)
    {
        $this->titre = $titre;
    }
    public function getTitre()
    {
        return $this->titre;
    }

    public function setContenu($contenu)
    {
        $this->contenu = $contenu;
    }
    public function getContenu()
    {
        return $this->contenu;
    }
}

Inutile de créer ce fichier pour l'instant, nous allons le générer plus bas, patience. ;)

Comme vous pouvez le voir, c'est très simple. Un objet, des propriétés, et bien sûr, les getters/setters correspondants. On pourrait en réalité utiliser notre objet dès maintenant !
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
// src/Sdz/BlogBundle/Controller/BlogController.php

namespace Sdz\BlogBundle\Controller;

// N'oubliez pas de rajouter ce use bien entendu ;)
use Sdz\BlogBundle\Entity\Article;

// ...

public function testAction()
{
	$article = new Article;
	$article->setDate(new \Datetime()); // date d'aujourd'hui
	$article->setTitre('Mon dernier weekend');
	$article->setContenu("C'était vraiment super et on s'est bien amusé.");
	
	return $this->render('SdzBlogBundle:Article:test.html.twig', array('article' => $article));
}

Tout cela avec la vue correspondante qui afficherait l'article passé en argument avec un joli code HTML. Le code est un peu limité car statique, mais l'idée est là et vous voyez comment l'on peut se servir d'une entité.

Normalement, vous devez vous poser une question : comment l'ORM va-t-il faire pour enregistrer cet objet dans la base de données s'il ne connaît rien de nos propriétés « date », « titre » et « contenu » ? Comment peut-il deviner que notre propriété « date » doit être stockée avec un champ de type DATE dans la table ? La réponse est aussi simple que logique : il ne devine rien, on va le lui dire !

Une entité, c'est juste un objet… mais avec des commentaires !


Quoi ? Des commentaires ?

O.K., je dois avouer que ça n'est pas intuitif si vous ne vous en êtes jamais servi, mais... oui, on va rajouter des commentaires dans notre code et Symfony2 va se servir directement de ces commentaires pour ajouter des fonctionnalités à notre application. Ce type de commentaire se nomme l'annotation. Les annotations doivent respecter une syntaxe particulière, regardez par vous-même :
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
// src/Sdz/BlogBundle/Entity/Article.php

namespace Sdz\BlogBundle\Entity;

// On définit le namespace des annotations utilisées par Doctrine2
// En effet il existe d'autres annotations, on le verra par la suite, qui utiliseront un autre namespace
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository")
 */
class Article
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(name="date", type="date")
     */
    private $date;

    /**
     * @ORM\Column(name="titre", type="string", length=255)
     */
    private $titre;

    /**
     * @ORM\Column(name="contenu", type="text")
     */
    private $contenu;

    // les getters
    // les setters
}

Ne recopiez pas toutes ces annotations à la main, on utilise le générateur en console au paragraphe juste en dessous :) .

Grâce à ces annotations, Doctrine2 dispose de toutes les informations nécessaires pour utiliser notre objet, créer la table correspondante, l'enregistrer, définir un identifiant (id) en auto-incrément, etc. Ces informations se nomment les metadatas de notre entité. Je ne vais pas épiloguer sur les annotations, elles sont suffisamment claires pour être comprises par tous. :) Ce qu'on vient de faire, à savoir rajouter les metadatas à notre objet Article s'appelle mapper l'objet Article. C'est-à-dire faire le lien entre notre objet de base et la représentation physique qu'utilise Doctrine2.

Sachez quand même que, bien que l'on utilisera les annotations tout au long de ce tutoriel, il existe d'autres moyens de définir les metadatas d'une entité : en YAML, en XML et en PHP. Vous trouverez plus d'informations sur la définition des metadatas dans la documentation de Doctrine2. Je vous invite également à aller lire cette page de la documentation pour connaitre tous les types possible, car ici on n'a utilisé que integer, date, string et text ; mais il en existe bien sûr d'autres.

Générer une entité : le générateur à la rescousse !


En tant que bon développeur, on est fainéant à souhait, et ça, Symfony2 l'a bien compris ! On va donc se refaire une petite session en console afin de générer notre première entité. Entrez la commande php app/console generate:doctrine:entity et suivez le guide :
  1. The Entity shortcut name: : grâce au commentaire juste au-dessus, vous avez compris, il faut rentrer le nom de l'entité sous le format NomBundle:NomEntité. Dans notre cas, on entre donc SdzBlogBundle:Article ;
  2. Configuration format (yml, xml, php, or annotation) [annotation]: : comme précisé, on va utiliser les annotations qui sont d'ailleurs le format par défaut. Appuyez juste sur la touche Entrée ;
  3. New field name (press <return> to stop adding fields): : on commence à saisir le nom de nos champs. Lisez bien ce qui est inscrit avant : Doctrine2 va ajouter automatiquement l'id, de ce fait, pas besoin de le redéfinir ici. On entre donc notre date : date ;
  4. Field type [string]: : c'est maintenant que l'on va dire à Doctrine à quel type correspond notre propriété « date ». Voici la liste des types possibles : array, object, boolean, integer, smallint, bigint, string, text, datetime, datetimetz, date, time, decimal, et float. Tapez donc datetime ;
  5. Répétez les points 3 et 4 pour les propriétés « titre » et « contenu ». Titre est de type string de 255 caractères (pourquoi pas). Contenu est par contre de type text ;
  6. Lorsque vous avez fini, appuyez sur la touche Entrée ;
  7. Do you want to generate an empty repository class [no]? : oui, on va créer le repository associé, c'est très pratique. Entrez donc yes ;
  8. Confirmez la génération, et voilà !

Allez tout de suite voir le résultat dans le fichier Entity/Article.php. Symfony2 a tout généré, même les getters et les setters ! Vous êtes l'heureux propriétaire d'une simple classe… avec plein d'annotations ! :p

Affiner notre entité avec de la logique métier


L'exemple de notre entité Article est un peu simple, mais rappelez-vous que la couche modèle dans une application est la couche métier. C'est-à-dire qu'en plus de gérer vos données, un modèle contient également la logique de l'application. Voyez par vous-mêmes avec les exemples ci-dessous.

Attributs calculés


Prenons l'exemple d'une entité Commande, qui représenterait un ensemble de produit à acheter sur un site d'e-commerce. Cette entité aurait les attributs suivant :
  • ListeProduits : qui contient un tableau des produits de la commande ;
  • AdresseLivraison : qui contient l'adresse où expédier la commande ;
  • Date : qui contient la date de la prise de la commande ;
  • Etc.
Ces trois attributs devront bien entendu être mappés (c'est-à-dire défini comme des colonnes pour l'ORM via des annotations) pour être enregistrés en base de données par Doctrine2. Mais il existe d'autres caractéristiques pour une commande, qui nécessitent un peu de calcul : le prix total, un éventuel coupon de réduction, etc. Ces caractéristiques n'ont pas à être persisté en base de données, car ils peuvent être déduits des informations que l'on a déjà. Par exemple pour avoir le prix total, il suffit de faire une boucle sur ListeProduits et d’additionner les prix de chaque produit :
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?php
// Exemple :
class Commande
{
    public function getPrixTotal()
    {
        $prix = 0;
        foreach($this->getListeProduits() as $produit)
        {
            $prix += $produit->getPrix();
        }
        return $prix;
    }
}


N'hésitez donc pas à créer des méthodes getQuelquechose() qui contiennent de la logique métier. L'avantage de mettre la logique dans l'entité même est que vous êtes sûr de réutiliser cette même logique partout dans votre application. Il est bien plus propre et pratique de faire <?php $commande->getPrixTotal() que d'éparpiller à droite et à gauche différentes manières de calculer ce prix total ;) . Bien sûr, ces méthodes n'ont pas d'équivalent setQuelquechose(), cela n'a pas de sens !

Attributs par défaut


Vous avez aussi besoin des fois de définir une certaine valeur à vos entités lors de leur création. Or nos entités sont de simple objets PHP, et la création d'un objet PHP fait appel... au constructeur. Pour notre entité Article on pourrait définir le constructeur suivant :
Code : PHP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// src/Sdz/BlogBundle/Entity/Article.php

namespace Sdz\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Article
{
    // La définition des attributs ...

    public function __construct()
    {
        $this->date = new \Datetime(); // Par défaut, la date de l'article est la date d'aujourd'hui
    }

    // Les getter/setter ...
}


Conclusion


N'oubliez pas : une entité est un objet PHP qui correspond à un besoin dans votre application.

N'essayez donc pas de raisonner en termes de tables, base de données, etc. Vous travaillez maintenant avec des objets PHP, qui contiennent une part de logique métier, et qui peuvent se manipuler facilement.

Tout sur le mapping !

Vous avez rapidement vu comment mapper vos objets avec les annotations. Mais ces annotations permettent d'inscrire pas mal d'autres informations. Il faut juste en connaitre la syntaxe, c'est l'objectif de cette sous-partie.

Tout ce qui va être décrit ici se trouve bien entendu dans la documentation officielle sur le mapping, que vous pouvez garder à porter de main.

L'annotation Entity


L'annotation Entity définit un objet comme étant une entité, et donc persisté par Doctrine. Cette annotation s'écrit comme suit :
Code : Autre
1
@ORM\Entity

Elle se positionne juste avant la définition de la classe.

Il existe un seul paramètre facultatif pour cette annotation, "repositoryClass". Il permet de préciser le namespace complet du repository qui gère cette entité. Nous donnerons le même nom à nos repository qu'à nos entités, en les suffixant simplement de "Repository". Pour notre entité Article, cela donne :
Code : Autre
1
@ORM\Entity(repositoryClass="Sdz\BlogBundle\Entity\ArticleRepository")


L'annotation Table


L'annotation Table est facultative, une entité se définit juste par son annotation Entity. Cependant l'annotation Table permet de personnaliser le nom de la table qui sera créée dans la base de données. Par exemple, on pourrait préfixer notre table article par "sdz" :
Code : Autre
1
@ORM\Table(name="sdz_article")

Elle se positionne juste avant la définition de la classe.

L'annotation Column


L'annotation Column permet de définir les caractéristiques de la colonne concernée. Elle s'écrit comme suit :
Code : Autre
1
@ORM\Column

Elle se positionne juste avant la définition de l'attribut concerné.

L'annotation Column comprend quelques paramètres, dont le plus important est le type de colonne.

Les types de colonnes


Les types de colonnes que vous pouvez définir en annotation sont des types Doctrine, et uniquement Doctrine. Ne les confondez pas avec leurs homologues SQL ou PHP, ce sont des types à Doctrine seul. Ils font le passage des types SQL aux types PHP.

Voici la liste exhaustive :

Type Doctrine Type SQL Type PHP Utilisation
string VARCHAR string Toutes les chaines de caractères jusqu'à 255 caractères.
integer INT integer Tous les nombres jusqu'à 2 147 483 647.
smallint SMALLINT integer Tous les nombres jusqu'à 32 767.
bigint BIGINT string Tous les nombres jusqu'à 9 223 372 036 854 775 807.
Attention PHP reçoit une chaîne de caractères car il ne supporte pas un si grand nombre (suivant que vous êtes en 32 ou en 64 bits).
boolean BOOLEAN boolean Les valeurs booléennes true et false.
decimal DECIMAL double Les nombres à virgules.
date ou datetime DATETIME objet DateTime Toutes les dates et heures.
time TIME objet DateTime- Toutes les heures.
text CLOB string Les chaînes de caractères de plus de 255 caractères.
object CLOB Type de l'objet stocké Stocke un objet PHP en utilisant serialize/unserialize.
array CLOB array Stocke un tableau PHP en utilisant serialize/unserialize.
float FLOAT double Tous les nombres à virgules.
Attention, fonctionne uniquement sur les serveurs dont la locale utilise un point comme séparateur.


Les types Doctrine sont sensibles à la casse. Ainsi, le type "String" n'existe pas, il s'agit du type "string". Facile à retenir : tout est en minuscule !


Le type de colonne se définit en tant que paramètre de l'annotation Column, comme suit :
Code : Autre
1
@ORM\Column(type="string")


Les paramètres de l'annotation Column


Il existe 7 paramètres, tous facultatifs, que l'on peut passer à l'annotation Column afin de personnaliser le comportement. Voici la liste exhaustive :

Paramètre Valeur par défaut Utilisation
type string Définit le type de colonne comme nous venons de le voir.
name Nom de l'attribut Définit le nom de la colonne dans la table. Par défaut, le nom de la colonne est le nom de l'attribut de l'objet, ce qui convient parfaitement.
Mais vous pouvez changer le nom de la colonne, par exemple si vous préférez "isExpired" en attribut mais "is_expired" dans la table.
length 255 Définit la longueur de la colonne.
Applicable uniquement sur un type de colonne string.
unique false Définit la colonne comme unique. Par exemple sur une colonne email pour vos membres.
nullable false Permet à la colonne de contenir des NULL.
precision 0 Définit la précision d'un nombre à virgule, c'est-à-dire le nombre de chiffres en tout.
Applicable uniquement sur un type de colonne decimal.
scale 0 Définit le scale d'un nombre à virgule, c'est-à-dire le nombre de chiffres après la virgule.
Applicable uniquement sur un type de colonne decimal.


Pour définir plusieurs options en même temps, il faut simplement les séparer avec une virgule. Par exemple, pour une colonne "email" en string 255, et unique :
Code : Autre
1
@ORM\Column(type="string", length=255, unique=true)
Vous savez maintenant tout ce qu'il faut savoir sur la couche Modèle sous Symfony2 en utilisant les entités de l'ORM Doctrine2.

Dans le prochain chapitre, nous apprendrons à manipuler ces entités.
Chapitre précédent Sommaire Chapitre suivant

Partager

88 commentaires pour "La couche métier : les entités"
Note moyenne : 3.75 / 4 (245 votes)
Pseudo Commentaire
Hors ligne oukacha # Posté le 13/04/2012 à 02:34:29

salut, 1000 merci pour ce tuto très interessent. j ai une question si tu me le permet

quand est il des de l'utilisation des classes abstraites dans symfony/doctrine car j ai chercher sans rien trouver sur la toile. je m explique. supposons que j ai une table voiture et camion qui tout deux héritent de la table abstraite véhicule, comment je fais à quelque moment je le spécifie ?? de plus si je désire changer le type d'un attribut de mon objet voiture, me suffit il de le modifier dans la classe voiture toute seul?

et enfin: pour mon application qui va m aider à suivre les différents processus lié à l 'achat vente de véhicule j 'ai créer 7 bundle:
- voiture( pour gérer les catalogues de produits)
- contact(clients, employé, fournisseur etc)
- Responsable Achat
- Commercial( interface de vente, saisie des commandes etc)
- Gérant( pour tout gérer)
- Site (pour présenter les produit)
- comptabilité (pour les données comptables)

à fin de valider ma compréhension de la notion de bundle, pourrais tu stp me dire si tout cela te semble cohérent??
et 1000000 merci
Hors ligne ovh # Posté le 25/04/2012 à 16:24:56

Salut et bravo pour ce tuto très complet :)

Je me permets de revenir sur la définition des attributs par défaut. Les initialiser dans le constructeur ne fonctionne que quand on crée une nouvelle entité en faisant
Code : PHP
1
$toto = new Toto;


Si on récupère l'entité depuis une requête, le constructeur n'est jamais appelé.

Je n'ai rien à voir avec www.ovh.com
 
Hors ligne xloulouxhas # Posté le 01/05/2012 à 15:23:53 Message supprimé pour le motif suivant : Merci de poster vos problèmes sur le forum, pas ici..

Avis : Décevant

bonjour , j ai un probleme , dans mes entites doctrine quand je creer mes entites et que je met des raltions ou bien j ajoute un nouveau champs a main , et je met la commande php app/console doctrine:schema:update --dump-sql ben ma bdd ne se met pas a jour les champs ke j ajoute a main ne se mettent pas a jour et les relations non plus
Hors ligne zero_un # Posté le 02/05/2012 à 23:57:54

Avis : Très bon

Bon tuto ...
Hors ligne balidhi # Posté le 09/05/2012 à 19:38:14 Message supprimé pour le motif suivant : Merci de poster vos problèmes sur le forum, pas ici..

salut et merci pour ce tutorial
-J'ai un probleme,quand je tape la commandes:
php app/console generate:doctrine:entity
il m'affiche ça : Could not open input file:app/console
-Quesque je dois faire,merci

Voir tous les commentaires