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

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

Нам нужно создать новый метод отрисовки, который получит одну конкретную запись и передаст её в шаблон. Помещать этот метод в 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.

Итоги

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