doctrine/orm
Doctrine ORM is a PHP 8.1+ object-relational mapper that provides transparent persistence for objects on top of Doctrine DBAL. Includes DQL, an object-oriented SQL-like query language inspired by Hibernate HQL, for flexible, powerful querying.
Installation:
composer require doctrine/orm
For Laravel integration, use doctrine/dbal (dependency) and optionally doctrine/doctrine-bundle (Symfony-based projects).
First Use Case: Define an entity (model) and map it to a database table:
// app/Models/User.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private string $name;
// Getters/setters...
}
Bootstrap ORM:
In bootstrap/app.php or a service provider:
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
$paths = [__DIR__.'/app/Models'];
$isDevMode = true;
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$conn = ['driver' => 'pdo_mysql', 'user' => 'user', 'password' => 'pass', 'dbname' => 'db'];
$entityManager = EntityManager::create($conn, $config);
First Query:
$user = $entityManager->find(User::class, 1);
$users = $entityManager->createQuery('SELECT u FROM App\Models\User u')->getResult();
Doctrine\ORM\Tools\SchemaTool to generate/update DB schema.Doctrine\ORM\EntityRepository for custom logic.CRUD Operations:
// Create
$user = new User();
$user->setName('John Doe');
$entityManager->persist($user);
$entityManager->flush();
// Update
$user->setName('Jane Doe');
$entityManager->flush();
// Delete
$entityManager->remove($user);
$entityManager->flush();
Querying with DQL:
// Basic query
$query = $entityManager->createQuery('SELECT u FROM App\Models\User u WHERE u.name = :name')
->setParameter('name', 'John Doe');
$users = $query->getResult();
// Joins
$query = $entityManager->createQuery(
'SELECT u, p FROM App\Models\User u JOIN u.posts p WHERE p.title LIKE :title'
)->setParameter('title', '%Doctrine%');
Relationships:
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Post::class)]
private Collection $posts;
// Fetch related entities
$user = $entityManager->find(User::class, 1);
foreach ($user->getPosts() as $post) { ... }
Transactions:
$entityManager->beginTransaction();
try {
$entityManager->persist($user);
$entityManager->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
laravel-doctrine/orm bridge for seamless Eloquent + Doctrine integration.$config->setSecondLevelCacheEnabled(true);
$config->setSecondLevelCache(new \Doctrine\Common\Cache\ApcuCache());
#[ORM\PrePersist]
public function prePersist() {
$this->createdAt = new \DateTime();
}
EntityRepository for complex logic:
class UserRepository extends EntityRepository {
public function findActiveUsers() {
return $this->createQueryBuilder('u')
->where('u.isActive = :active')
->setParameter('active', true)
->getQuery()
->getResult();
}
}
Lazy Loading:
ProxyQueryException.->initialize() or ->get() with DISTINCT to eager-load:
$user = $entityManager->find(User::class, 1);
$user->getPosts()->initialize(); // Force load
$query->leftJoin('u.posts', 'p')->addSelect('p');
N+1 Problem:
->fetch('EXECUTE') or ->getArrayResult() for bulk operations:
$query->setFetchMode('App\Models\User', 'array');
Case Sensitivity:
LOWER() or UPPER() for case-sensitive queries:
$query->where('LOWER(u.name) = LOWER(:name)');
Transaction Management:
try-catch blocks or wrap in a service with transaction handling.Entity Manager Lifecycle:
EntityManager across requests (thread-safety).EntityManager per request (Laravel service container handles this).SQL Logging:
$config->setSQLLogger(new \Doctrine\ORM\Logging\EchoSQLLogger());
Or use Doctrine\ORM\Logging\DebugStack for detailed logging.
Query Profiling:
$profiler = new \Doctrine\ORM\Tools\Console\Helper\ProfilerHelper($entityManager);
$profiler->collect();
Schema Validation:
php vendor/bin/doctrine orm:validate-schema
Custom Types:
#[ORM\Column(type: 'json')]
private array $metadata;
// Or create a custom type:
class JsonType extends \Doctrine\DBAL\Types\JsonType { ... }
Event Subscribers:
use Doctrine\Common\EventSubscriber;
class MySubscriber implements EventSubscriber {
public function getSubscribedEvents() {
return ['prePersist', 'postUpdate'];
}
public function prePersist(LifecycleEventArgs $args) { ... }
}
Filters:
#[ORM\Table(name: 'users', options: ['indexes' => ['active_idx' => 'is_active']])]
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User {
#[ORM\Column(name: 'is_active', type: 'boolean')]
private bool $isActive;
}
// In repository:
$filter = new \Doctrine\ORM\Mapping\ClassMetadata($entityManager->getClassMetadata(User::class));
$filter->addFilter('active', 'isActive = :active');
Hydration:
// Return scalar arrays
$query->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_ARRAY);
// Return objects with custom constructor
$query->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_CUSTOM, 'App\Hydrator\MyHydrator');
Service Container Binding:
Bind EntityManager in a service provider:
$this->app->singleton(EntityManager::class, function ($app) {
$config = Setup::createAnnotationMetadataConfiguration([...]);
return EntityManager::create($app['db']->connection()->getPdo(), $config);
});
Migrations:
Use doctrine/dbal migrations alongside Laravel migrations:
php vendor/bin/doctrine orm:schema-tool:create
Caching: Disable Doctrine cache in production if using Laravel’s cache:
$config->setMetadataCacheImpl(null);
$config->setQueryCacheImpl(null);
How can I help you explore Laravel packages today?