Представим проект, который либо не использует фреймворки, либо к нему подключена ORM DOCTRINE, но у используемого фреймворка/проекта нет адаптера для пагинации с Doctrine.
Но в один прекрасный момент пагинация становится нужна. И нужна она именно для Doctrine результата, ведь именно ее вы используете для работы с БД.
Для меня таким проектом стал проект на Kohana, который я поддерживаю. Отсутствие в этом фреймворке таких необходимых вещей, как адаптер для Doctrine и делает его устаревшим. Я использую модуль, который был вынужден допиливать сам (при чем не мало так), а мои pull request автор оригинального модуля даже не смотрит. Сообщество Kohana почти не существует. Вы один на один со своими проблемами в Kohana.
Но тут не об этом.
Этот пост можно было бы назвать еще так:
Вывод пагинации Doctrine используя Zend Paginator в Kohana Framework
И так, мне надо вывести пагинацию результата Doctrine, и для этого я вынужден подключать компоненты Zend. Добавляю в composer опытным путем, пока разбирался, следующие компоненты:
1 2 3 4 5 |
"zendframework/zend-paginator": "2.3.*", "zendframework/zend-view": "2.3.*", "zendframework/zend-filter": "2.3.*", |
Стоит пояснить, что сам Zend Paginator не может работать без Zend View, иначе как он будет рендерить шабло пагинации? Ну а Zend Filter был нужен при исполнении, где-то используется filter chain.
Далее я подключил следующие пространства имен в контроллер Kohana:
1 2 3 4 5 6 7 |
use Zend\Paginator\Paginator; use Zend\Paginator\Adapter\ArrayAdapter; use Zend\View\Renderer\PhpRenderer; use Zend\View\Helper\PaginationControl; use Zend\View\Resolver; |
ArrayAdapter использовался для тестов, в примерно таком виде.
1 2 3 |
$p = new Paginator(new ArrayAdapter([1,2,3,4,5])); |
Т.е. мы не будем делать findAll() и потом уже пытаться разбить весь огромный результат на страницы, мы же не дураки, верно? :)
Тут я хотел бы передать привет одному китайскому парню, чья статься в блоге помогла мне.
Далее ArrayAdapter был заменен на адаптер Doctrine. Ниже я привожу весь остальной код контроллера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
class Controller_Admin_Statistic extends Controller_Admin { private $entityName = Doctrine_Entities::PRICING_STATISTIC; public function before() { parent::before(); $this->repository = $this->em->getRepository($this->entityName); } public function action_index() { $page = $this->request->query("page") ? : 1; $limit = 10; //отвечает за рендеринг пхп $renderer = new PhpRenderer; //карта вьюшек $resolver = new Resolver\AggregateResolver(); $map = new Resolver\TemplateMapResolver(array( 'pagination' => APPPATH . 'views/paginator.phtml', )); $resolver->attach($map); //прикрепляем карту $renderer->setResolver($resolver); //ставим шаблон из карты PaginationControl::setDefaultViewPartial('pagination'); //получаем запрос и пагинацию $query = $this->repository->getSearchQuery([], ["id" => "DESC"]); $adapter = $this->repository->getPaginationAdapterDoctrine($query); $paginator = new Paginator($adapter); //настрйока пагинации $paginator->setCurrentPageNumber($page) ->setItemCountPerPage($limit); $paginator->setDefaultScrollingStyle('Sliding'); //вместо шаблона - рендерер $paginator->setView($renderer); $this->pageTitle = 'Настройки статистики прайсинга'; $this->content = View::factory('admin/statistic/main') ->set('errors', self::$errors) ->set('statistic', $paginator); } public function after() { $this->template->page_title = $this->pageTitle; $this->template->block_center = array($this->content); parent::after(); } } |
В оригинале в ZF2 пагинация выводится через View Helper метод, вот пример из документации.
1 2 3 4 5 |
<?php echo $this->paginationControl($this->paginator, 'Sliding', 'my_pagination_control', array('route' => 'application/paginator')); ?> |
Я не стал им пользоваться, иначе надо было бы инициализировать как-то View Helper и передать его в Kohana View. Но все равно внутри компонентов Zend использует PaginationControl, следовательно установка шаблона пагинации идет через него.
Сначала я пробовал передать напрямуб в него путь к шаблону, но эт оприводило к ошибке и это не верный путь.
Для рендеринга php шаблонов phtml используется класс View\PhpRenderer. Но что бы найти нужный шаблон он пользуется услугами класса View\Resolver\TemplateMapResolver. Именно поэтому я передал в него карту шаблонов, обозвал его pagination и передал в PhpRenderer.
Тепеь в PaginationControl мы можем использовать alias шаблона.
Далее я получаю Doctrine\Orm\Query из репозитория, без параметров, т.к. ищу все. Вот этот код находится у меня в базовом репозитории:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as PaginatorAdapter; use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator; ....... /** * adapter for paginator * @param type $query * @return \DoctrineORMModule\Paginator\Adapter\DoctrinePaginator */ public function getPaginationAdapterDoctrine($query) { return new PaginatorAdapter(new ORMPaginator($query)); } /** * standard search query * @param array $params * @return Query */ public function getSearchQuery($params = array(), $orderBy = array()) { $this->queryBuilder = $this->getEntityManager()->createQueryBuilder(); $this->queryBuilder->select('e')->from($this->getEntityName(), 'e'); $this->addOrder($orderBy); $query = $this->queryBuilder->getQuery(); return $query; } |
Его я и вызываю для получения Doctrine Adapter с уже построенным запросом, чтобы передать его в Zend Paginator.
1 2 3 4 5 6 |
//получаем запрос и пагинацию $query = $this->repository->getSearchQuery([], ["id" => "DESC"]); $adapter = $this->repository->getPaginationAdapterDoctrine($query); $paginator = new Paginator($adapter); |
Нижу идет код, в котором происходит настройка пагинации и в качестве View для пагинации мы устанавливаем настроенный PhpRenderer. В Zend MVC делаются все эти действия, но мы же не пользуемся им..
В конечном итоге я просто передаю экземпляр Paginator в view и вывожу его.
1 2 3 |
<div class="pagination-sm"><?php echo $statistic; ?></div> |
И да, я использую bootstrap. У меня уже был шаблон для bootstrap пагинации в Zend проекте, я скопировал его, удалил все упоминания $this->route (т.к. нет MVC) и он вполне подошел.
Вот код шаблона views/paginator.phtml.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<?php if ($this->pageCount): ?> <div class="pagination pagination-centered"> <ul class="pagination"> <!-- Previous page link --> <?php if (isset($this->previous)): ?> <li> <a href="?page=<?php echo $this->previous; ?>"> << </a> </li> <?php else: ?> <li class="disabled"> <a href="#"> << </a> </li> <?php endif; ?> <!-- Numbered page links --> <?php foreach ($this->pagesInRange as $page): ?> <?php if ($page != $this->current): ?> <li> <a href="?page=<?php echo $page; ?>"> <?php echo $page; ?> </a> </li> <?php else: ?> <li class="active"> <a href="#"><?php echo $page; ?></a> </li> <?php endif; ?> <?php endforeach; ?> <!-- Next page link --> <?php if (isset($this->next)): ?> <li> <a href="?page=<?php echo $this->next; ?>"> >> </a> </li> <?php else: ?> <li class="disabled"> <a href="#"> >> </a> </li> <?php endif; ?> </ul> </div> <?php endif; ?> |
UPD:
Приведенный выше вариант шаблона для пагинатора имеет недостатки
1) что если у нас страница должна иметь вид /page-:page/ (так настроен роут Segment)
2) что если у нас в GET есть еще параметры, например сортировка?
Чтобы сделать для варианта с GET нужен такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<?php if ($this->pageCount): ?> <ul class="pagination pull-right"> <!-- Previous page link --> <?php if (isset($this->previous)): ?> <li> <a href=" <?php $query = array_merge($this->query, ['page' => $this->previous]); // $params = array_merge($this->params, ['page' => $this->previous]); echo $this->url($this->route, $this->params, ['query' => $query]); ?>"> << </a> </li> <?php else: ?> <li class="disabled"> <a href="#"> << </a> </li> <?php endif; ?> <!-- Numbered page links --> <?php foreach ($this->pagesInRange as $page): ?> <?php if ($page != $this->current): ?> <li> <a href="<?php $query = array_merge($this->query, ['page' => $page]); echo $this->url($this->route, $this->params, ['query' => $query]); ?>"> <?php echo $page; ?> </a> </li> <?php else: ?> <li class="active"> <a href="#"><?php echo $page; ?></a> </li> <?php endif; ?> <?php endforeach; ?> <!-- Next page link --> <?php if (isset($this->next)): ?> <li> <a href="<?php $query = array_merge($this->query, ['page' => $this->next]); echo $this->url($this->route, $this->params, ['query' => $query]); ?>"> >> </a> </li> <?php else: ?> <li class="disabled"> <a href="#"> >> </a> </li> <?php endif; ?> </ul> <?php endif; ?> |
Ну а для варианта с параметром надо поменять мержи $query на $params в шаблоне
1 2 3 |
$params = array_merge($this->params, ['page' => $this->previous]); |
В контексте Zend 2 для установки query делаем так в шаблоне
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php echo $this->paginationControl( // the paginator object $paginator, // the scrolling style 'sliding', // the partial to use to render the control ['partial/paginator.phtml', 'default'], // the route to link to when a user clicks a control link ['route' => $route, 'query' => $query] ); ?> |
В других случая надо установить в Paginator соответствующие опции.