Nette Documentation Preview

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

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


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

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

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

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

Итоги

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