Для современных веб-приложений на PHP выбор ORM играет ключевую роль в организации работы с базой данных. В Laravel по умолчанию используется Eloquent — простой и удобный инструмент, идеально подходящий для большинства задач. Однако при сложных проектах, требующих гибкости, расширяемости и мощных возможностей работы с объектно-реляционным отображением — стоит обратить внимание на Doctrine ORM.
Во-первых, Doctrine обеспечивает более гибкое и мощное управление связями между сущностями, поддерживая различные типы ассоциаций и наследование. Во-вторых, она предлагает обширные возможности кэширования и оптимизации запросов, что улучшает производительность. Наконец, Doctrine использует шаблон Data Mapper, отделяя бизнес-логику от слоя данных, в отличие от Active Record в Eloquent, что способствует чистоте архитектуры и упрощает тестирование.
Установка необходимых пакетов
composer require symfony/cache doctrine/orm doctrine/dbal
Дополнительно можно установить пакет ramsey/uuid-doctrine для работы с UUID. В Laravel, например, уже есть удобный встроенный метод хелпера/фасада str()->uuid()->toString(), поэтому выбор зависит от ваших предпочтений и конкретных задач.
Еще важно убедиться, что у вас установлено и активировано расширение APCu. Для Doctrine ORM — APCu играет ключевую роль как механизм кэширования метаданных и результатов запросов. Это значительно повышает производительность приложения, так как сокращает количество обращений к базе данных и повторной обработки схемы. Без APCu Doctrine вынуждена каждый раз заново парсить аннотации и строить запросы, что замедляет работу.
Symfony Cache предоставляет общий интерфейс для кэширования, однако APCu обеспечивает быстрое хранение данных в памяти на уровне сервера, что критично для эффективной работы Doctrine и всего кэширования в проекте.
Настройка конфига для Doctrine ORM
В простейшем своем виде конфиг для Doctrine ORM в Laravel может выглядеть так:
<?php
return [
/*
|--------------------------------------------------------------------------
| Database Connection
|--------------------------------------------------------------------------
|
| Define your database connection parameters here. These values are pulled
| from your environment file using the env() helper for security.
| Customize driver, host, port, database name, username, password, and PDO options.
|
| Available drivers supported by Doctrine ORM:
| - pdo_mysql (MySQL)
| - pdo_pgsql (PostgreSQL)
| - pdo_sqlite (SQLite)
| - pdo_sqlsrv (SQL Server / MSSQL)
| - oci8 (Oracle)
|
| Make sure to set your DB_CONNECTION environment variable to one of these.
|
*/
'connection' => [
'driver' => 'pdo_pgsql',
'host' => env(key: 'DB_HOST', default: 'postgres'),
'port' => env(key: 'DB_PORT', default: '5432'),
'dbname' => env(key: 'DB_DATABASE'),
'user' => env(key: 'DB_USERNAME'),
'password' => env(key: 'DB_PASSWORD'),
'options' => [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
],
],
/*
|--------------------------------------------------------------------------
| Metadata Directories
|--------------------------------------------------------------------------
|
| Paths where Doctrine will look for metadata with attributes to map your entities.
| Typically, this points to your application's Entities directory.
|
*/
'metadata_dirs' => [
app_path(path: 'Entities'),
],
/*
|--------------------------------------------------------------------------
| Custom Doctrine Types
|--------------------------------------------------------------------------
|
| Register any custom Doctrine types here.
| Example here registers UUID type from ramsey/uuid package.
|
*/
'custom_types' => [
\Ramsey\Uuid\Doctrine\UuidType::NAME => \Ramsey\Uuid\Doctrine\UuidType::class,
],
/*
|--------------------------------------------------------------------------
| Development Mode
|--------------------------------------------------------------------------
|
| When set to true, Doctrine will generate proxies and metadata dynamically.
| Typically enabled in local or dev environments.
|
*/
'dev_mode' => env(key: 'APP_ENV') === 'dev',
];
Вместо APCu можно в этом конфиге настроить Redis, вот пример: https://github.com/initialstacker/laravel-ddd/blob/main/src/config/doctrine.php, но опять же, это на вкус и цвет. Цель этой статьи — просто показать, как подружить Laravel 12 с Doctrine ORM.
Настройка сервис-провайдера
В Laravel для интеграции Doctrine ORM создайте сервис-провайдер, который зарегистрирует и сконфигурирует EntityManager как singleton в контейнере приложений.
Вот пример реализации класса DoctrineServiceProvider:
<?php declare(strict_types=1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Foundation\Application;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type;
final class DoctrineServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->singleton(
abstract: EntityManagerInterface::class,
concrete: function (Application $app): EntityManager {
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: config(key: 'doctrine.metadata_dirs'),
isDevMode: config(key: 'doctrine.dev_mode')
);
$config->setAutoGenerateProxyClasses(
autoGenerate: ProxyFactory::AUTOGENERATE_ALWAYS
);
$connection = DriverManager::getConnection(
params: config(key: 'doctrine.connection'),
config: $config
);
foreach (config(key: 'doctrine.custom_types') as $name => $type) {
if (!Type::hasType(name: $name)) {
Type::addType(name: $name, type: $type);
}
}
return new EntityManager(conn: $connection, config: $config);
}
);
}
}
Этот провайдер позволяет удобно внедрять в приложение EntityManagerInterface и использовать Doctrine ORM с настраиваемыми параметрами, указанными в конфигурационных файлах Laravel.
Не забудьте только добавить его в bootstrap/providers.php.
Пример сущности Doctrine ORM
В качестве примера рассмотрим реализацию сущности User с использованием PHP 8.4 атрибутов (attributes) для описания маппинга с базой данных:
<?php declare(strict_types=1);
namespace App\Entities;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\Types;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\Doctrine\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
#[ORM\Entity]
#[ORM\Table(name: '`users`')]
class User
{
/**
* Unique identifier for the user.
*
* @var UuidInterface
*/
#[ORM\Id]
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
public private(set) ?UuidInterface $id;
/**
* The user's name.
*
* @var string
*/
#[ORM\Column(name: 'name', type: Types::STRING, length: 35)]
public private(set) string $name {
set (string $value) {
$value = trim(string: $value);
$name = mb_convert_case(string: $value, mode: MB_CASE_TITLE);
$this->name = $name;
}
}
/**
* The user's email address.
*
* @var string
*/
#[ORM\Column(name: 'email', type: Types::STRING, length: 254, unique: true)]
public private(set) string $email;
/**
* The timestamp when the email was verified.
*
* @var \DateTimeImmutable|null
*/
#[ORM\Column(
name: 'email_verified_at',
type: Types::DATETIME_IMMUTABLE,
nullable: true
)]
private ?\DateTimeImmutable $emailVerifiedAt = null;
/**
* The user's password.
*
* @var Password
*/
#[ORM\Column(name: 'password', type: Types::STRING, length: 60)]
private string $password;
/**
* The token used for "remember me" functionality.
*
* @var string|null
*/
#[ORM\Column(
name: 'remember_token',
type: Types::STRING,
length: 100,
unique: true,
nullable: true
)]
private ?string $rememberToken = null {
set (?string $value) {
$rememberToken = $value !== null ? trim(string: $value) : null;
$this->rememberToken = $rememberToken;
}
}
/**
* Timestamp when was created.
*
* @var \DateTimeImmutable
*/
#[ORM\Column(name: 'created_at', type: Types::DATETIME_IMMUTABLE, options: [
'precision' => 6,
'default' => 'CURRENT_TIMESTAMP',
])]
public private(set) ?\DateTimeImmutable $createdAt = null;
/**
* Timestamp when was last updated.
*
* @var \DateTimeImmutable
*/
#[ORM\Column(name: 'updated_at', type: Types::DATETIME_IMMUTABLE, options: [
'precision' => 6,
], nullable: true)]
public private(set) ?\DateTimeImmutable $updatedAt = null;
/**
* Initializes a new user with the given details.
*
* @param string $name
* @param string $email
* @param string $password
* @param UuidInterface|null $id
*/
public function __construct(
string $name,
string $email,
string $password,
?UuidInterface $id = null,
) {
/**
* Generates a new user ID if none is provided.
*/
$this->id = $id ?? Uuid::uuid4();
/**
* Assigns user personal and account details.
*/
$this->name = $name;
$this->email = $email;
$this->password = $password;
/**
* Initialize created_at and updated_at timestamps.
*/
$this->createdAt = new \DateTimeImmutable();
$this->updatedAt = new \DateTimeImmutable();
}
}
В этом примере используется кастомный генератор UUID для первичного ключа через пакет ramsey/uuid-doctrine.
Пример запроса в Doctrine ORM
В этом примере показано, как с помощью Doctrine ORM и EntityManager получить всех пользователей из таблицы users через репозиторий сущности User:
<?php declare(strict_types=1);
namespace App\Http\Controllers;
use Doctrine\ORM\EntityManagerInterface;
use App\Entities\User;
final class UserController extends Controller
{
/**
* Doctrine EntityManager instance for DB operations.
*
* @param EntityManagerInterface $entityManager
*/
public function __construct(
private EntityManagerInterface $entityManager
) {}
/**
* Handle the incoming request.
*/
public function __invoke(): void
{
$users = $this->entityManager->getRepository(
className: User::class
)->findAll();
dd($users);
}
}
Объяснение:
EntityManagerInterface— это основной интерфейс для работы с Doctrine ORM, который позволяет взаимодействовать с базой данных.Метод
getRepository(ClassName::class)возвращает репозиторий для конкретной сущности, в данном случае User.Вызов метода
findAll()репозитория получает все записи из таблицы пользователей, возвращая массив объектов сущности.
Результат:

Заключение
Использование Doctrine ORM в Laravel предоставляет разработчикам возможность объединить мощь и гибкость Doctrine с удобством и функциональностью Laravel. Doctrine предлагает высокий уровень абстракции и более строгий паттерн Data Mapper, что позволяет отделить логику домена от способа хранения данных и использовать сложные типы данных, кэширование и расширенные возможности запросов через DQL. В то время как Laravel по умолчанию использует Eloquent с паттерном Active Record, интеграция Doctrine через пакеты, такие как Laravel Doctrine, позволяет получать преимущества обоих миров.
Doctrine ORM легко интегрируется с основными Laravel-компонентами — системой аутентификации, валидации и пагинации, что упрощает разработку и расширяет возможности приложения. Использование Doctrine с Laravel помогает организовать более чистую архитектуру, особенно в крупных проектах, где нужна сложная бизнес-логика и масштабируемость.
abyrvalg
Используйте для идентификаторов UuidV7, субд вам спасибо скажет.