Ответ
Ленивая загрузка (Lazy Loading) в Doctrine — это стратегия загрузки связанных сущностей, при которой они не извлекаются из базы данных сразу при запросе основной сущности, а только в момент первого обращения к этому отношению. Это реализуется с помощью прокси-объектов.
Как это работает:
- При загрузке сущности
UserDoctrine создаёт для свойства$orders(коллекция) специальный прокси-объект, а не выполняет SQL-запрос. - При первом вызове
$user->getOrders()->count()или итерации по коллекции прокси «просыпается» и выполняет запрос к БД для загрузки реальных данных.
Пример с атрибутами в Symfony/Doctrine:
// src/Entity/User.php
#[ORMEntity]
class User
{
#[ORMId, ORMGeneratedValue, ORMColumn]
private ?int $id = null;
#[ORMOneToMany(targetEntity: Order::class, mappedBy: 'user')]
// fetch: 'LAZY' установлен по умолчанию для OneToMany и ManyToMany
private Collection $orders;
public function __construct()
{
$this->orders = new ArrayCollection();
}
public function getOrders(): Collection
{
return $this->orders;
}
}
// В коде контроллера или сервиса
$user = $entityManager->find(User::class, 1);
// На этом этапе запроса для заказов НЕТ
foreach ($user->getOrders() as $order) {
// ЗДЕСЬ выполняется SQL-запрос типа:
// SELECT * FROM orders WHERE user_id = 1
echo $order->getProductName();
}
Критически важные нюансы и проблема N+1:
- Прокси: Doctrine использует сгенерированные классы-прокси, которые наследуют вашу сущность и перехватывают обращения к геттерам.
- Проблема N+1: Если вы загрузите 100 пользователей в цикле и для каждого обратитесь к его заказам, Doctrine выполнит 1 запрос для пользователей + 100 отдельных запросов для заказов. Это убийственно для производительности.
- Решение — жадная загрузка (Eager Fetching) или DQL JOIN:
// Использование DQL с JOIN FETCH $dql = "SELECT u, o FROM AppEntityUser u JOIN u.orders o WHERE u.id = :id"; $user = $entityManager->createQuery($dql)->setParameter('id', 1)->getSingleResult(); // Или через QueryBuilder $user = $entityManager->getRepository(User::class) ->createQueryBuilder('u') ->leftJoin('u.orders', 'o') ->addSelect('o') ->where('u.id = :id') ->setParameter('id', 1) ->getQuery() ->getSingleResult();Теперь и пользователь, и его заказы загружены одним запросом.
Ленивая загрузка — удобная стратегия по умолчанию, но требует осознанного управления запросами в местах, где загружаются коллекции.