doctrine/persistence
Doctrine Persistence provides shared interfaces and abstractions for object mapper persistence in PHP. It standardizes common concepts like object managers, repositories, and metadata across Doctrine and other mappers, helping libraries integrate consistently.
To leverage doctrine/persistence in Laravel, start by installing the package via Composer:
composer require doctrine/persistence
Laravel developers typically use Doctrine ORM (which depends on this package). For standalone use, initialize a ManagerRegistry and ObjectManager:
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain;
// Configure a simple mapping driver (e.g., for annotations)
$metadataDriver = new AnnotationDriver(new \Doctrine\Common\Annotations\AnnotationReader(), ['path/to/entities']);
// Create a registry and manager
$registry = new ManagerRegistry();
$manager = $registry->getManager();
$manager->setMetadataDriverImpl($metadataDriver);
ManagerRegistry: Central registry for all ObjectManager instances.ObjectManager: Core interface for persisting entities (CRUD operations).MappingDriverChain: Combines multiple mapping drivers (e.g., XML, YAML, annotations).Define entities with Doctrine annotations/attributes and configure metadata drivers:
// Example: Using attributes (Doctrine 3.0+)
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User {
#[ORM\Id, ORM\GeneratedValue]
private ?int $id = null;
#[ORM\Column]
private string $name;
}
// Configure metadata driver in Laravel's service provider
$container->singleton(ManagerRegistry::class, function ($container) {
$registry = new ManagerRegistry();
$driver = new AttributeDriver(new ClassLocator());
$registry->setManager($container->make(ObjectManager::class), 'default');
$registry->getManager('default')->setMetadataDriverImpl($driver);
return $registry;
});
Extend Doctrine\Persistence\Repository\Repository for custom queries:
use Doctrine\Persistence\Repository\Repository;
class UserRepository extends Repository {
public function findByName(string $name): array {
return $this->createQueryBuilder('u')
->where('u.name = :name')
->setParameter('name', $name)
->getQuery()
->getResult();
}
}
Attach listeners to lifecycle events (e.g., prePersist):
use Doctrine\Persistence\Event\LifecycleEventArgs;
class UserListener {
public function prePersist(User $user, LifecycleEventArgs $args): void {
if (empty($user->getCreatedAt())) {
$user->setCreatedAt(new \DateTime());
}
}
}
// Register in Laravel's service provider
$registry->getManager('default')->getEventManager()->addEventListener(
['prePersist'],
$container->make(UserListener::class)
);
Use ObjectManager for transactions:
$manager = $registry->getManager();
$manager->beginTransaction();
try {
$user = new User();
$user->setName('John Doe');
$manager->persist($user);
$manager->flush();
$manager->commit();
} catch (\Exception $e) {
$manager->rollback();
throw $e;
}
Leverage QueryBuilder for complex queries:
$qb = $manager->createQueryBuilder();
$qb->select('u')
->from(User::class, 'u')
->where('u.active = :active')
->setParameter('active', true);
$users = $qb->getQuery()->getResult();
Metadata Driver Conflicts:
Ensure your MappingDriverChain is configured correctly. Mixing drivers (e.g., annotations + XML) may cause conflicts.
// Bad: Duplicate drivers
$driverChain = new MappingDriverChain([$annotationDriver, $xmlDriver]);
Fix: Use MappingDriverChain::addDriver() carefully or replace existing drivers.
Proxy Generation:
Doctrine proxies require __toString() and other magic methods. If using enums or custom classes, ensure they implement required interfaces.
Tip: Use #[ORM\Proxy] or extend Doctrine\ORM\Mapping\ClassMetadata.
Transaction Isolation:
Laravel’s database transactions may conflict with Doctrine’s. Use DB::transaction() or Doctrine’s beginTransaction() explicitly.
Enable SQL Logging:
$manager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
Metadata Validation:
php artisan doctrine:metadata:info
(Requires doctrine/orm for full CLI support.)
Event Debugging:
Use EventManager::addEventSubscriber() with a subscriber that logs events:
$eventManager->addEventSubscriber(new class {
public function getSubscribedEvents(): array {
return ['onFlush', 'preUpdate'];
}
public function onFlush(OnFlushEventArgs $args) {
\Log::debug('Flush event triggered');
}
});
N+1 queries by using fetch="EAGER" or join in QueryBuilder.
$qb->addSelect('u.posts')->leftJoin('u.posts', 'p');
$cache = new \Doctrine\Common\Cache\FilesystemCache('/path/to/cache');
$driver = new AnnotationDriver($reader, $cache);
Doctrine\Persistence\Mapping\Driver\MappingDriverInterface for custom formats (e.g., JSON).getSubscribedEvents() to hook into Doctrine’s lifecycle.Doctrine\ORM\Proxy\Proxy for custom proxy behavior.ManagerRegistry and ObjectManager as singletons in Laravel’s AppServiceProvider:
$this->app->singleton(ManagerRegistry::class, function ($app) {
$registry = new ManagerRegistry();
$registry->setManager($app->make(ObjectManager::class), 'default');
return $registry;
});
doctrine/orm for migrations, but ensure doctrine/persistence is compatible with your Doctrine version.Doctrine\Persistence\ObjectManager in tests with an in-memory database:
$manager = new ObjectManager();
$manager->setConnection($this->createTestConnection());
ReflectionClass or ReflectionProperty directly.How can I help you explore Laravel packages today?