Les entités, cœur de la mémoire de votre application
Un article consacré à la persistance de vos données, ou comment vous aider à les garder bien au chaud
.
Une entité n’est guère plus qu’une classe PHP avec des commentaires. Oui, mais quels commentaires ! On les appelle couramment des annotations et ils permettent tout bonnement d’indiquer à Doctrine les données à stocker, comment, et leur lien éventuel avec d’autres.
Et si tu nous expliquais comment ça fonctionne ?
Mais bien sûr
. Avant toute chose, prenons un exemple simple :
Nous désirons créer un site sur les jeux vidéos, permettant de les recenser, les tester, recevoir des notes et des commentaires des utilisateurs, etc…
Nous allons simplement réduire cet exemple à des jeux videos et des notes (sur 20, pour ne pas être originaux
).
Dans un premier temps, il nous faut définir ce que vont contenir ces deux entités. Ici, c’est très simple. Un jeu vidéo va posséder un titre, une description et une date de sortie (oui, tout cela est réduit au strict minimum). Un note va concerner un jeu et contiendra simplement le nombre entier.
Symfony vient avec de nombreux avantages, mais le générateur d’entités reste peut être le meilleur d’entre eux. Il nous prépare efficacement le terrain.
Ouvrez votre console favorite, placez-vous dans le dossier de votre projet (c’est plus pratique
) et entrez la commande suivante :
php ./app/console doctrine:generate:entity
Le nom court de votre entité vous est demandé. Qu’est-ce que c’est ? Eh bien tout « simplement » le nom de votre bundle, suivi du nom de votre entité. Dans notre exemple, le bundle s’appellerait TestJeuBundle:Jeu pour notre entité Jeu.
Le tout est de bien comprendre que le fichier créé va être envoyé dans le dossier src/Test/JeuBundle/Entity et se nommera Jeu.php.
On nous demande alors le format de configuration. Je vous recommande grandement les annotations (choix par défaut) et c’est le cas que je traiterai.
Vient maintenant le moment d’entrer les noms de champs.
Nous voulons :
- Un texte (court) pour le titre. On va donc choisir le type string, puis 128 caractères (c’est arbitraire, adaptez à vos besoin
) - Un texte long pour la description. Les types text et longtext sont donc parfaitement adaptés
- La date de sortie sera…. Une date. Si ça ce n’est pas enfoncer des portes ouvertes
.
Pour cesser d’ajouter des champs, laissez simplement le champs vide et appuyer sur « entrée ». On nous demande maintenant si l’on souhaite créer un repository. Je vous expliquerai de quoi il retourne un peu plus tard, mais je vous conseille d’en créer une pour ce cas-là.
Il ne reste alors plus qu’à valider la génération du code.
Vous remarquerez que les deux fichiers (Jeu.php et JeuRepository.php) sont bien créés. Jetons un oeil à leur contenu.
<?php
// fichier src/Test/JeuBundle/Entity/Jeu.php
namespace Test\JeuBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Dreeckan\ForumBundle\Entity\Jeu
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Test\JeuBundle\Entity\JeuRepository")
*/
class Jeu
{
/**
* @var integer $id
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string $titre
*
* @ORM\Column(name="titre", type="string", length=128)
*/
private $titre;
/**
* @var text $description
*
* @ORM\Column(name="description", type="text")
*/
private $description;
/**
* @var date $date
*
* @ORM\Column(name="date", type="date")
*/
private $date;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set titre
*
* @param string $titre
* @return Jeu
*/
public function setTitre($titre)
{
$this->titre = $titre;
return $this;
}
/**
* Get titre
*
* @return string
*/
public function getTitre()
{
return $this->titre;
}
/**
* Set description
*
* @param text $description
* @return Jeu
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* @return text
*/
public function getDescription()
{
return $this->description;
}
/**
* Set date
*
* @param date $date
* @return Jeu
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* @return date
*/
public function getDate()
{
return $this->date;
}
}
Vous remarquerez que propriété, getters et setters ont été générés avec les commentaires classiques (type de retours et de paramètres). Vous noterez aussi la présence de commentaire commençant pas @ORM. Ce sont eux qui informent Doctrine de la nature des données à persister. Il retient ainsi toutes les informations qui nous intéressent et va pouvoir créer la table correspondante dès que nous le lui demanderons.
Le fichier repository ne contient pas grand chose. J’y reviendrai donc en détail plus tard, quand nous aurons quelques éléments à lui demander
.
Créer notre entité Note sera encore plus simple, il nous suffit de répéter l’opération, mais en ne créant qu’un seul champ (note, de type smallint ou integer). J’aurais tendance à ne pas créer de repository pour celle-ci, mais j’y reviendrai.
Si vous avez jeté un oeil au contenu de votre base de données, vous remarquerez qu’il n’y a strictement rien de nouveau. C’est normal, nous n’avons pas encore demandé à Doctrine de créer nos table !
Deux commandes sont possibles :
php ./app/console doctrine:schema:create
si vous qu’une base de données vide (ce qui devrait être le cas à l’heure actuelle).
php ./app/console doctrine:schema:update --force
pour mettre à jour votre base de données avec les modifications faites sur vos entités.
Les relations entre les entités, toute une histoire
Vous aurez remarqué que notre entité de Note n’est pas reliée à notre entité de Jeu. C’est bien normal, puisque nous n’avons dit nulle part à Doctrine que c’était le cas. Le générateur ne permet pas de définir de relations, hélas (ou alors, je ne suis pas au courant, ce qui m’embête grandement ! Je compte sur vous pour me l’annoncer
).
Il va donc falloir définir quel type de relation existe entre ces deux entités. Un jeu peut-il avoir plusieurs notes ? Une seule ? Partons du principe que chaque utilisateur peut mettre une note. Un Jeu peut donc être relié à plusieurs notes, mais une note ne peut être lié qu’à un jeu. La manière de le dire est peut être étrange, mais elle reflète le type de relation entre nos deux entités.
Doctrine permet 3 types de relations :
- OneToOne : une entité A ne peut être lié qu’à une seule entité B et vice versa.
- ManyToOne : une entité A peut être liée à plusieurs entités B, mais une entité B ne peut être associée qu’à une seule A. C’est notre cas ici.
- ManyToMany : Plusieurs entités A peuvent être liées à plusieurs entités B et vice versa.
Dans la manière de le dire, vous comprendrez que ces relations peuvent fonctionner dans deux sens, et c’est bien ce que nous permet Doctrine !
Voyons comment les définir dans notre entité de note :
Tout d’abord, il nous faut créer un nouvel attribut à notre classe Note :
private $jeu;
Il va falloir ensuite dire à Doctrine qu’il s’agit d’une information à conserver et que $jeu va être un objet de type… Jeu ! Pour ça, on utilise toujours nos annotations :
/** * @ORM\ManyToOne(targetEntity="Test\JeuBundle\Entity\Jeu", inversedBy="notes") */ private $jeu;
Nous avons définit le type de relation et il nous suffit d’indiquer l’entité que l’on souhaite relier. « inversedBy » nous permet d’indiquer le nom du champ de l’entité Jeu nous permettant d’accéder à nos différentes entités Note pour notre Jeu. Vous en verrez tout l’intérêt dans un paragraphe suivant.
Bien entendu, il nous faut également définir notre getter et notre setter.
/**
* Set jeu
*
* @param \Test\JeuBundle\Entity\Jeu $jeu
* @return Note
*/
public function setJeu(\Test\JeuBundle\Entity\Jeu $jeu)
{
$this->jeu = $jeu;
return $this;
}
/**
* Get jeu
*
* @return \Test\JeuBundle\Entity\Jeu
*/
public function getJeu()
{
return $this->jeu;
}
Je force le type du setter afin de m’assurer que l’on ne puisse lui passer quoi que ce soit d’autre. Simple sécurité en somme.
Définir la relation inverse
Il peut être intéressant de pouvoir récupérer nos différentes notes directement depuis notre entité Jeu, n’est-ce pas ?
Pour cela, il nous suffit d’indiquer à Doctrine que cette relation existe. Ajoutons pour cela un attribut à Jeu :
/**
* @ORM\OneToMany(targetEntity="Test\JeuBundle\Entity\Note", mappedBy="jeu")
*/
private $notes;
L’annotation OneToMany permet à Doctrine de savoir que l’on se trouve dans le cas d’une relation inverse, et il nous suffit alors d’indiquer dans quelle classe regardé et quel attribut définit la relation.
Il nous faut également indiquer un setter et un getter. Comme l’on veut pouvoir récupérer plusieurs notes, on utilisera un adder plutôt qu’un setter.
public function addNote(\Test\JeuBundle\Entity\Note $note)
{
$this->notes[] = $note;
return $this;
}
public function getNotes()
{
return $this->notes;
}
Je vous laisse ajouter les commentaires nécessaires, je suis d’humeur fainéante ce soir
.
Créer et récupérer nos entités, pour ne jamais rompre le lien
Parce qu’il est bien beau de stocker, il nous faut également pouvoir récupérer le contenu de notre base de données (c’est quand même le but…).
Créer et modifier une entité
Parce que Symfony a vraiment décidé de nous simplifier la vie, il est aisé de générer un CRUD (formulaires de création (Create), visualisation (Read), édition (Update) et suppression (Delete)) pour gérer nos entités. La commande suivante se charge de tout :
php ./app/console generate:doctrine:crud
Je pense que vous n’aurez pas besoin de moi pour comprendre le fonctionnement de ces formulaires
. N’hésitez pas à regarder le fonctionnement des éléments créés, c’est très instructif.
Retrouver nos entités, le rôle des repositories
Ces charmantes classes (oui, je vous assure que vous allez vite les adorer) vont vous permettre de récupérer vos entités soit selon des critères simples (changer l’ordre, restreindre à un certain nombre de données ou rechercher certaines données) et cela sans que vous n’ayez forcément besoin de les créer.
Voici un petit exemple :
Nous voulons récupérer tous nos jeux, du plus récent au plus ancien. Dans l’action de notre controller (imaginons une action listAction), on obtient ceci :
public function listAction()
{
$em = $this->getDoctrine()->getEntityManager();
$jeux = $em->getRepository('TestJeuBundle:Jeu')->findBy(array(), array('date' => 'desc'));
$this->render('TestJeuBundle:Page:list.html.twig', array('liste' => $jeux));
}
Le premier paramètre de la méthode FindBy est un tableau de critères. On aurait très bien pu lui demander la liste des jeux ayant pour nom Mario. Il suffisait de lui indiquer.
Pour une référence complète et des explications plus développées, je vous renvoie sur le tutoriel sur Symfony2 sur le Site du Zéro.







