Nette Documentation Preview

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

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


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

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

```php .{file:app/UI/Post/PostPresenter.php}
<?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 |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7].

Метод `renderShow` требует один аргумент — ID отображаемого поста. Затем он загружает этот пост из базы данных и передает результат в шаблон.

В шаблоне `Home/default.latte` мы добавляем ссылку на действие `Post:show`:

```latte .{file:app/UI/Home/default.latte}
...
<h2><a href="{link Post:show $post->id}">{$post->title}</a></h2>
...
```

Тег `{link}` генерирует адрес URL, который указывает на действие `Post:show`. Этот тег также передает ID поста в качестве аргумента.


То же самое мы можем написать коротко, используя n:attribute:

```latte .{file:app/UI/Home/default.latte}
...
<h2><a n:href="Post:show $post->id">{$post->title}</a></h2>
...
```

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



Шаблон для действия `Post:show` ещё не существует. Мы можем открыть ссылку на этот пост. [Tracy |tracy:] покажет ошибку о том, что `app/UI/Post/show.latte` не существует. Если вы видите любой другой отчёт об ошибке, вероятно, вам нужно включить mod_rewrite в вашем веб-сервере.

Поэтому мы создадим `Post/show.latte` с таким содержанием:

```latte .{file:app/UI/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: атрибуты`. Вот ещё один пример. Это эквивалентно:

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

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

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


Проверка идентификатора поста .[#toc-checking-post-id]
======================================================

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

```php .{file:app/UI/Post/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`.


Подведём итог .[#toc-summary]
=============================

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

{{priority: -1}}
{{sitename: Быстрый старт с Nette}}

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

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

Нам нужно создать новый метод 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 и вставит несуществующий postId? Мы должны предоставить пользователю красивую страницу ошибки «страница не найдена». Давайте обновим метод 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.

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

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