Décrire la structure des données
Pour interagir avec la base de données depuis PHP, nous allons nous appuyer sur Doctrine, un ensemble de bibliothèques qui nous aide à gérer les bases de données : Doctrine DBAL (une couche d'abstraction de la base de données), Doctrine ORM (une librairie pour manipuler le contenu de notre base de données en utilisant des objets PHP), et Doctrine Migrations.
Configurer Doctrine ORM
Comment est-ce que Doctrine est au courant de notre connexion à la base de données ? La recette de Doctrine a ajouté un fichier de configuration qui contrôle son comportement : config/packages/doctrine.yaml
. Le paramètre principal est le DSN de la base de données, une chaîne contenant toutes les informations sur la connexion : identifiants, hôte, port, etc. Par défaut, Doctrine recherche une variable d'environnement DATABASE_URL
.
Presque tous les paquets installés sont configurés dans le répertoire config/packages/
. Les valeurs par défaut ont été choisies avec soin pour fonctionner avec la plupart des applications.
Comprendre les conventions des variables d'environnement de Symfony
Vous pouvez définir la variable DATABASE_URL
manuellement dans le fichier .env
ou .env.local
. En fait, grâce à la recette du paquet, vous verrez un exemple de variable DATABASE_URL
dans votre fichier .env
. Mais comme le port exposé par Docker vers PostgreSQL peut changer, c'est assez lourd. Il y a une meilleure solution.
Au lieu de coder en dur la variable DATABASE_URL
dans un fichier, nous pouvons préfixer toutes les commandes avec symfony
. Ceci détectera les services exécutés par Docker et/ou Platform.sh (lorsque le tunnel est ouvert) et définira automatiquement la variable d'environnement.
Docker Compose et Platform.sh fonctionnent parfaitement avec Symfony grâce à ces variables d'environnement.
Vérifiez toutes les variables d'environnement exposées en exécutant symfony var:export
:
1
$ symfony var:export
1 2
DATABASE_URL=postgres://app:!ChangeMe!@127.0.0.1:32781/app?sslmode=disable&charset=utf8
# ...
Vous rappelez-vous du nom du service database
utilisé dans les configurations Docker et Platform.sh ? Les noms des services sont utilisés comme préfixes pour définir des variables d'environnement telles que DATABASE_URL
. Si vos services sont nommés selon les conventions Symfony, aucune autre configuration n'est nécessaire.
Note
Les bases de données ne sont pas les seuls services qui bénéficient des conventions Symfony. Il en va de même pour Mailer, par exemple (via la variable d'environnement MAILER_DSN
).
Modifier la valeur par défaut de DATABASE_URL dans le fichier .env
Nous allons quand même changer le fichier .env
pour initialiser la variable DATABASE_URL
pour l'utilisation de PostgreSQL :
1 2 3 4 5 6 7 8 9 10 11
--- a/.env
+++ b/.env
@@ -26,7 +26,7 @@ APP_SECRET=ce2ae8138936039d22afb20f4596fe97
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
-DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
+DATABASE_URL="postgresql://127.0.0.1:5432/db?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ###
###> symfony/messenger ###
Pourquoi l'information doit-elle être dupliquée à deux endroits différents ? Parce que sur certaines plates-formes de Cloud, au moment de la compilation, l'URL de la base de données n'est peut-être pas encore connue mais Doctrine a besoin de connaître le moteur de la base de données pour initialiser sa configuration. Ainsi, l'hôte, le pseudo et le mot de passe n'ont pas vraiment d'importance.
Créer des classes d'entités
Une conférence peut être décrite en quelques propriétés :
- La ville où la conférence est organisée ;
- L'année de la conférence ;
- Une option international pour indiquer si la conférence est locale ou internationale (SymfonyLive vs SymfonyCon).
Le Maker Bundle peut nous aider à générer une classe (une classe Entity) qui représente une conférence.
Il est maintenant temps de générer l'entité Conférence
:
1
$ symfony console make:entity Conference
Cette commande est interactive : elle vous guidera dans le processus d'ajout de tous les champs dont vous avez besoin. Utilisez les réponses suivantes (la plupart d'entre elles sont les valeurs par défaut, vous pouvez donc appuyer sur la touche "Entrée" pour les utiliser) :
city
,string
,255
,no
;year
,string
,4
,no
;isInternational
,boolean
,no
.
Voici la sortie complète lors de l'exécution de la commande :
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 54
created: src/Entity/Conference.php
created: src/Repository/ConferenceRepository.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):
> city
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/Conference.php
Add another property? Enter the property name (or press <return> to stop adding fields):
> year
Field type (enter ? to see all types) [string]:
>
Field length [255]:
> 4
Can this field be null in the database (nullable) (yes/no) [no]:
>
updated: src/Entity/Conference.php
Add another property? Enter the property name (or press <return> to stop adding fields):
> isInternational
Field type (enter ? to see all types) [boolean]:
>
Can this field be null in the database (nullable) (yes/no) [no]:
>
updated: src/Entity/Conference.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 make:migration
La classe Conference
a été stockée sous le namespace App\Entity\
.
La commande a également généré une classe de repository Doctrine : App\Repository\ConferenceRepository
.
Le code généré ressemble à ce qui suit (seule une petite partie du fichier est retranscrite ici) :
Notez que la classe elle-même est une classe PHP sans aucune référence à Doctrine. Les attributs sont utilisés pour ajouter des métadonnées utiles à Doctrine afin de mapper la classe à sa table associée dans la base de données.
Doctrine a ajouté un attribut id
pour stocker la clé primaire de la ligne dans la table de la base de données. Cette clé (ORM\Id()
) est générée automatiquement (ORM\GeneratedValue()
) avec une stratégie qui dépend du moteur de base de données.
Maintenant, générez une classe d'entité pour les commentaires de la conférence :
1
$ symfony console make:entity Comment
Entrez les réponses suivantes :
author
,string
,255
,no
;text
,text
,no
;email
,string
,255
,no
;createdAt
,datetime_immutable
,no
.
Lier les entités
Les deux entités, Conference et Comment, devraient être liées l'une à l'autre. Une conférence peut avoir zéro commentaire ou plus, ce qui s'appelle une relation one-to-many.
Utilisez à nouveau la commande make:entity
pour ajouter cette relation à la classe Conference
:
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
Your entity already exists! So let's add some new fields!
New property name (press <return> to stop adding fields):
> comments
Field type (enter ? to see all types) [string]:
> OneToMany
What class should this entity be related to?:
> Comment
A new property will also be added to the Comment class...
New field name inside Comment [conference]:
>
Is the Comment.conference property allowed to be null (nullable)? (yes/no) [yes]:
> no
Do you want to activate orphanRemoval on your relationship?
A Comment is "orphaned" when it is removed from its related Conference.
e.g. $conference->removeComment($comment)
NOTE: If a Comment may *change* from one Conference to another, answer "no".
Do you want to automatically delete orphaned App\Entity\Comment objects (orphanRemoval)? (yes/no) [no]:
> yes
updated: src/Entity/Conference.php
updated: src/Entity/Comment.php
Note
Si vous entrez ?
comme réponse pour le type, vous obtiendrez tous les types pris en charge :
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
Main types
* string
* text
* boolean
* integer (or smallint, bigint)
* float
Relationships / Associations
* relation (a wizard will help you build the relation)
* ManyToOne
* OneToMany
* ManyToMany
* OneToOne
Array/Object Types
* array (or simple_array)
* json
* object
* binary
* blob
Date/Time Types
* datetime (or datetime_immutable)
* datetimetz (or datetimetz_immutable)
* date (or date_immutable)
* time (or time_immutable)
* dateinterval
Other Types
* decimal
* guid
* json_array
Jetez un coup d'oeil au diff complet entre les classes d'entités après l'ajout de la relation :
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
--- a/src/Entity/Comment.php
+++ b/src/Entity/Comment.php
@@ -36,6 +36,12 @@ class Comment
*/
private $createdAt;
+ #[ORM\ManyToOne(inversedBy: 'comments')]
+ #[ORM\JoinColumn(nullable: false)]
+ private Conference $conference;
+
public function getId(): ?int
{
return $this->id;
@@ -88,4 +94,16 @@ class Comment
return $this;
}
+
+ public function getConference(): ?Conference
+ {
+ return $this->conference;
+ }
+
+ public function setConference(?Conference $conference): self
+ {
+ $this->conference = $conference;
+
+ return $this;
+ }
}
--- a/src/Entity/Conference.php
+++ b/src/Entity/Conference.php
@@ -2,6 +2,8 @@
namespace App\Entity;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
@@ -31,6 +33,16 @@ class Conference
*/
private $isInternational;
+ #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: "conference", orphanRemoval: true)]
+ private $comments;
+
+ public function __construct()
+ {
+ $this->comments = new ArrayCollection();
+ }
+
public function getId(): ?int
{
return $this->id;
@@ -71,4 +83,35 @@ class Conference
return $this;
}
+
+ /**
+ * @return Collection<int, Comment>
+ */
+ public function getComments(): Collection
+ {
+ return $this->comments;
+ }
+
+ public function addComment(Comment $comment): self
+ {
+ if (!$this->comments->contains($comment)) {
+ $this->comments[] = $comment;
+ $comment->setConference($this);
+ }
+
+ return $this;
+ }
+
+ public function removeComment(Comment $comment): self
+ {
+ if ($this->comments->contains($comment)) {
+ $this->comments->removeElement($comment);
+ // set the owning side to null (unless already changed)
+ if ($comment->getConference() === $this) {
+ $comment->setConference(null);
+ }
+ }
+
+ return $this;
+ }
}
Tout ce dont vous avez besoin pour gérer la relation a été généré pour vous. Une fois généré, le code devient le vôtre ; n'hésitez pas à le personnaliser comme vous le souhaitez.
Ajouter d'autres propriétés
Je viens de réaliser que nous avons oublié d'ajouter une propriété sur l'entité Comment : une photo de la conférence peut être jointe afin d'illustrer un retour d'expérience.
Exécutez à nouveau make:entity
et ajoutez une propriété/colonne photoFilename
de type string
. Mais, comme l'ajout d'une photo est facultatif, permettez-lui d'être null
:
1
$ symfony console make:entity Comment
Migrer la base de données
La structure du projet est maintenant entièrement décrite par les deux classes générées.
Ensuite, nous devons créer les tables de base de données liées à ces entités PHP.
Doctrine Migrations est la solution idéale pour cela. Le paquet a déjà été installé dans le cadre de la dépendance orm
.
Une migration est une classe qui décrit les changements nécessaires pour mettre à jour un schéma de base de données, de son état actuel vers le nouveau, en fonction des attributs de l'entité. Comme la base de données est vide pour l'instant, la migration devrait consister en la création de deux tables.
Voyons ce que Doctrine génère :
1
$ symfony console make:migration
Notez le nom du fichier généré (un nom qui ressemble à migrations/Version20191019083640.php
) :
Mettre à jour la base de données locale
Vous pouvez maintenant exécuter la migration générée pour mettre à jour le schéma de la base de données locale :
1
$ symfony console doctrine:migrations:migrate
Le schéma de la base de données locale est à jour à présent, prêt à stocker des données.
Mettre à jour la base de données de production
Les étapes nécessaires à la migration de la base de données de production sont les mêmes que celles que vous connaissez déjà : commiter les changements et déployer.
Lors du déploiement du projet, Platform.sh met à jour le code, mais exécute également la migration de la base de données si nécessaire (il détecte si la commande doctrine:migrations:migrate
existe).
Aller plus loin
$ symfony console make:entity Conference