Для этого нам нужно будет научить контроллер 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 контроллера, чтобы работать с ним дальше.
В итоге, верный адрес новости у нас — 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 от силы.
На мой взгляд, на этом наш курс обучения можно смело заканчивать, потому что мы:
- Хорошо познакомились с ООП в PHP
- Написали собственное ядро сайта с простеньким роутером и контроллерами запросов
- Отрефакторили наш проект для соответствия PSR-4 и познакомились с Composer
- Подключили Fenom для шаблонизации
- Подключили xPDO для работы с БД, написали схему и сгенерировали модель
- Подключили Parsedown и вывели новости на сайте
Если у вас есть вопросы — задавайте. Если нужно рассказать еще что-то — предлагайте, я не против написать еще одно занятие.
← Следующая заметка
Добавляем новостям пагинацию
Добавляем новостям пагинацию
Предыдущая заметка →
Подключаем xPDO
Подключаем xPDO
Все ясно и понятно, осталось больше попрактиковаться, чтобы уложились новые знания.
Ещё интересен вопрос по безопасности приложений, на что обращать больше внимания и может есть какие-то приёмы для проверки взломоустойчивости написанного кода.
А вообще, тема эта очень обширная, советую начать отсюда.