Logo Doctrine

Doctrine ORM

Un ORM puissant et flexible pour PHP qui simplifie la persistance de données et élimine la complexité des requêtes SQL.

Pour les non-initiés

Qu'est-ce que Doctrine ?

Imaginez que vous devez gérer une grande collection d'objets physiques (livres, meubles, outils...) et les ranger dans un entrepôt. Chaque fois que vous voulez ajouter, retrouver ou déplacer un objet, vous devez connaître précisément son emplacement et suivre des règles strictes de classement.

Doctrine agit comme un assistant intelligent qui s'occupe de toute cette organisation pour vous. Il vous permet de manipuler vos objets simplement, sans vous soucier de comment ils sont rangés physiquement dans l'entrepôt.

Pourquoi Doctrine est-il si utile ?

Simplicité d'utilisation

Les développeurs manipulent des objets familiers en PHP, sans écrire de requêtes SQL complexes.

Sécurité renforcée

Doctrine protège automatiquement contre les injections SQL et autres vulnérabilités courantes.

Indépendance du SGBD

Votre application peut fonctionner avec différentes bases de données (Icône MySQLMySQL, Icône PostgreSQLPostgreSQL, SQLite...) sans modifier votre code.

Performances optimisées

Doctrine gère intelligemment les requêtes et minimise les accès à la base de données pour plus de rapidité.

En résumé, Doctrine permet aux développeurs PHP de se concentrer sur la logique métier de leur application plutôt que sur les détails techniques de stockage des données. Il offre une approche plus intuitive, orientée objet, pour travailler avec des bases de données relationnelles.

Pour les développeurs

Fonctionnement technique

Doctrine est un ORM (Object-Relational Mapper) pour Icône PHPPHP qui implémente le pattern DataMapper. Il offre une couche d'abstraction puissante entre votre code PHP orienté objet et la base de données relationnelle.

Les concepts fondamentaux

Entités (Entities)

Les entités sont des classes PHP qui représentent des tables dans la base de données. Chaque propriété de l'entité correspond à une colonne de la table. Les relations entre entités (one-to-one, one-to-many, many-to-many) sont également déclarées dans ces classes.

Exemple d'entité Doctrine avec annotations
<?php namespace AppEntity; use DoctrineORMMapping as ORM; /** * @ORMEntity(repositoryClass="AppRepositoryProductRepository") * @ORMTable(name="products") */ class Product { /** * @ORMId * @ORMGeneratedValue * @ORMColumn(type="integer") */ private $id; /** * @ORMColumn(type="string", length=255) */ private $name; /** * @ORMColumn(type="text", nullable=true) */ private $description; /** * @ORMColumn(type="decimal", precision=10, scale=2) */ private $price; /** * @ORMColumn(type="datetime") */ private $createdAt; /** * @ORMManyToOne(targetEntity="Category", inversedBy="products") * @ORMJoinColumn(name="category_id", referencedColumnName="id", nullable=false) */ private $category; /** * @ORMOneToMany(targetEntity="ProductImage", mappedBy="product", cascade={"persist", "remove"}, orphanRemoval=true) */ private $images; public function __construct() { $this->createdAt = new \DateTime(); $this->images = new \Doctrine\Common\Collections\ArrayCollection(); } // Getters and setters... }

À partir de PHP 8, les attributs peuvent être utilisés à la place des annotations:

Exemple d'entité Doctrine avec PHP 8 Attributes
<?php namespace AppEntity; use AppRepositoryProductRepository; use DoctrineCommonCollectionsArrayCollection; use DoctrineCommonCollectionsCollection; use DoctrineORMMapping as ORM; #[ORM\Entity(repositoryClass: ProductRepository::class)] #[ORM\Table(name: 'products')] class Product { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(type: 'string', length: 255)] private string $name; #[ORM\Column(type: 'text', nullable: true)] private ?string $description = null; #[ORM\Column(type: 'decimal', precision: 10, scale: 2)] private string $price; #[ORM\Column(type: 'datetime')] private \DateTimeInterface $createdAt; #[ORM\ManyToOne(targetEntity: Category::class, inversedBy: 'products')] #[ORM\JoinColumn(name: 'category_id', referencedColumnName: 'id', nullable: false)] private Category $category; #[ORM\OneToMany(targetEntity: ProductImage::class, mappedBy: 'product', cascade: ['persist', 'remove'], orphanRemoval: true)] private Collection $images; public function __construct() { $this->createdAt = new \DateTime(); $this->images = new ArrayCollection(); } // Getters and setters... }

EntityManager

L'EntityManager est le cœur de Doctrine. Il gère le cycle de vie des entités, synchronise les objets avec la base de données et fournit une API pour effectuer des opérations CRUD (Create, Read, Update, Delete).

Opérations CRUD avec l'EntityManager
<?php // Exemple d'utilisation de l'EntityManager pour les opérations CRUD // Récupération de l'EntityManager $entityManager = $container->get('doctrine')->getManager(); // CREATE - Création et persistance d'une nouvelle entité $product = new Product(); $product->setName('Smartphone XYZ'); $product->setDescription('Un smartphone puissant avec de nombreuses fonctionnalités.'); $product->setPrice('499.99'); $product->setCategory($category); // Indique à Doctrine que l'entité doit être sauvegardée $entityManager->persist($product); // Exécute les requêtes SQL nécessaires $entityManager->flush(); // READ - Récupération d'entités avec le repository $productRepository = $entityManager->getRepository(Product::class); // Récupérer un produit par son ID $product = $productRepository->find(1); // Récupérer des produits selon des critères $products = $productRepository->findBy(['category' => $category], ['createdAt' => 'DESC'], 10); // Récupérer tous les produits $allProducts = $productRepository->findAll(); // UPDATE - Modification d'une entité existante $product = $productRepository->find(1); $product->setPrice('459.99'); $product->setDescription('Description mise à jour du produit.'); // Pas besoin de persist() pour les entités déjà gérées par Doctrine $entityManager->flush(); // DELETE - Suppression d'une entité $product = $productRepository->find(1); $entityManager->remove($product); $entityManager->flush();

Repository

Les repositories sont des classes qui encapsulent la logique nécessaire pour récupérer des entités de la base de données. Ils fournissent des méthodes personnalisées pour des requêtes spécifiques à une entité.

Exemple de Repository personnalisé
<?php namespace App\Repository; use App\Entity\Product; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; class ProductRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Product::class); } // Méthode personnalisée avec QueryBuilder public function findTrendingProducts(int $limit = 10): array { return $this->createQueryBuilder('p') ->select('p') ->join('p.orders', 'o') ->where('o.createdAt > :date') ->groupBy('p.id') ->orderBy('COUNT(o.id)', 'DESC') ->setParameter('date', new \DateTime('-30 days')) ->setMaxResults($limit) ->getQuery() ->getResult(); } // Méthode avec DQL native public function findProductsOnSale(string $categoryName = null): array { $dql = 'SELECT p FROM ' . Product::class . ' p WHERE p.discountPercentage > 0'; if ($categoryName) { $dql .= ' AND p.category.name = :categoryName'; } $dql .= ' ORDER BY p.discountPercentage DESC'; $query = $this->getEntityManager()->createQuery($dql); if ($categoryName) { $query->setParameter('categoryName', $categoryName); } return $query->getResult(); } // Méthode avec SQL natif public function findMostReviewedProducts(int $limit = 5): array { $conn = $this->getEntityManager()->getConnection(); $sql = ' SELECT p.id, p.name, p.price, COUNT(r.id) as review_count FROM products p JOIN reviews r ON r.product_id = p.id GROUP BY p.id ORDER BY review_count DESC LIMIT :limit '; $stmt = $conn->prepare($sql); $stmt->bindValue('limit', $limit, \PDO::PARAM_INT); $resultSet = $stmt->executeQuery(); return $resultSet->fetchAllAssociative(); } }

DQL (Doctrine Query Language)

DQL est un langage de requête inspiré du SQL mais orienté objet. Il vous permet de formuler des requêtes en utilisant les noms de classes et de propriétés plutôt que les noms de tables et de colonnes.

Exemples de requêtes DQL
<?php // Exemple de requêtes DQL (Doctrine Query Language) $entityManager = $container->get('doctrine')->getManager(); // Requête DQL simple $query = $entityManager->createQuery( 'SELECT p FROM App\Entity\Product p WHERE p.price > :price ORDER BY p.createdAt DESC' ); $query->setParameter('price', '100.00'); $expensiveProducts = $query->getResult(); // Requête avec jointure $query = $entityManager->createQuery( 'SELECT p, c FROM App\Entity\Product p JOIN p.category c WHERE c.name = :categoryName AND p.price < :maxPrice' ); $query->setParameters([ 'categoryName' => 'Électronique', 'maxPrice' => '500.00' ]); $products = $query->getResult(); // Requête d'agrégation $query = $entityManager->createQuery( 'SELECT c.name as categoryName, AVG(p.price) as averagePrice, COUNT(p.id) as productCount FROM App\Entity\Product p JOIN p.category c GROUP BY c.id HAVING COUNT(p.id) > 5 ORDER BY averagePrice DESC' ); $categoryStats = $query->getResult(); // Requête de mise à jour $query = $entityManager->createQuery( 'UPDATE App\Entity\Product p SET p.price = p.price * 0.9 WHERE p.category = :category' ); $query->setParameter('category', $saleCategory); $updatedRows = $query->execute(); // Requête de suppression $query = $entityManager->createQuery( 'DELETE FROM App\Entity\Product p WHERE p.createdAt < :date' ); $query->setParameter('date', new \DateTime('-1 year')); $deletedRows = $query->execute();

QueryBuilder

Le QueryBuilder offre une API fluide pour construire des requêtes complexes de manière programmatique, sans avoir à écrire de DQL ou SQL.

Utilisation du QueryBuilder
<?php // Exemple d'utilisation du QueryBuilder $entityManager = $container->get('doctrine')->getManager(); $repository = $entityManager->getRepository(Product::class); // Requête avec QueryBuilder $qb = $repository->createQueryBuilder('p') ->select('p') ->where('p.price > :price') ->andWhere('p.createdAt > :date') ->orderBy('p.createdAt', 'DESC') ->setParameter('price', '100.00') ->setParameter('date', new \DateTime('-1 month')); $products = $qb->getQuery()->getResult(); // Requête avec jointures $qb = $repository->createQueryBuilder('p') ->select('p', 'c', 'i') ->join('p.category', 'c') ->leftJoin('p.images', 'i') ->where('c.name = :categoryName') ->setParameter('categoryName', 'Électronique') ->orderBy('p.name', 'ASC'); $products = $qb->getQuery()->getResult(); // Requête avec agrégation $qb = $repository->createQueryBuilder('p') ->select('c.name as categoryName', 'COUNT(p.id) as productCount', 'AVG(p.price) as averagePrice') ->join('p.category', 'c') ->groupBy('c.id') ->having('COUNT(p.id) > 5'); $categoryStats = $qb->getQuery()->getResult(); // Requête avec sous-requête $qb = $repository->createQueryBuilder('p') ->where('p.price > ( SELECT AVG(p2.price) FROM ' . Product::class . ' p2 WHERE p2.category = p.category )'); $aboveAveragePriceProducts = $qb->getQuery()->getResult();

Fonctionnalités avancées

  • Unit of Work - Pattern qui suit les changements apportés aux objets et synchronise ces changements avec la base de données de manière optimisée
  • Lazy Loading - Chargement des relations entre entités uniquement lorsqu'elles sont réellement nécessaires pour optimiser les performances
  • Cache d'objets - Stockage des entités en mémoire pour éviter des requêtes redondantes à la base de données
  • Migrations - Outils pour gérer les évolutions de schéma de base de données de manière contrôlée et incrémentale
  • Events & Listeners - Système d'événements permettant d'exécuter du code lors de certaines opérations (pre/post persist, update, remove...)
  • Filtres - Mécanisme pour ajouter automatiquement des conditions aux requêtes (par exemple pour la multi-tenancy)
  • Types personnalisés - Possibilité de créer des types de données personnalisés pour la conversion entre PHP et la base de données
Applications concrètes

Cas d'usage

Applications Icône SymfonySymfony

Doctrine est parfaitement intégré avec Icône SymfonySymfony, formant une combinaison puissante pour développer rapidement des applications web robustes avec une gestion efficace des données.

E-commerce

Les plateformes e-commerce bénéficient de la capacité de Doctrine à gérer des modèles de données complexes avec de nombreuses relations (produits, catégories, commandes, clients...).

CMS et systèmes de contenu

Pour les CMS, Doctrine facilite la gestion des hiérarchies de contenu, des métadonnées, des systèmes de taxonomie et des relations entre les différents types de contenu.

Applications d'entreprise

Les applications métier complexes utilisent Doctrine pour modéliser des domaines métier sophistiqués et gérer efficacement leurs données tout en assurant l'intégrité des transactions.

Avantages par rapport aux alternatives

  • Par rapport au SQL brut : Réduit considérablement la quantité de code, élimine les vulnérabilités d'injection SQL et augmente la maintenabilité.

  • Par rapport aux ORMs plus légers : Offre plus de fonctionnalités avancées comme la gestion des relations, le lazy loading et le caching.

  • Par rapport aux solutions NoSQL : Maintient les avantages des bases de données relationnelles (ACID, intégrité des données) tout en offrant une API orientée objet.