Страница одной записи
Добавим в наш блог ещё одну страницу, которая будет показывать содержимое одной конкретной записи.
Нам нужно создать новый метод отрисовки, который получит одну
конкретную запись и передаст её в шаблон. Помещать этот метод в
HomePresenter не очень изящно, потому что речь об одной записи, а не о
главной странице. Так что создадим новый класс PostPresenter и
поместим его в app/Presentation/Post/. Этому презентеру тоже понадобится
соединение с базой данных, так что мы добавим конструктор, который его
затребует.
PostPresenter мог бы выглядеть так:
<?php
namespace App\Presentation\Post;
use Nette;
use Nette\Application\UI\Form;
final class PostPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function renderShow(int $id): void
{
$this->template->post = $this->database
->table('posts')
->get($id);
}
}
Нам нельзя забыть указать правильное пространство имён
App\Presentation\Post, которое зависит от настройки отображения
презентеров.
Метод renderShow требует один аргумент – ID записи, которую нужно
показать. Затем он загружает запись из базы данных и передаёт её в
шаблон.
В шаблон Home/default.latte добавим ссылку на действие Post:show:
...
<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2>
...
Тег {link} порождает URL-адрес, указывающий на действие
Post:show. Он также передаёт ID записи аргументом.
То же самое можно записать короче с помощью n:атрибута:
...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...
Атрибут n:href похож на тег {link}.
Однако шаблона для действия Post:show пока не существует. Мы можем
попробовать открыть ссылку на эту запись. Tracy
покажет ошибку, потому что шаблона Post/show.latte ещё нет. Если вы
увидите другое сообщение об ошибке, вам, вероятно, нужно включить
mod_rewrite на своём веб-сервере.
Итак, создадим Post/show.latte с таким содержимым:
{block content}
<p><a n:href="Home:default">← назад к списку записей</a></p>
<div class="date">{$post->created_at|date:'F j, Y'}</div>
<h1 n:block="title">{$post->title}</h1>
<div class="post">{$post->content}</div>
Теперь разберём отдельные части шаблона.
Первая строка начинает определение блока с именем “content”, точно как
на главной странице. Этот блок снова будет отображён внутри главного
шаблона макета. Как видите, закрывающего тега {/block} нет. Он
необязателен.
Вторая строка даёт обратную ссылку на список записей блога, позволяя
пользователю легко перемещаться между списком записей и конкретной
записью. Мы снова используем атрибут n:href, так что порождением URL
займётся Nette. Ссылка указывает на действие default презентера
Home (можно было бы написать и n:href="Home:", потому что имя
действия default можно опустить, оно добавится автоматически).
Третья строка форматирует дату фильтром, с которым мы уже знакомы.
Четвёртая строка выводит заголовок записи блога внутри HTML-тега
<h1>. У этого тега есть атрибут, который вы, возможно, не
узнаете (n:block="title"). Догадаетесь, что он делает? Если вы
внимательно читали предыдущий раздел, вы уже знаете, что это
n:атрибут. Это ещё один пример, равносильный записи:
{block title}<h1>{$post->title}</h1>{/block}
Проще говоря, этот блок переопределяет блок с именем title. Этот
блок уже определён в главном шаблоне layout
(/app/Presentation/@layout.latte:11), и, точно как переопределение метода в ООП,
этот блок переопределяет тот, что в главном шаблоне. Поэтому
<title> страницы теперь будет содержать заголовок отображаемой
записи, и всё, что нам для этого понадобилось, – простой атрибут
n:block="title". Здорово, правда?
Пятая и последняя строка шаблона выводит полное содержимое конкретной записи.
Проверка ID записи
Что произойдёт, если кто-нибудь изменит ID в URL и вставит
несуществующий id? Нам стоит показать пользователю понятную
ошибку “страница не найдена”. Немного изменим метод отрисовки в
PostPresenter:
public function renderShow(int $id): void
{
$post = $this->database
->table('posts')
->get($id);
if (!$post) {
$this->error('Запись не найдена');
}
$this->template->post = $post;
}
Если запись найти не удастся, вызов $this->error(...) покажет
страницу 404 с понятным сообщением. Обратите внимание, что в среде
разработки (на localhost) вы эту страницу с ошибкой не увидите. Вместо этого
Tracy покажет исключение со всеми подробностями, что при разработке
довольно удобно. Если вы хотите проверить оба режима, просто измените
аргумент, передаваемый методу setDebugMode в Bootstrap.php.
Итоги
У нас есть база данных с записями и веб-приложение с двумя представлениями: первое показывает обзор всех записей, а второе – одну конкретную запись.