Apprendre Symfony 6 : Manipulation des Entités
Le gestionnaire d'entité (entity manager) et les repositories permettent d'interagir (écriture / lecture) avec les données d'une application.
Entity Manager
L’Entity Manager ou « gestionnaire d’entité » est l’objet le plus important de Doctrine. Il synchronise les données stockées en base avec nos objets PHP. On l’exploite depuis les méthodes de nos contrôleurs.
C’est grâce à lui que nous allons pouvoir effectuer les opérations élémentaires d’un CRUD :
- Lecture
- Création
- Modification
- Suppression
On peut appeler le gestionnaire d’entités en important la classe EntityManagerInterface
dans le contrôleur.
use Doctrine\ORM\EntityManagerInterface;
Ensuite, typer un paramètre (souvent nommé $em
pour « Entity Manager ») d’une méthode de contrôleur avec la classe EntityManagerInterface
permet d’y injecter notre gestionnaire d’entité à l’intérieur.
On appelle ceci « autowiring ».
#[Route('/', name: 'home')]
public function home(EntityManagerInterface $em): Response {
// Je peux utiliser ici l'objet $em
}
Implémenter un CRUD
Récupération (READ
)
Repositories
Souvenez vous, lorsque nous avions tapé la commande symfony console make:entity
pour la première fois, cela avait généré deux fichiers :
- L’entité
- Le repository associé
Ces repositories sont des classes Doctrine que l’on va utiliser pour lire nos données (sous forme d’objets et de collections). On les utilisera systématiquement pour aller chercher des informations en base de données.
Le repository associé à une entité Article
ressemblerait ainsi à cela :
namespace App\Repository;
use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Article>
*
* @method Article|null find($id, $lockMode = null, $lockVersion = null)
* @method Article|null findOneBy(array $criteria, array $orderBy = null)
* @method Article[] findAll()
* @method Article[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ArticleRepository extends ServiceEntityRepository {
public function __construct(ManagerRegistry $registry) {
parent::__construct($registry, Article::class);
}
// Des exemples de methodes sur-mesure
}
C’est à travers un ensemble de méthodes prédéfinies que les repositories permettent de récupérer des données en base. Il sera également possible d’y intégrer ses propres méthodes personnalisées.
Dans nos méthodes de contrôleur, on peut appeler nos repositories via le gestionnaire d’entité, accessible via notre objet $em
.
Sur ce dernier, on peut appeler la méthode getRepository()
en lui transmettant en argument l’entité Article::class
pour laquelle nous aimerions obtenir le repository.
#[Route('/', name: 'app_index')]
public function home(EntityManagerInterface $em) {
$repository = $em->getRepository(Article::class);
}
Doctrine sait qu’il doit instancier le repository ArticleRepository
, car cela est spécifié dans l’entité avec l’attribut PHP #[ORM\Entity(repositoryClass: ArticleRepository::class)]
.
Avec la classe {entite}Repository
Il est également possible de sauter une étape en injectant directement le repository {entite}Repository
.
use App\Repository\ArticleRepository;
#[Route('/', name: 'app_index')]
public function home(ArticleRepository $articleRepository): Response {
// Je peux utiliser ici l'objet $articleRepository
}
4 méthodes d’accès
Par défaut, nos repositories posséderont tous 4 méthodes d’accès à nos données, nous permettant de couvrir la plupart de nos besoins.
Chacune de ces méthodes va permettre de requêter les données propres à une entité.
findAll()
La méthode findAll()
permet de récupérer toutes les données en base de données. Le résultat sera livré sous forme de collection.
// Récupération de tous les articles
$articles = $repository->findAll();
findBy()
La méthode findBy()
permet de récupérer tous les résultats, correspondant à un ou plusieurs critère(s). Le résultat sera livré sous forme de collection.
// Récupération de tous les articles publiés
$articles = $repository->findBy(
['published' => true],
['createdAt' => 'DESC'],
10,
20
);
Paramètres :
$criteria
(obligatoire) : un tableau associatif de critère(s) pour filtrer les résultats.$orderBy
(optionnel) : un tableau associatif de critère(s) pour ordonner les résultats.$limit
(optionnel) : un nombre maximal de résultats à récupérer.$offset
(optionnel) : un nombre spécifiant un décalage pour le curseur de sélection de résultats. Très utile pour la pagination.
findOneBy()
La méthode findOneBy()
permet de récupérer un résultat, correspondant à un ou plusieurs critère(s). Le résultat sera livré sous forme d’objet.
// Récupération de l'article ayant le slug `extension-io-signification`
$article = $repository->findOneBy(['slug' => 'extension-io-signification']);
Paramètres :
$criteria
(obligatoire) : un tableau associatif de critère(s) pour filtrer les résultats.$orderBy
(optionnel) : un tableau associatif de critères pour ordonner les résultats.
find()
La méthode find()
permet de récupérer un résultat, correspondant à l’identifiant $id
passé en paramètre. Le résultat sera livré sous forme d’objet.
// Récupération de l'article ayant l'identifiant 64
$article = $repository->find(64);
La récupération d’objets depuis la base de données est omniprésente dans le développement d’un site Symfony. Voici un exemple de méthode de contrôleur chargée de récupérer les informations d’un article de blog, afin de les afficher sur une page :
#[Route('/blog/{slug}', name: 'article_show')]
public function showArticle(ArticleRepository $articleRepository, string $slug): Response {
return $this->render(
'article/show.html.twig', [
'article' => $articleRepository->findOneBy(['slug' => $slug])
]
);
}
Requêtes personnalisées
Si les 4 méthodes détaillées ci-dessus permettent de faire un grand nombre de requêtes, elles ne suffiront pas toujours lorsque vous souhaiterez faire des requêtes complexes.
Pour cela, vous aurez toujours la possibilité d’écrire vos requêtes personnalisées dans votre repository.
Enregistrement (CREATE
)
Pour enregistrer un nouvel objet, il faut avant tout instancier une entité, définir ses valeurs pour ses attributs, puis utiliser les méthodes persist()
et flush()
.
#[Route('/admin/articles/ajouter', name: 'admin_article_add')]
public function addArticle(EntityManagerInterface $em): Response {
$article = new Article();
$article->setTitle("Apprendre le HTML");
$article->setContent("Contenu du cours de HTML...");
$em->persist($article);
$em->flush();
// ...
}
- Récupération de l’Entity Manager avec
EntityManagerInterface $em
. - Instanciation de l’objet à enregistrer (ici vide) avec
$article = new Article()
. Cet objet est mappé sur l’entitéArticle
. Ensuite, définition manuelle de ses attributs, mais vous verrez par la suite qu’il sera plus fréquent de récupérer un objet déjà tout prêt, via la saisie d’un formulaire par l’utilisateur. - Persistance de l’objet avec
$em->persist($article)
. On signale ainsi à Doctrine de se préparer à enregistrer cet objet en base de données. Concrètement, rien n’a encore été modifié en base de données. - Application des modifications en base de données avec
$em->flush()
. On demande à Doctrine de mettre à jour la base à partir des objets signalés. Tant qu’elle n’est pas appelée, rien n’est modifié en base. Ici, comme elle est précédée de la méthodepersist()
, elle va exécuter les instructions d’insertion en base de données qui ont été demandées.
Modification (UPDATE
)
Le processus de modification d’un objet est très proche de celui de l’enregistrement, à la seule différence que nous n’allons pas instancier un nouvel objet mais en récupérer un existant.
Nous n’aurons ainsi pas besoin de persister avec persist()
l’objet avant l’enregistrement car il existe déjà dans notre base de données.
#[Route('/admin/articles/{id}/modifier', name: 'admin_article_edit')]
public function editArticle(int $id, EntityManagerInterface $em): Response {
$repository = $em->getRepository(Article::class);
$article = $repository->find($id);
$article->setTitle("Apprendre le HTML 5");
$em->flush();
}
- Récupération de l’Entity Manager avec
EntityManagerInterface $em
. - Récupération de l’objet
$article
- Modification de la propriété title avec
setTitle()
- Application des modifications en base de données avec
$em->flush()
.
Suppression (DELETE
)
Le processus de suppression d’un objet est très proche de celui de l’enregistrement, à la seule différence que nous n’allons pas créer un nouvel objet mais en récupérer un existant.
Nous aurons ensuite besoin de prévenir Doctrine de la suppression avec remove()
avant notre flush.
#[Route('/admin/articles/{id}/supprimer', name: 'admin_article_delete')]
public function deleteArticle(int $id, EntityManagerInterface $em): Response {
$repository = $em->getRepository(Article::class);
$article = $repository->find($id);
$em->remove($article);
$em->flush();
}
- Récupération de l’Entity Manager avec
EntityManagerInterface $em
. - Récupération de l’objet
$article
- Préparation à la suppression de l’objet avec
$em->remove()
. On signale ainsi à Doctrine de se préparer à supprimer cet objet de la base de données. Concrètement, rien n’a encore été modifié en base de données. - Application des modifications en base de données avec
$em->flush()
.
Vous savez désormais créer, modifier ou encore supprimer des données en base de données. Néanmoins, on ne se contentera évidemment pas de saisir des valeurs « en dur » via nos setters comme dans les exemples précédents. On passera par des formulaires. On en parle au chapitre suivant.