06 - Symfony : Créer ta première entité avec Doctrine
Objectifs
- ✅ Installer Doctrine
- ✅ Créer une entité ”category”
- ✅ Découvrir Doctrine en mode CLI
- ✅ Découvrir Doctrine Migration
Pré-requis
Avoir terminé la quête
Introduction
User stories 📋
en tant que visiteur, je souhaite récupérer les données d'une série
en tant que visiteur, je souhaite récupérer la liste des séries
en tant que contributeur, je souhaite ajouter une série
In progress 🤸
en tant que visiteur, je souhaite visualiser la fiche d'une série
Doing 🏗️
création des entités Program et Category
Done ✅
création de la route /program/{id}
Installation des outils de gestion d'assets
Cette quête va te faire découvrir comment créer une entité et mettre à jour ta base de données grâce à celle-ci, via Doctrine.
Sommaire
Qu'est-ce que Doctrine et pourquoi l'utiliser ?
Doctrine est ce que l’on appelle un ORM (Object-Relational Mapping). Concrètement, cela te permet de manipuler directement des objets PHP au lieu de manipuler la base de données avec des requêtes SQL. Toutes les modifications que tu feras sur tes objets seront enregistrées dans la base de données de ton application.
Il n'est pas obligatoire d'utiliser un ORM, cela est même un sujet de débat régulier entre développeurs. En effet, un ORM est souvent lourd et complexe à mettre en place. Cependant, s'il est maîtrisé, il apporte beaucoup en matière de logique métier. Le développeur aura un code beaucoup plus axé sur le métier que sur la technique, plus parlant, mieux organisé.
Dans l'écosystème PHP, Doctrine n'est pas le seul ORM, mais c'est le plus utilisé. Il en existe d'autres comme Propel, Eloquent, etc.
Installer Doctrine et configurer l'accès à la base de données
Cette étape est assez simple et rapide. Comme pour les quêtes précédentes, si tu as utilisé l'option
--webapp quand tu as installé ton projet symfony, tu n'as rien à faire !
Si par contre tu étais parti d'un skeleton de base, il te faudrait installer Doctrine via la commande : composer require symfony/orm-pack, ainsi que le bundle permettant d'utiliser les assistants en ligne de commande : composer require symfony/maker-bundle --dev
Avant tout, il te faut paramétrer la connexion à ta base de données, afin que Doctrine puisse s'y connecter.
Symfony gère ce genre de paramètres sensibles dans des variables d'environnement globales au système pour un projet en production.
En local sur ta machine de développement, cela va se passer dans le fichier .env.local à la racine de ton projet.
Tu peux le chercher longtemps, il n’existe pas. En revanche, tu peux dupliquer le fichier déjà existant .env à la racine de ton projet pour t’en servir de modèle de base et le configurer correctement sous le nom de .env.local.
# .env.local
DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4"
Attention, par défaut Symfony propose une ligne préconfigurée pour le SGBD PostgreSQL.
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=14&charset=utf8".
Puisque tu utilises MySQL, tu dois utiliser à la place la ligne commentée commençant bien par mysql. Pense à commenter la ligne pour PostgreSQL.
Dans un fichier .env, commenter ou décommenter une ligne se fait grâce au caractère # en début de ligne.
Maintenant modifie la ligne MySQL ainsi
DATABASE_URL="mysql://your_username:your_pwd@127.0.0.1:3306/wild-series?serverVersion=8&charset=utf8mb4"
Un peu comme tu as pu le voir avec PDO, Symfony utilise une sorte de DSN sous forme de chaîne de caractères avec toutes les informations nécessaires pour se connecter :
-
Type de base de données : mysql
-
Utilisateur : your_username
-
Mot de passe : your_pwd
-
Hôte : 127.0.0.1 (ou localhost)
-
Port : 3306 (port standard de MySql)
-
Base de données : wild-series
Remarque : 127.0.0.1 ou localhost indique que la base de données est installée sur le poste qui exécute le script PHP, mais cela n'est pas forcément le cas. La base de données peut être sur un serveur dédié. Dans ce cas, il faut bien entendu saisir l'IP du serveur et s'assurer que le serveur autorise les connexions dites "externes" ou "distantes"...
Une fois cela fait, il ne te reste plus qu'à vérifier si tout est bien "câblé" en créant la base de données, via la commande :
symfony console doctrine:database:create
# ou symfony console d:d:c
La console t'indique en retour quelque chose comme ceci :
Created database `wild_series` for connection named default
Créer ta première Entité
Une entité est une simple classe représentant un objet métier. La plupart du temps, une entité représente une table de la base de données, et chaque propriété de l'entité, un champ de la table. Mais ça n'est pas obligatoirement le cas.
Il y a plusieurs méthodes pour créer une entité. Tu peux décider de tout créer à la main. Cette méthode marche très bien si tu maîtrises toutes les subtilités de Doctrine. Malgré tout, elle est un peu longue. Alors Symfony a intégré quelques outils en ligne de commande (CLI) pour te faire gagner du temps.
symfony console make:entity
`Class name of the entity to create or update (e.g. FierceKangaroo)`:
> Category
`Add the ability to broadcast entity updates using Symfony UX Turbo? (yes/no)` [no]:
> no
created: src/Entity/Category.php
created: src/Repository/CategoryRepository.php
Entity generated! Now let's add some fields!
You can always add more fields later manually or by re-running this command.
`New property name (press <return> to stop adding fields)`:
> name
`Field type (enter ? to see all types)` [string]:
>
`Field length` [255]:
>
`Can this field be null in the database (nullable) (yes/no)` [no]:
>
updated: src/Entity/Category.php
`Add another property? Enter the property name (or press <return> to stop adding fields)`:
>
`Success!`
Next: When you're ready, create a migration with `symfony console make:migration`
Symfony t'indique tout ce que tu dois remplir :
-
Nom de l'entité : dans notre exemple
Category -
Broadcast avec Symfony UX Turbo : nous en parlerons plus tard, ignore cette question pour le moment en répondant non
-
Propriétés de l'entité : une seule dans notre exemple (
name), mais tu peux en ajouter autant que tu le souhaites. -
Type des propriétés, ainsi que les spécificités du type. Dans notre cas, le type est string, Symfony nous demande la longueur de la chaîne (255 par défaut).
-
Les propriétés sont elles nullables ou non ? C’est-à-dire, est-ce que ce champ dans la table a le droit d’être vide ?
Avant de créer une entité, réfléchis bien à choisir un nom cohérent, en anglais et surtout au singulier. Par exemple, pour le Wild Séries, lorsque tu devras créer l’entité correspondant aux séries, tu vas devoir utiliser le nom Program (qui est un synonyme de series télé en anglais).
Pourquoi ? Et bien, le fait est que Serie en anglais est invariable ! Il aurait été également envisageable d’utiliser “Show”, sauf que ce terme, est un mot clé reservé en SQL, souviens toi : SHOW TABLES. Cela aurait provoqué des erreurs à l’execution de tes requetes SQL.
Le plus simple alors, est de trouver une alternative, compréhensible par tous, et utilisable par les outils : Program est au final le terme le plus approprié.
Regarde la classe générée.
<?php
namespace App\Entity;
use App\Repository\CategoryRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
class Category
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $name = null;
/* getters / setters */
}
Il est important d’identifier les attributs générés par Symfony (là encore, avant Symfony 6, des annotations étaient utilisées au lieu d'attributs, et il est possible que la documentation officielle ne soit pas encore à jour partout). Ils commencent tous par #[ORM\], cela fait référence à une classe (et un alias). Il ne faut pas oublier le use nécessaire si tu crées une entité manuellement.
use Doctrine\ORM\Mapping as ORM;
Ensuite, toutes les propriétés reliées à une colonne de la table ont au minimum l'attribut ORM\Column avec certains paramètres le cas échéant :
#[ORM\Column]
Puis certaines propriétés sont spécifiques, comme l'id, qui est dans notre cas une clé primaire représentée par un integer auto-incrémenté.
#[ORM\Id]
Indique que la propriété sera une clé primaire.
#[ORM\GeneratedValue]
Indique que la valeur sera auto-incrémentée. (Augmentera de 1 à chaque nouvel enregistrement)
Les types des propriétés :

Attention, ces types sont des types PHP, le type correspondant côté base de données dépend du système de gestion de la base de données utilisée (MySQL, PostgreSQL, SQL Server, Oracle...).
Symfony ne te l'indique pas dans sa console, mais chaque fois que tu génères une entité en ligne de commande, il crée spontanément une propriété clé primaire nommée id. Tu n'as donc pas à spécifier le champ "id" quand tu génères l'entité via la console de Symfony. Tu peux le constater dans la classe générée précédemment.
Une fois toutes ces informations renseignées, Symfony t'indique que la classe est créée avec succès, mais la base de données n'est pas encore à jour.
Mettre à jour la base de données
Encore une fois, Symfony t'indique quoi faire :
Next: When you're ready, create a migration with `symfony console make:migration`
Lance la commande :
symfony console make:migration

Là, il faut bien comprendre ce qu'a fait Symfony.
Doctrine Migration est un outil qui permet de générer des scripts permettant de déployer la structure de sa base de données. Tu pourras lire la documentation plus en détail si nécessaire, mais l'idée à retenir est que Doctrine Migration génère une classe avec un numéro de version qui correspond à un état de la base de données.
Dans cette classe, tu pourras retrouver deux méthodes :
-
Une contenant un script pour monter de version ta base de données
-
Une autre contenant un script pour revenir sur la version précédente. Très important, si jamais il y avait un problème lors du déploiement.
En gros, pour chaque ordre SQL, Doctrine Migration va générer son ordre SQL inverse.
Exemple (CREATE vs DROP) :
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20210805173740 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE category (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE category');
}
}
Ces scripts sont manipulables via des commandes intégrées dans la console Symfony, et ils iront s'enregistrer dans le dossier /migrations.
À ce stade, les scripts existent mais la base de données reste inchangée.
Pour exécuter cette migration (et réellement impacter le schéma de la base de données), il suffit de lancer la commande :
symfony console doctrine:migrations:migrate
# ou symfony console d:m:m

Note que si tu relances la commande symfony console make:migration avant d’avoir fait symfony console doctrine:migrations:migrate, Symfony va comparer l’état actuel de la base de données avec l’état actuel des entités, sans tenir compte de la classe migration que tu viens de créer. Tu vas alors te retrouver avec 2 classes de migrations, comportant les mêmes requetes SQL. Et forcément, comme tu t’en doutes, lorsque tu décideras enfin d’éxecuter symfony console doctrine:migrations:migrate, ta console te renverra une erreur comme quoi la modification n’est pas possible.
Doctrine CLI
Pour rappel, Symfony te propose beaucoup d'outils en mode CLI. Pour avoir la liste complète, il te suffit de lancer la console sans argument :
symfony console
Les commandes qui vont nous intéresser dans cette quête sont celles concernant Doctrine. Toutes ne sont pas utiles au quotidien, mais les suivantes peuvent l'être :
doctrine:database:createdoctrine:database:dropmake:entity --regeneratedoctrine:mapping:infodoctrine:schema:createdoctrine:schema:dropdoctrine:schema:updatedoctrine:schema:validatedoctrine:migrations:migratedoctrine:migrations:status
Certaines commandes parlent d'elles-mêmes. Attardons-nous sur les autres.
Dans tous les cas, lorsque tu ne sais pas ou que tu as un doute sur les options possibles liées à une commande, tu peux toujours utiliser l'option --help. Par exemple : symfony console doctrine:database:create --help
Mise à jour d’une entité
symfony console make:entity --regenerate

Cette commande permet, à partir d'une entité comportant uniquement des propriétés, de générer les getters / setters ainsi que certaines autres méthodes utiles pour les mappings plus complexes (que tu verras dans une future quête).
Attention à garder ta classe "propre". En effet, si tu supprimes des propriétés, la commande ne va pas supprimer les getters / setters automatiquement. Elle ne fait que de l'ajout.
Détection des entités
symfony console doctrine:mapping:info

Cette commande t'indique toutes les entités trouvées par Doctrine dans ton application.
Validation des entités
symfony console doctrine:schema:validate

Valide si la définition des différentes entités de l’application sont cohérentes. Cela peut mettre en avant des problèmes de relation entre entités (souvent à cause de problèmes de nommage).
Garde bien cette commande en tête pour une future quête où tu feras des relations entre entités.
Contrôle des migrations
symfony console doctrine:migrations:status

T'indique toutes les informations concernant le paramétrage de Doctrine Migration, ainsi que l'état de la base de données.
Tu peux aussi exécuter des migrations de manière unitaire, c’est-à-dire en lancer une seule à la fois avec la commande doctrine:migrations:execute. En exécutant tes migrations de manière unitaire tu peux choisir la méthode que tu vas lancer de la migration de ton choix: up ou down . C’est très utile si tu exécutes une migration et que tu te rends compte qu’elle est fausse, dans ce cas il te faudra exécuter la méthode down pour revenir à l’état antérieur :
symfony console doctrine:migrations:execute 'DoctrineMigrations\<nom_de_classe_de_ta_migration>' --down
Pour exécuter une migration dans le sens inverse
symfony console doctrine:migrations:execute 'DoctrineMigrations\<nom_de_classe_de_ta_migration>' --up
💪 Challenge
Crée les entités Category et Program (sans liaison)
Crée deux entités Category et Program.
Category- name : string (Obligatoire)
Program- title : string (Obligatoire)
- synopsis : text (Obligatoire)
- poster : string (Facultative)
Pour le moment ces deux entités ne sont pas liées. Tu dois également mettre à jour ta base de données en conséquence.
Critères de validation
- Lance la commande
doctrine:mapping:info, le résultat affiche bien les deux entitésCategoryetProgram. - Lance la commande
doctrine:schema:validate, le résultat affiche bien OK pour le mapping ET la base de données. - Ton code est disponible sur un repository GitHub
Ressources partagées sur cette quête
- Tuto français pour créer son entitéhttps://www.youtube.com/watch?v=_XUOfuUBMqM
- bonnes explications sur la céation et manipulation des entitéshttps://writecode.fr/tutoriel/les-entites
- Utilitaire pour visualiser son travail effectué sur la BDD, a telecharger, mettre le fichier en public, renommer en adminer et ouvrir dans un onglet http://localhost:8000/adminer.phphttps://www.adminer.org/#download