doctrine/orm
Doctrine ORM is an object-relational mapper for PHP 8.1+ providing transparent persistence for PHP objects on top of Doctrine DBAL. Includes Doctrine Query Language (DQL), an object-oriented SQL-like dialect for flexible querying without duplication.
Installation:
composer require doctrine/orm
For Laravel-specific integration, use doctrine/dbal (dependency) and doctrine/doctrine-bundle (if using Symfony components).
Configuration:
app/Models/User.php) with Doctrine annotations or XML/YAML mappings.use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class User {
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $name;
}
First Use Case:
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);
// Persist an entity
$user = new User();
$user->name = 'John Doe';
$entityManager->persist($user);
$entityManager->flush();
CRUD Operations:
persist() + flush().EntityManager::find() or DQL:
$users = $entityManager->createQuery('SELECT u FROM User u WHERE u.name = :name')
->setParameter('name', 'John Doe')
->getResult();
flush().remove() + flush().Relationships:
@OneToMany, @ManyToMany).join in DQL or fetch="EAGER":
#[ORM\OneToMany(targetEntity: Post::class, mappedBy: "author")]
private Collection $posts;
Repositories:
Doctrine\ORM\EntityRepository for custom logic:
namespace App\Repositories;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository {
public function findActiveUsers() {
return $this->createQueryBuilder('u')
->where('u.isActive = :active')
->setParameter('active', true)
->getQuery()
->getResult();
}
}
Transactions:
$entityManager->beginTransaction();
try {
$entityManager->persist($user);
$entityManager->flush();
$entityManager->commit();
} catch (\Exception $e) {
$entityManager->rollback();
throw $e;
}
Service Providers:
Bind EntityManager in a Laravel service provider:
public function register() {
$this->app->singleton(EntityManager::class, function ($app) {
$paths = [__DIR__.'/../app/Models'];
$config = Setup::createAnnotationMetadataConfiguration($paths, true);
$conn = $app['config']['database.connections.mysql'];
return EntityManager::create($conn, $config);
});
}
Eloquent Interop: Use Doctrine alongside Eloquent by sharing the same DB connection or via a facade:
use Doctrine\ORM\EntityManagerInterface;
class UserService {
public function __construct(private EntityManagerInterface $em) {}
public function syncUser(User $user) {
$this->em->persist($user);
$this->em->flush();
// Use Eloquent for other operations if needed
}
}
Lazy Loading:
ProxyException. Use fetch="EAGER" or initialize with ->getPosts()->toArray().->getPosts() or use join fetch in DQL.Caching Metadata:
php vendor/bin/doctrine orm:clear-cache:metadata
Connection Management:
doctrine/dbal adapters if needed.Case Sensitivity:
Transaction Isolation:
READ UNCOMMITTED or optimize queries.SQL Logging: Enable SQL logging in config:
$config->setSQLLogger(new \Doctrine\ORM\Logging\EchoSQLLogger());
Or use a file logger:
$config->setSQLLogger(new \Doctrine\ORM\Logging\FileLogger('/tmp/doctrine.log'));
Query Profiling: Use the profiler to analyze slow queries:
$query = $entityManager->createQuery('...');
$query->useResultCache(true);
$query->useQueryCache(true);
Custom DQL Functions:
$config->addCustomStringFunction('CONCAT_WS', 'DoctrineExtensions\Query\Mysql\ConcatWs');
Event Listeners:
prePersist, postUpdate):
$eventManager = $entityManager->getEventManager();
$eventManager->addEventListener(
\Doctrine\ORM\Events::prePersist,
new \App\EventListeners\UserListener()
);
Second-Level Cache:
$config->setSecondLevelCacheEnabled(true);
$config->setSecondLevelCache(new \Doctrine\Common\Cache\ApcuCache());
Native Query Support:
createNativeQuery() for complex SQL:
$query = $entityManager->createNativeQuery('SELECT * FROM users WHERE id = ?');
$query->setParameter(1, 1);
$result = $query->getResult();
Migration Conflicts: Avoid mixing Doctrine schema updates with Laravel migrations. Use one tool per project.
Service Container Conflicts:
If using both Doctrine and Eloquent, ensure no binding collisions (e.g., DBAL\Connection vs. Illuminate\Database\Connection).
Testing: Reset the entity manager between tests:
$entityManager->getConnection()->beginTransaction();
$entityManager->getConnection()->rollBack();
How can I help you explore Laravel packages today?