Хинт HINT_FETCH_JOIN_COLLECTION
Хинт HINT_FETCH_JOIN_COLLECTION - это константа класса Paginator,
которая передаётся в метод paginate для управления стратегией
загрузки данных. По умолчанию пагинатор выполняет отдельный запрос
для подсчёта общего количества записей. Если пагинируемая сущность
содержит коллекцию (связь OneToMany или ManyToMany), это может
привести к проблеме N+1 запроса. Установка хинта
HINT_FETCH_JOIN_COLLECTION заставляет пагинатор использовать
JOIN для выборки коллекции в основном запросе, что снижает нагрузку
на базу данных. Хинт передаётся четвёртым аргументом в метод
paginate.
Синтаксис
Paginator::HINT_FETCH_JOIN_COLLECTION
Пример
Давайте создадим пагинатор для сущности Article и передадим
хинт HINT_FETCH_JOIN_COLLECTION:
<?php
namespace AppController;
use AppEntityArticle;
use DoctrineORMEntityManagerInterface;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAttributeRoute;
use KnpComponentPagerPaginatorInterface;
use KnpComponentPagerPaginator;
class ArticleController extends AbstractController
{
#[Route('/articles', name: 'article_list')]
public function list(EntityManagerInterface $em, PaginatorInterface $paginator): Response
{
$dql = 'SELECT a FROM AppEntityArticle a';
$query = $em->createQuery($dql);
$res = $paginator->paginate(
$query,
1,
10,
[
Paginator::HINT_FETCH_JOIN_COLLECTION => true
]
);
return $this->render('article/list.html.twig', [
'pagination' => $res,
]);
}
}
?>
В результате пагинатор выполнит JOIN-запрос для выборки связанных коллекций, что позволит избежать дополнительных запросов при итерации по результатам.
Пример
Давайте сравним поведение пагинатора без хинта и с хинтом. Без хинта для каждой записи будет выполнен отдельный запрос на загрузку коллекции:
<?php
namespace AppController;
use AppEntityArticle;
use DoctrineORMEntityManagerInterface;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAttributeRoute;
use KnpComponentPagerPaginatorInterface;
class ArticleController extends AbstractController
{
#[Route('/articles/n-plus-one', name: 'article_n_plus_one')]
public function nPlusOne(EntityManagerInterface $em, PaginatorInterface $paginator): Response
{
$dql = 'SELECT a FROM AppEntityArticle a';
$query = $em->createQuery($dql);
$res = $paginator->paginate($query, 1, 10);
foreach ($res as $article) {
foreach ($article->getComments() as $comment) {
echo $comment->getText();
}
}
return new Response('done');
}
}
?>
Если у каждой статьи есть комментарии, то при итерации
будет выполнен дополнительный запрос для каждой статьи.
С хинтом HINT_FETCH_JOIN_COLLECTION коллекция
загружается сразу:
<?php
namespace AppController;
use AppEntityArticle;
use DoctrineORMEntityManagerInterface;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentRoutingAttributeRoute;
use KnpComponentPagerPaginatorInterface;
use KnpComponentPagerPaginator;
class ArticleController extends AbstractController
{
#[Route('/articles/fetch-join', name: 'article_fetch_join')]
public function fetchJoin(EntityManagerInterface $em, PaginatorInterface $paginator): Response
{
$dql = 'SELECT a FROM AppEntityArticle a';
$query = $em->createQuery($dql);
$res = $paginator->paginate(
$query,
1,
10,
[
Paginator::HINT_FETCH_JOIN_COLLECTION => true
]
);
foreach ($res as $article) {
foreach ($article->getComments() as $comment) {
echo $comment->getText();
}
}
return new Response('done');
}
}
?>
Результат выполнения кода:
done
Смотрите также
-
класс
Paginator,
который выполняет пагинацию данных -
метод
setFetchJoinCollection,
который включает fetch join для коллекций -
метод
getQuery,
который возвращает пагинированный запрос -
метод
setUseOutputWalkers,
который управляет обходом результатов