Installation Add the bundle via Composer:
composer require doctrine/doctrine-bundle
Enable the bundle in config/bundles.php:
return [
// ...
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
];
Configuration
Configure your database connection in config/packages/doctrine.yaml:
doctrine:
dbal:
url: '%env(DATABASE_URL)%'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
First Use Case
Create an entity (e.g., src/Entity/User.php):
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $name;
// Getters/setters...
}
Run migrations:
php bin/console doctrine:schema:update --force
Repository Pattern
Use repositories for complex queries (e.g., src/Repository/UserRepository.php):
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findActiveUsers(): array
{
return $this->createQueryBuilder('u')
->where('u.isActive = :active')
->setParameter('active', true)
->getQuery()
->getResult();
}
}
QueryBuilder for Dynamic Queries Build flexible queries in controllers/services:
$qb = $this->createQueryBuilder('u')
->select('u.id', 'u.name')
->where('u.createdAt > :date')
->setParameter('date', new \DateTime('-1 week'));
Lifecycle Callbacks
Use @ORM\PrePersist, @ORM\PostUpdate, etc., for entity events:
#[ORM\PrePersist]
public function setCreatedAt(): void
{
$this->createdAt = new \DateTime();
}
DQL for Complex Joins Leverage Doctrine Query Language for readability:
$query = $this->createQueryBuilder('u')
->select('u, p')
->join('u.products', 'p')
->where('p.price > :minPrice')
->getQuery();
Event Subscribers
Hook into Doctrine events (e.g., src/EventSubscriber/UserSubscriber.php):
namespace App\EventSubscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
class UserSubscriber implements EventSubscriber
{
public function getSubscribedEvents(): array
{
return ['prePersist', 'preUpdate'];
}
public function prePersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof User) {
$entity->setUpdatedAt(new \DateTime());
}
}
}
Native SQL Queries
Use EntityManager::createNativeQuery() for raw SQL:
$results = $entityManager->createNativeQuery('SELECT * FROM users WHERE active = 1')->getResult();
Proxy Classes
php bin/console cache:clear) after adding new entities can cause ClassNotFoundException.php bin/console doctrine:cache:clear-metadata or enable auto_generate_proxy_classes: true.Case Sensitivity
naming_strategy to handle underscores/camelCase:
orm:
naming_strategy: doctrine.orm.naming_strategy.underscore
Lazy Loading
fetch: EAGER) can cause performance issues. Prefer lazy loading (fetch: LAZY) and fetch only when needed.#[ORM\ManyToOne(fetch: 'EAGER')] sparingly.Transaction Management
try-catch blocks with rollback():
$entityManager->beginTransaction();
try {
$entityManager->persist($entity);
$entityManager->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Circular References
inversedBy/mappedBy cause infinite loops.#[ORM\ManyToMany(targetEntity: Product::class, inversedBy: 'users')]
private Collection $users;
Schema Updates
doctrine:schema:update --force in production can be risky. Use migrations (doctrine:migrations:diff + doctrine:migrations:migrate) instead.Query Logging
Enable SQL logging in config/packages/dev/doctrine.yaml:
dbal:
logging: true
profiling: true
View queries in Symfony Profiler or var/log/dev.log.
Entity Manager Debugging
Use getMetadataFactory() to inspect entities:
$metadata = $entityManager->getMetadataFactory()->getMetadataFor(User::class);
dump($metadata->getAssociationMappings());
Common Errors
@ORM\Column or schema mismatches.Custom DQL Functions
Register custom functions in doctrine.yaml:
orm:
dql:
string_functions:
CONCAT: Doctrine\ORM\Query\AST\Functions\StringFunction
Event Listeners Create listeners for global logic (e.g., logging, auditing):
namespace App\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
class AuditListener implements EventSubscriber
{
public function getSubscribedEvents(): array
{
return ['onFlush'];
}
public function onFlush(OnFlushEventArgs $args): void
{
$entityManager = $args->getEntityManager();
$uow = $entityManager->getUnitOfWork();
// Custom logic...
}
}
Custom Repository Factories Override repository creation for custom logic:
orm:
repository_factory: App\Doctrine\CustomRepositoryFactory
Database Platform-Specific Features
Use getDatabasePlatform() to write platform-aware queries:
$platform = $entityManager->getConnection()->getDatabasePlatform();
if ($platform->supportsLimitOffset()) {
$qb->setMaxResults(10)->setFirstResult(0);
}
Hybrid ODMMappings Combine ORM and DBAL for mixed persistence strategies:
orm:
mappings:
App:
type: xml
dir: '%kernel.project_dir%/config/doctrine'
prefix: 'App\Entity'
How can I help you explore Laravel packages today?