Nette Documentation Preview

syntax
Страница поста
**************

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


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

`PostPresenter` мог бы выглядеть так:

```php .{file:app/Presentation/Post/PostPresenter.php}
<?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`, которое подчиняется настройке [сопоставления презентеров |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7].

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

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

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

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


То же самое можно записать короче с помощью n:атрибута:

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

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



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

Создадим шаблон `Post/show.latte` с таким содержимым:

```latte .{file:app/Presentation/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`, Nette само позаботится о генерации ссылок. Ссылка указывает на действие `default` презентера `Home` (можно также написать `n:href="Home:"`, потому что действие с именем `default` может быть опущено, оно дополнится автоматически).

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

Четвертая строка отображает *заголовок* блога в HTML-теге `<h1>`. Этот тег содержит атрибут, который вы, возможно, не знаете (`n:block="title"`). Угадаете, что он делает? Если вы читали предыдущую часть внимательно, то уже знаете, что это `n:атрибут`. Это еще один их пример, который эквивалентен:

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

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

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


Проверка ID поста
=================

Что произойдет, если кто-то изменит ID в URL и вставит какой-то несуществующий `id`? Мы должны предложить пользователю красивую ошибку типа "страница не найдена". Изменим немного метод render в `PostPresenter`:

```php .{file:app/Presentation/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 с понятным сообщением. Обратите внимание, что в режиме разработки (localhost) вы эту страницу ошибки не увидите. Вместо этого покажется Tracy с деталями об исключении, что довольно удобно для разработки. Если мы хотим отобразить оба режима, достаточно просто изменить аргумент метода `setDebugMode` в файле `Bootstrap.php`.


Резюме
======

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

{{priority: -1}}
{{sitename: Nette Quickstart}}

Страница поста

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

Нам нужно создать новый метод render, который получит одну конкретную статью и передаст ее в шаблон. Иметь этот метод в 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, 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? Мы должны предложить пользователю красивую ошибку типа „страница не найдена“. Изменим немного метод render в 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.

Резюме

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