Добавляем новостям пагинацию

Это последнее занятие нашего курса, в котором мы закрепим знания по работе с Composer и чужими классами, добавлением постраничной навигации разделу новостей.
Первым делом выбираем что-нибудь попроще на https://packagist.org/search/?q=pagination - мне приглянулся второй пункт, с kilte/pagination. Добавляем его в наш composer.json и устанавливаем на сервере.
Теперь пишем новый метод в Brevis\Controller:

    /**
     * Возвращает массив с постраничной навигацией
     *
     * @param $totalItems
     * @param int $currentPage
     * @param int $itemsPerPage
     * @param int $neighbours
     *
     * @return array
     */
    public function getPagination($totalItems, $currentPage = 1, $itemsPerPage = 10, $neighbours = 2) {
        $pagination = new Pagination($totalItems, $currentPage, $itemsPerPage, $neighbours);

        return $pagination->build();
    }
А в начале файла указываем
use \Kilte\Pagination\Pagination as Pagination;
Всё, в нашем проекте уже есть постраничная навигация.
Дальше мы научим контроллер новостей её использовать, а шаблон - оформлять.

Меняем Controllers\News

В прошлый раз мы написали контроллер новостей, и научили его выводит заметки по alias или id. Для приятной постраничной навигации, от вывода по id придётся отказаться, потому что адреса, типа /news/2/, будут у нас отдельными страницами.
То есть, /news/the-first - это заметка в разделе новостей, а /news/3/ - это третья страница новостей.
Добавляем новые свойства в класс, для разбивки на страницы:

    public $limit = 2;
    public $page = 1;
    private $_offset = 0;
    private $_total = 0;
limit у нас уже был, я просто уменьшил его для наглядности.
Теперь меняем метод initialize():

public function initialize(array $params = array()) {
        if (empty($params)) {
            $this->redirect("/{$this->name}/");
        }
        // После адреса страницы указан параметр
        elseif (!empty($params[0])) {
            // Указано число, значит это номер страницы
            // Реагируем только на вторую страницу и дальше
            if (is_numeric($params[0]) && $params[0] > 1) {
                // После номера нет косой, или наоборот, указано что-то еще
                if (!isset($params[1]) || !empty($params[1])) {
                    // Делаем редирект на канонический адрес
                    $this->redirect("/{$this->name}/$params[0]/");
                }
                // В противном случае, сохраняем номер страницы и считаем,
                // сколько строк нужно пропустить от начала в выборке
                $this->page = (int)$params[0];
                $this->_offset = ($this->page - 1) * $this->limit;
            }
            // Указано не число - это alias новости
            else {
                // Здесь всё осталось как раньше, только кода поменьше
                $c = $this->core->xpdo->newQuery('Brevis\Model\News', array('alias' => $params[0]));
                if ($news = $this->core->xpdo->getObject('Brevis\Model\News', $c)) {
                    $this->item = $news;
                }
            }
            // Если не выбрана заметка и offset пустой, то делаем редирект в корень раздела
            // Это будет в случае, если в параметрах указана какая-то ерунда
            if (!$this->_offset && !$this->item) {
                $this->redirect("/{$this->name}/");
            }
        }

        return true;
    }
В методе getItems() добавляем пропуск результатов от начала - offset, подсчёт общего количества строк и редирект в корень раздела, если указана несуществующая страница.

        $c = $this->core->xpdo->newQuery('Brevis\Model\News');
        // Считаем общее количество новостей
        $this->_total = $this->core->xpdo->getCount('Brevis\Model\News');
        // Если пропуск от начала больше, чем общее количество - указана несуществующая страница
        if ($this->_offset >= $this->_total) {
            // Редиректим в корень раздела
            $this->redirect("/{$this->name}/");
        }
        $c->select($this->core->xpdo->getSelectColumns('Brevis\Model\News', 'News'));
        $c->sortby('id', 'DESC');
        // А здесь, помимо лимита, добавляем и пропуск от начала
        $c->limit($this->limit, $this->_offset);
Остался последний штрих - нам нужно получить массив в пагинацией после вызова getItems(). Это мы делаем в методе run()

            $data = array(
                'title' => 'Новости',
                'pagetitle' => 'Новости',
                'items' => $this->getItems(),
                // Пагинация с нашими свойствами: total, page и limit
                'pagination' => $this->getPagination($this->_total, $this->page, $this->limit),
                'content' => '',
            );
Осталось оформить странички в шаблоне.

Меняем news.tpl

Как указано в документации kilte/pagination, компонент должен вернуть нам массив номеров страниц с указанием их типа: текущая, предыдущая и т.д.
Так что, просто добавляем прокрутку этого массива с оформлением в наш шаблон:

        {if $pagination}
            <nav>
                <ul class="pagination">
                    {foreach $pagination as $page => $type}
                        {switch $type}
                            {case 'first'}
                                <li><a href="/news/">«</a></li>
                            {case 'last'}
                                <li><a href="/news/{$page}/">»</a></li>
                            {case 'less', 'more'}
                            {case 'current'}
                                <li class="active"><a href="/news/{$page}/">{$page}</a></li>
                            {case default}
                                <li><a href="/news/{$page}/">{$page}</a></li>
                        {/switch}
                    {/foreach}
                </ul>
            </nav>
        {/if}
Думаю, тут всё понятно без комментариев. Обратите только внимание на то, что я пропускаю страницы more и less, потому что, на мой взгляд, они не нужны.
Вот и всё, наша постраничная навигация уже работает - можно проверять!

Заключение

Думаю, после прохождения курса, вам стало ясно, насколько легко и просто можно строить сайты самостоятельно, используя свои и чужие готовые решения, без CMS и тяжелых фреймворков.
Я ни в коем случае не призываю вас отказываться от них и делать всё самостоятельно, нет! Просто знайте, что где-то там внутри примерно такой же код, как мы написали с вами на этих курсах. Конечно, обычно он более продвинутый и крутой, но базовые принципы везде одинаковы.
Надеюсь, мои уроки помогут вам разбираться в PHP немного лучше. Если у вас остались вопросы - задавайте!
Оказалось, что я забыл написать еще один урок - вот и он.