Я настраиваю проект Symfony, отображающий посты в блогах с разбивкой на страницы, с интерфейсом администратора для управления всем этим. В этом администраторе также можно «выделить» одну из этих общедоступных записей в блоге, чтобы она отображалась на первой позиции только на первой странице.
Мне нужно одинаковое количество элементов на каждой странице, и это проблема, с которой я сталкиваюсь.
Я использую ПейджерФанта, поэтому я создал AbstractRepository с функцией "paginate".
protected function paginate(QueryBuilder $qb, $limit = 20, $offset = 0)
{
if ($limit == 0) {
throw new \LogicException('$limit must be greater than 0.');
}
//Instantiates the pagination object with the result of the query
$pager = new Pagerfanta(new DoctrineORMAdapter($qb));
//Sets max data per page
$pager->setMaxPerPage($limit);
//Sets the current page
$pager->setCurrentPage($offset);
return $pager;
}
В моем репозитории сообщений в блогах я сделал построитель запросов, чтобы получить все общедоступные сообщения в блогах, кроме выделенного, потому что я могу получить его другим способом, чтобы отобразить его поверх первой страницы.
public function findAllVisible($id, $limit = 3, $offset = 1, $order = 'DESC')
{
$qb = $this
->createQueryBuilder('a')
->select('a')
->where('a.website = :website')
->setParameter('website', 'blog')
->andWhere('a.public = :public')
->setParameter('public', true)
->andWhere('a.id != :id')
->setParameter('id', $id)
->orderBy('a.dateInsert', $order)
;
return $this->paginate($qb, $limit, $offset);
}
Итак, я сначала попытался изменить ограничение и смещение в соответствии с текущей страницей, но логически потерял один элемент между первой и второй страницей.
Затем я попытался включить выделенный блог в построитель запросов, но я не знаю, как определить его как первый результат, если текущая страница является первой.
Любая идея о том, как заставить первый результат быть выделенным сообщением в блоге только на первой странице? Или другой чистый и подходящий способ отобразить ожидаемые результаты?




Я отвечаю перед собой, потому что мне удалось сделать то, что мне было нужно. В случае, если кто-то имеет дело с той же проблемой, вот как я это сделал.
Я больше не использую PagerFanta, но пользуюсь инструментом Doctrine Пагинатор.
Вместо того, чтобы исключить выделенную статью из моего запроса, я заменил свой первоначальный ORDER BY на a.id = :highlightedId DESC, a.dateInsert DESC.
Теперь он работает, как ожидалось.
Вот моя новая функция репозитория:
/**
* Finds all visible articles
*
* @param int $highlightedTipId the highlighted tip id
* @param int $page current page
* @param int $limit max items per page
*
* @throws InvalidArgumentException
* @throws NotFoundHttpException
*
* @return Paginator
*/
public function findAllVisible($highlightedTipId, $limit = 3, $page)
{
if (!is_numeric($page)) {
throw new InvalidArgumentException('$page value is incorrect (' . $page . ').');
}
if ($page < 1) {
throw new NotFoundHttpException('Page not found');
}
if (!is_numeric($limit)) {
throw new InvalidArgumentException('$limit value is incorrect (' . $limit . ').');
}
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
"SELECT
a,
CASE WHEN a.id = :id THEN 1 ELSE 0 END AS HIDDEN sortCondition
FROM App\Entity\Item a
WHERE
a INSTANCE OF App\Entity\TipArticle
AND
a.website = :website
AND
a.public = :public
ORDER BY
sortCondition DESC,
a.dateInsert DESC
"
);
$query->setParameter(':website', 'blog');
$query->setParameter(':public', true);
$query->setParameter(':id', $highlightedTipId);
$firstResult = ($page - 1) * $limit;
$query
->setFirstResult($firstResult)
->setMaxResults($limit);
$paginator = new Paginator($query);
if (($paginator->count() <= $firstResult) && $page != 1) {
throw new NotFoundHttpException('Page not found');
}
return $paginator;
}
Несколько слов об этой строке CASE WHEN a.id = :id THEN 1 ELSE 0 END AS HIDDEN sortCondition: это единственный способ сделать ORDER BY a.id = :highlightedId DESC с Doctrine. Как видите, я сделал DQL, но это также возможно с QueryBuilder.
Надеюсь, это поможет! :)
Привет, разместил несколько комментариев, но удалил их в пользу ответа. Не стесняйтесь использовать его, если хотите. Я подумал, что ваш ответ довольно беспорядочный, поскольку вы смешиваете «нормальные» места. Например, вам не нужно использовать EntityManager в репозитории, и вам действительно не следует создавать запросы вручную (об этом позаботятся и QueryBuilder, и Criteria). Так что подумал, что я предоставлю вам свой ответ. Впрочем, держитесь ;-)
Красиво, молодец. Если я могу предложить несколько советов, хотя. В репо вам не нужно получать ObjectManager ($this->getEntityManager()), так как репо уже для типа сущности, в данном случае Item. Вместо этого вы должны использовать Критерии. практические примеры документов
У вас должен быть ObjectManager в любом контроллере, который у вас есть, поэтому вы должны сделать:
$items = $this-getObjectManager()->getRepository(Item::class)->findAllVisible($params)
Для использования Paginator вы должны использовать QueryBuilder, с выражениями, как здесь
В качестве практического примера, мой indexAction:
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\Tools\Pagination\Paginator as OrmPaginator;
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as OrmAdapter;
use Zend\Paginator\Paginator;
public function indexAction()
{
// get current page, defaults to 1
$page = $this->params()->fromQuery('page', 1);
// get current page size, defaults to 10
$pageSize = $this->params()->fromQuery('pageSize', 10);
// get ordering, defaults to 'createdAt'
$orderBy = $this->params()->fromQuery('orderBy', 'createdAt');
// get order direction, defaults to Criteria::DESC
$orderDirection = ($this->params()->fromQuery('orderDirection') === Criteria::ASC)
? Criteria::ASC
: Criteria::DESC;
$criteria = new Criteria();
$criteria->setFirstResult($page * $pageSize);
$criteria->setMaxResults($pageSize);
$criteria->orderBy([$orderBy => $orderDirection]);
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $this->getObjectManager()->createQueryBuilder();
$queryBuilder->select('a')->from(Article::class, 'a');
$queryBuilder->addCriteria($criteria);
$paginator = new Paginator(new OrmAdapter(new OrmPaginator($queryBuilder)));
$paginator->setCurrentPageNumber($page); // set current page
$paginator->setItemCountPerPage($pageSize); // set item count per page
return [
'paginator' => $paginator,
'queryParams' => $this->params()->fromQuery(), // pass these for your pagination uri's
];
}
ПРИМЕЧАНИЕ. В приведенном выше примере $this является экземпляром контроллера Zend Framework, где ->fromQuery возвращает (если присутствует) заданный ключ из бита запроса, если это URI, иначе возвращает второй параметр по умолчанию (или null). Вы должны сделать что-то подобное.
Молодец, что разобрался. Не могли бы вы также опубликовать код, который является вашим ответом? Просто смена пагинатора на самом деле не распространяется на сайт вопросов и ответов по коду ;-) Спасибо.