What it is
Doctrine is a Data Mapper ORM for PHP with a Unit of Work, its own query language (DQL), and a migrations system.
The EntityManager tracks loaded entities and computes the necessary SQL at flush time. DQL queries objects rather than tables.
Installation
composer require doctrine/orm doctrine/dbalGetting started
The smallest useful thing you can do with it, and what each part means.
php
<?php
$book = $em->find(Book::class, 42);
$book->setTitle('Dune Messiah'); // no query yet — just tracked
$book->getAuthor()->setName('F. Herbert');
$em->flush(); // one transaction, only the changed columns
// New entities must be told about explicitly.
$new = new Book('Children of Dune', 1976);
$em->persist($new);
$em->flush();php
<?php
// DQL queries entities and properties, not tables and columns.
$books = $em->createQuery(
'SELECT b, a FROM App\Entity\Book b
JOIN b.author a
WHERE b.year >= :year
ORDER BY b.year DESC'
)->setParameter('year', 1990)
->setMaxResults(10)
->getResult();
// Selecting `b, a` fetch-joins the author, avoiding N+1.
$qb = $repository->createQueryBuilder('b')
->where('b.year >= :year')->setParameter('year', 1990);
if ($search !== null) {
$qb->andWhere('b.title LIKE :q')->setParameter('q', "%$search%");
}Advanced usage
Where the library earns its place over a simpler alternative.
php
<?php
$batchSize = 100;
$query = $em->createQuery('SELECT b FROM App\Entity\Book b');
foreach ($query->toIterable() as $i => $book) {
$book->recalculate();
if (($i % $batchSize) === 0) {
$em->flush();
$em->clear(); // detach everything; otherwise memory grows unbounded
}
}
$em->flush();
$em->clear();Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- A new entity was found through the relationship
- A related object was never persisted. Add persist(), or configure cascade: ['persist'] on the association.
- Allowed memory size exhausted in a batch job
- The identity map is holding every entity. Use toIterable with periodic flush() and clear().
Best practices
- Call persist() on new entities; only already-managed ones are tracked automatically.
- Fetch-join relations by selecting both aliases in DQL to avoid N+1.
- Clear the EntityManager periodically in batch jobs or memory grows without bound.
- Use migrations rather than schema:update in production.
Background
Why it exists, and what it was reacting to.
Doctrine follows the Data Mapper pattern rather than Active Record: entities are plain objects that know nothing about persistence, which keeps domain logic independent of the database.
