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

Это последнее занятие нашего курса, в котором мы закрепим знания по работе с 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 немного лучше. Если у вас остались вопросы - задавайте!
Оказалось, что я забыл написать еще один урок - вот и он.

8 комментариев

Семён Лобачевский
Спасибо за финальный урок и курс в целом! Стало более понятно как устроены php фреймворки и любимый MODx в частности)
Василий Наумкин
На здоровье!
Похоже, ты единственный, кто всё прочитал =)
Перетягин Илья
Все впереди ))) Я первый урок осиливал около 3-4 дней, завтра буду читать второй, но если они по сложности идут по нарастающей, то до третьего не добраться ))))
Василий Наумкин
Да ладно тебе! Делай как написано и через какое-то время придёт понимание. Тем более, всё закоммичено на GitHub, поэтапно.
Я так разработку компонентов для MODX осваивал - просто методом тыка повторял чужие разработки, пока не дошло, откуда какие файлы и зачем берутся.
Перетягин Илья
Тут больше проблема в самом коде, так как программирование больше хобби, то и опыта мало в этом, и некоторые моменты не понятны, приходиться вычитывать на других ресурсах, от сюда и время изучения.
На самом деле все толково написано, просто понадобиться больше времени чем ожидалось.
Василий Наумкин
приходиться вычитывать на других ресурсах
Так спрашивай здесь, расскажу.
Перетягин Илья
Да там вопросы банальные, даже ка кто стыдно спрашивать..... Сейчас напишу один в первой теме
Алексей
Ждём урока по Ajax )
bezumkin.ru
Personal website of Vasily Naumkin
Прямой эфир
Александр Наумов
23.07.2024, 00:20:37
Василий, спасибо большое!!
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.
Василий Наумкин
22.01.2024, 07:43:20
Давай-давай!
Василий Наумкин
24.12.2023, 14:26:13
Спасибо!