Выводим новости

На прошлом занятии мы подключили xPDO и попробовали его в работе. Сегодня мы можем добавим в БД пару новостей и вывести их на отдельных страницах.

Для этого нам нужно будет научить контроллер News определять, что именно запросил пользователь: список новостей или отдельную новость. Это несложно, нужно только проверять, что указано в url после /news/.

Пишем контроллер Brevis\Controllers\News с вот такой инициализацией:


    public function initialize(array $params = array()) {
        if (empty($params)) {
            $this->redirect("/{$this->name}/");
        }
        elseif (!empty($params[0])) {
            $c = $this->core->xpdo->newQuery('Brevis\Model\News');
            if (is_numeric($params[0])) {
                $c->where(array('id' => $params[0]));
            }
            else {
                $c->where(array('alias' => $params[0]));
            }
            if ($news = $this->core->xpdo->getObject('Brevis\Model\News', $c)) {
                $alias = $news->get('alias');
                if (isset($params[1]) || $params[0] != $alias) {
                    $this->redirect("/{$this->name}/{$alias}");
                }
                else {
                    $this->item = $news;
                }
            }
            else {
                $this->redirect("/{$this->name}/");
            }
        }

        return true;
    }

Давайте разберёмся, что здесь происходит?

Первое правило осталось неизменным - если мы запрашиваем страницу /news без завершающего слеша, то происходит редирект на правильный адрес.

Дальше мы проверяем наличие первого параметра в $params[0]. Он может содержать или число (id новости) или не число (alias новости). Мы ищем нашу новость в зависимости от типа переменной.

Если нашли, но запрошенный параметр не соответствует alias новости - делаем редирект. Также мы делаем редирект на каноничный url, если параметров больше, чем 1. Под этот случай попадает и косая на конце адреса новости.

Если же все проверки пройдены, то мы сохраняем полученную новость в свойство $item контроллера, чтобы работать с ним дальше.

В итоге, верный адрес новости у нас - http://s1889.bez.modhost.pro/news/the-first и никакой другой. При попытке ввести что-то не то, будет редирект или на правильный адрес, или на раздел /news/.

Теперь в методе run нам нужно просто проверять $this->item на пустоту. Если в него что-то загружено, то выводить страницу отдельной новости. Если нет - то список всех новостей.

Вывод новости

Основной метод контроллера новостей я написал такой:


    /**
     * @return string
     */
    public function run() {
        if ($this->item) {
            $data = array(
                'title' => $this->item->get('pagetitle'),
                'pagetitle' => $this->item->get('pagetitle'),
                'longtitle' => $this->item->get('longtitle'),
                'content' => $this->item->get('text'),
            );
        }
        else {
            $data = array(
                'title' => 'Новости',
                'pagetitle' => 'Новости',
                'items' => $this->getItems(),
                'content' => '',
            );
        }

        return $this->template('news', $data, $this);
    }

Проверка наличия загруженной новости, и если её нет - то вывод списка новостей отдельным методом getItems().

В этом методе тоже ничего сложного:


    /**
     * Выбор последних новостей с обрезкой текста
     *
     * @return array
     */
    public function getItems() {
        $rows = array();
        $c = $this->core->xpdo->newQuery('Brevis\Model\News');
        $c->select($this->core->xpdo->getSelectColumns('Brevis\Model\News', 'News'));
        $c->sortby('id', 'DESC');
        $c->limit($this->limit);
        if ($c->prepare() && $c->stmt->execute()) {
            while ($row = $c->stmt->fetch(\PDO::FETCH_ASSOC)) {
                $cut = strpos($row['text'], "\n");
                if ($cut !== false) {
                    $row['text'] = substr($row['text'], 0, $cut);
                    $row['cut'] = true;
                }
                else {
                    $row['cut'] = false;
                }
                $rows[] = $row;
            }
        }
        else {
            $this->core->log('Не могу выбрать новости:' . print_r($c->stmt->errorInfo(), true));
        }

        return $rows;
    }

Обычный запрос через xPDO, с лимитом, указанным в News::limit. Из интересного только обрезка текста новости до первого встреченного символа переноса строки и добавление записи cut об этом в массив.

Теперь нужно добавить новый шаблон news.tpl и прописать в нём оформление.

Шаблон новостей

Здесь мы работаем с блоком content:


{block 'content'}
    {if $items}
        {foreach $items as $item}
            <div class="news">
                <h3><a href="/news/{$item.alias}">{$item.pagetitle}</a></h3>
                <p>{$item.text}</p>
                {if $item.cut}
                    <a href="/news/{$item.alias}" class="btn btn-default">Читать далее →</a>
                {/if}
            </div>
        {/foreach}
    {else}
        <a href="/news/">← Назад</a>
        {parent}
    {/if}
{/block}

Если есть переменная $items, то перебираем и оформляем её в цикле. Если нет - то выводим ссылку на все новости и контент страницы, как это указано в базовом шаблоне.

Таким образом, вот список новостей, вот одна новость, а вот коммит со всеми изменениями.

Подключаем Markdown

Не знаю, как вам, а вот лично мне не нравится, что в наших новостях нет переносов строки и весь текст идёт сплошняком.

Можно, конечно, добавить замену \n на br, но лучше сразу подключить обработчик разметки Markdown - отличный класс Parsedown. Тем более, что это займёт не более 5 минут.

Меняем composer.json:


  "require": {
    "php": ">=5.3.0",
    "fenom/fenom": "2.*",
    "xpdo/xpdo": "3.0.*@dev",
    "erusev/parsedown": "1.5.*"
  },

Добавляем в ядро загрузку парсера:


    public function getParser() {
        if (!$this->parser) {
            $this->parser = new Parsedown();
        }

        return $this->parser;
    }

Добавляем обработку текста парсером при выводе новости:


'content' => $this->core->getParser()->text($this->item->get('text')),

Вот и всё - проще некуда. Метод Core::getParser() возвращает нам объкт Parsedown, в котором мы используем text().

Коммит с изменениями.

Заключение

Мы сделали вывод списка новостей, отдельной новости и добавили в проект работу с разметкой Markdown.

Всё это заняло у нас минут 20 от силы.

На мой взгляд, на этом наш курс обучения можно смело заканчивать, потому что мы:

  1. Хорошо познакомились с ООП в PHP

  2. Написали собственное ядро сайта с простеньким роутером и контроллерами запросов

  3. Отрефакторили наш проект для соответствия PSR-4 и познакомились с Composer

  4. Подключили Fenom для шаблонизации

  5. Подключили xPDO для работы с БД, написали схему и сгенерировали модель

  6. Подключили Parsedown и вывели новости на сайте

Таким образом, мы создали полностью рабочий PHP сайт, который можно бесконечно расширять в любые стороны. Добавлять контроллеры, шаблоны, прописывать вывод информации из БД и всё это с минимальными усилиями.

Если у вас есть вопросы - задавайте. Если нужно рассказать еще что-то - предлагайте, я не против написать еще одно занятие.

Комментарии (2)
Семён Лобачевский
14.06.2015 16:17

Спасибо за уроки! Все ясно и понятно, осталось больше попрактиковаться, чтобы уложились новые знания. Ещё интересен вопрос по безопасности приложений, на что обращать больше внимания и может есть какие-то приёмы для проверки взломоустойчивости написанного кода.

bezumkinВасилий Наумкин
14.06.2015 16:55

Основное правило простое - всегда ожидать подвоха от юзера и проверять все входящие от него данные. Большинство возможных проблем по работе с БД мы сразу закрываем, используя xPDO. А вообще, тема эта очень обширная, советую начать отсюда.

ЕвгенийК
09.04.2022 03:35
Это хорошо, что такая возможность есть и может быть использована. А то тенденция, мания, что-то в по...
begoodco1
07.04.2022 05:49
Зарегистрировался чтобы выразить благодарность за доступное и подробное описание процесса. Была возм...
bezumkin
Василий Наумкин
18.03.2022 12:35
Авторизация есть из коробки, для входа в базовую админку. Можно установить через composer и собрать ...
bezumkin
Василий Наумкин
10.03.2022 12:08
Ну, я имел в виду, что по закону можно =) А в реальности с валютой очевидные проблемы.
Сергей Лелеко
04.03.2022 06:12
О как! не знал! спасибо
bezumkin
Василий Наумкин
01.03.2022 15:32
Я делал одного бота на botman/botman, но из-за своей универсальности конкретно с Телеграм на нём раб...
bezumkin
Василий Наумкин
25.02.2022 09:22
P.S. Кажется цитаты у тебя никак не стилизуются в комментариях... Спасибо, поправил!
Electrica
Михаил
08.02.2022 11:19
Работает!
Алексей
09.01.2019 10:55
Насыщенный год ) От души поздравляю с ДР! Счастья, успехов и семейного благополучия! Жаль лимит заме...
septa rose
28.05.2018 22:16
hmmm, keren abis