Installation
Add the package via Composer (included in symfony/orm-pack):
composer require symfony/orm-pack
This pulls in Doctrine ORM, Doctrine Bundle, and related dependencies.
Configuration
config/packages/doctrine.yaml exists (auto-generated by Symfony Flex).DATABASE_URL in .env is properly configured (e.g., mysql://user:pass@127.0.0.1:3306/db_name).First Use Case: Create an Entity
php bin/console make:entity User
Follow prompts to scaffold a User entity with fields like name (string) and email (string).
Run migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Basic CRUD
Use the EntityManager in a controller:
use Doctrine\ORM\EntityManagerInterface;
public function index(EntityManagerInterface $em)
{
$users = $em->getRepository(User::class)->findAll();
return $this->render('user/index.html.twig', ['users' => $users]);
}
Repository Pattern
Extend Doctrine\ORM\EntityRepository for custom queries:
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class UserRepository extends ServiceEntityRepository
{
public function findByEmail(string $email): ?User
{
return $this->createQueryBuilder('u')
->where('u.email = :email')
->setParameter('email', $email)
->getQuery()
->getOneOrNullResult();
}
}
Lifecycle Callbacks Use annotations or XML/YAML to hook into entity events:
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class User
{
#[ORM\PrePersist]
public function setCreatedAt(): void
{
$this->createdAt = new \DateTime();
}
}
DQL and QueryBuilder
$query = $em->createQuery('SELECT u FROM App\Entity\User u WHERE u.active = :active');
$query->setParameter('active', true);
$users = $query->getResult();
$qb = $em->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->where('u.role = :role')
->orderBy('u.name', 'ASC');
Transactions Wrap operations in a transaction for atomicity:
$em->beginTransaction();
try {
$em->persist($user);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
Symfony Integration
EntityManagerInterface into services/controllers.$form = $this->createForm(UserType::class, $user);
Fixtures and Testing
Use doctrine/doctrine-fixtures-bundle for test data:
composer require --dev doctrine/doctrine-fixtures-bundle
php bin/console doctrine:fixtures:load
Event Listeners/Subscribers
Listen to Doctrine events (e.g., postPersist) for cross-cutting logic:
namespace App\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
class UserListener
{
public function postPersist(LifecycleEventArgs $args): void
{
$entity = $args->getObject();
if ($entity instanceof User) {
$entity->setSlug(strtolower($entity->getName()));
}
}
}
Register in config/services.yaml:
services:
App\EventListener\UserListener:
tags:
- { name: 'doctrine.event_listener', event: 'postPersist' }
Custom DTOs with Hydration
Use ResultSetMapping for complex projections:
$rsm = new ResultSetMapping();
$rsm->addScalarResult('u.name', 'name');
$rsm->addScalarResult('COUNT(o.id)', 'orderCount');
$query = $em->createNativeQuery(
'SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id',
$rsm
);
$results = $query->getResult();
Caching
Enable query caching in config/packages/doctrine.yaml:
doctrine:
orm:
metadata_cache_driver: apcu
query_cache_driver: apcu
result_cache_driver: apcu
Batch Processing
Use DoctrineExtensions for bulk operations:
composer require stof/doctrine-extensions-bundle
Example with BulkInsert:
$em->getConnection()->getConfiguration()->setSQLLogger(null); // Disable logging
$em->getConnection()->beginTransaction();
$em->getConnection()->executeStatement('INSERT INTO users (...) VALUES (...)');
$em->getConnection()->commit();
Lazy Loading vs. Eager Loading
joinFetch or DISTINCT:
$qb->select('DISTINCT u')
->leftJoin('u.orders', 'o')
->addSelect('o');
Case Sensitivity in Migrations
schema.yml or migrations.Circular References
inversedBy/mappedBy cause infinite loops.#[ORM\ManyToMany(targetEntity: Role::class, inversedBy: "users")]
private Collection $roles;
Transaction Isolation
READ_COMMITTED isolation.Entity Manager Clearing
$em->clear() removes all managed entities (including those in the current request).EntityManagerInterface::getRepository()->find() to reload.UTF-8 Collation
utf8mb4_unicode_ci for MySQL).DATABASE_URL:
DATABASE_URL="mysql://user:pass@127.0.0.1:3306/db_name?serverVersion=8.0&charset=utf8mb4&collation=utf8mb4_unicode_ci"
Query Logging
Enable SQL logging in .env:
DOCTRINE_DQL_LOGGING_PROFILE=dev
Or in config/packages/dev/doctrine.yaml:
doctrine:
orm:
logging: true
Profiler Integration Use Symfony Profiler to inspect queries and entity states:
$profiler = $this->get('profiler');
$dataCollector = $profiler->getCollector('doctrine');
Common Errors
composer dump-autoload).namespace App\Doctrine\Types;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
class JsonType extends Type
{
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'JSON';
}
public function getName()
{
return 'json';
}
}
How can I help you explore Laravel packages today?