Страница отдельной записи

Давайте добавим в наш блог еще одну страницу, на которой будет отображаться содержимое одной конкретной записи блога.

Нам нужно создать новый метод render, который будет получать одну конкретную запись блога и передавать её в шаблон. Иметь это представление в HomePresenter не очень приятно, потому что речь идёт о записи в блоге, а не о главной странице. Итак, давайте создадим новый класс PostPresenter и поместим его в app/UI/Post/. Ему потребуется соединение с базой данных, поэтому снова поместите туда код внедрения зависимости.

PostPresenter должен выглядеть следующим образом:

<?php
namespace App\UI\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\UI\Post для нашего презентера. Это зависит от presenter mapping.

Метод 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:attribute:

...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...

Атрибут n:href аналогичен тегу {link}.

Шаблон для действия Post:show ещё не существует. Мы можем открыть ссылку на этот пост. Tracy покажет ошибку о том, что app/UI/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:'j.m.Y'}</div>

<h1 n:block="title">{$post->title}</h1>

<div class="post">{$post->content}</div>

Рассмотрим некоторые моменты.

Первая строка начинает определение именованного блока под названием content, которые мы видели ранее. Он будет отображаться в шаблоне макета.

Вторая строка содержит обратную ссылку на список постов блога, чтобы пользователь мог плавно перемещаться по нашему блогу вперед и назад. Мы снова используем атрибут n:href, поэтому Nette позаботится о генерации URL для нас. Ссылка указывает на действие default презентера Home (можно просто написать n:href="Home:", так как действие default может быть опущено).

Третья строка форматирует временную метку публикации с помощью фильтра date, как мы уже знаем.

Четвертая строка отображает заголовок записи блога в виде заголовка <h1>. Есть часть, с которой вы, возможно, не знакомы, это n:block="title". Можете ли вы догадаться, что она делает? Если вы внимательно читали предыдущие части, мы упоминали n: атрибуты. Вот ещё один пример. Это эквивалентно:

{block title}<h1>{$post->title}</h1>{/block}

Проще говоря, он переопределяет блок под названием title. Блок определен в шаблоне макета (/app/UI/@layout.latte:11) и, как и в случае с переопределением ООП, он переопределяется здесь. Поэтому <title> страницы будет содержать заголовок отображаемого поста. Мы переопределили заголовок страницы, и всё, что нам было нужно, это n:block="title". Здорово, да?

Пятая и последняя строка шаблона отображает полное содержание вашего поста.

Проверка идентификатора поста

Что произойдет, если кто-то изменит URL и вставит несуществующий id? Мы должны предоставить пользователю красивую страницу ошибки «страница не найдена». Давайте обновим метод render в файле PostPresenter.php:

public function renderShow(int $id): void
{
	$post = $this->database
		->table('posts')
		->get($id);
	if (!$post) {
		$this->error('Страница не найдена!');
	}

	$this->template->post = $post;
}

Если пост не может быть найден, вызов $this->error(...) покажет страницу 404 с красивым и понятным сообщением. Обратите внимание, что в режиме разработки вы не увидите страницу ошибки. Вместо этого Tracy покажет исключение с полной информацией, что довольно удобно для разработки. Вы можете проверить оба режима, просто изменив значение, передаваемое в setDebugMode в Bootstrap.php.

Подведём итог

У нас есть база данных с записями блога и веб-приложение с двумя представлениями: первое отображает сводку всех последних записей, а второе — одну конкретную запись.