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}` のタグは `Post:show` のアクションを指す URL アドレスを作ります。引数として記事の 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` のアクションのテンプレートはまだありません。この記事へのリンクを開いてみてください。テンプレート `Post/show.latte` がまだないので、[Tracy |tracy:]がエラーを見せます。違うエラーのメッセージが見えるなら、たぶんウェブサーバーで `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>
```

ではテンプレートの部分をひとつずつ見ていきましょう。

1 行めは、トップページと同じように「content」という名前のブロックの定義を始めます。このブロックもやはり主の layout のテンプレートの中に表示されます。ご覧のとおり、終わりのタグ `{/block}` がありません。これは省略できます。

2 行めはブログの記事の一覧へ戻るリンクを与え、利用者が記事の一覧と個別の記事のあいだを行き来しやすくします。ここでも `n:href` の属性を使っているので、URL の生成は Nette が引き受けます。このリンクは `Home` プレゼンターの `default` アクションを指します(`n:href="Home:"` とも書けます。`default` というアクションの名前は省略でき、自動的に補われるからです)。

3 行めは、もうおなじみのフィルタで日付を整えます。

4 行めはブログの記事の *タイトル* を `<h1>` の HTML のタグの中に表示します。このタグには見慣れない属性(`n:block="title"`)が付いています。それが何をするか分かりますか。前の節を丁寧に読んでいれば、これが `n:属性` だともう分かるでしょう。これはもうひとつの例で、次と同じ意味です。

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

かみ砕いて言えば、このブロックは `title` という名前のブロックを定義し直します。このブロックは主の *layout* のテンプレート(`/app/Presentation/@layout.latte:11`)ですでに定義されていて、OOP のメソッドの上書きと同じように、このブロックが主のテンプレートのものを上書きします。ですからページの `<title>` には、表示されている記事のタイトルが入るようになります。しかも必要だったのは、この単純な `n:block="title"` の属性だけです。素晴らしいでしょう。

テンプレートの 5 行め、つまり最後の行は、その記事の中身をすべて表示します。


記事の ID を確かめる
============

誰かが URL の ID を書き換えて、存在しない `id` を入れたらどうなるでしょうか。利用者にはきれいな「ページが見つかりません」のエラーを見せるべきです。`PostPresenter` の render のメソッドを少し書き換えましょう。

```php .{file:app/Presentation/Post/PostPresenter.php}
public function renderShow(int $id): void
{
	$post = $this->database
		->table('posts')
		->get($id);
	if (!$post) {
		$this->error('Post not found');
	}

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

記事が見つからなければ、`$this->error(...)` を呼ぶことで分かりやすいメッセージの付いた 404 のページが表示されます。開発の環境(localhost)では、このエラーのページは見えないことに注意してください。代わりに Tracy が例外を詳しく見せてくれるので、開発にはとても便利です。両方のモードを試したいなら、`Bootstrap.php` の `setDebugMode` メソッドに渡す引数を変えるだけです。


まとめ
===

記事の入ったデータベースと、2 つのビューを持つウェブアプリケーションができました。ひとつはすべての記事の概観を、もうひとつはある記事ひとつを表示します。

{{priority: -1}}

個別記事のページ

ブログにもうひとつページを足しましょう。ある記事ひとつの中身を表示するページです。

ひとつの記事を取り出してテンプレートへ渡す、新しい 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} のタグは Post:show のアクションを指す URL アドレスを作ります。引数として記事の ID も渡します。

同じことを n:属性で短く書けます。

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

n:href の属性は {link} のタグと似ています。

とはいえ Post:show のアクションのテンプレートはまだありません。この記事へのリンクを開いてみてください。テンプレート Post/show.latte がまだないので、Tracyがエラーを見せます。違うエラーのメッセージが見えるなら、たぶんウェブサーバーで 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>

ではテンプレートの部分をひとつずつ見ていきましょう。

1 行めは、トップページと同じように「content」という名前のブロックの定義を始めます。このブロックもやはり主の layout のテンプレートの中に表示されます。ご覧のとおり、終わりのタグ {/block} がありません。これは省略できます。

2 行めはブログの記事の一覧へ戻るリンクを与え、利用者が記事の一覧と個別の記事のあいだを行き来しやすくします。ここでも n:href の属性を使っているので、URL の生成は Nette が引き受けます。このリンクは Home プレゼンターの default アクションを指します(n:href="Home:" とも書けます。default というアクションの名前は省略でき、自動的に補われるからです)。

3 行めは、もうおなじみのフィルタで日付を整えます。

4 行めはブログの記事の タイトル<h1> の HTML のタグの中に表示します。このタグには見慣れない属性(n:block="title")が付いています。それが何をするか分かりますか。前の節を丁寧に読んでいれば、これが n:属性 だともう分かるでしょう。これはもうひとつの例で、次と同じ意味です。

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

かみ砕いて言えば、このブロックは title という名前のブロックを定義し直します。このブロックは主の layout のテンプレート(/app/Presentation/@layout.latte:11)ですでに定義されていて、OOP のメソッドの上書きと同じように、このブロックが主のテンプレートのものを上書きします。ですからページの <title> には、表示されている記事のタイトルが入るようになります。しかも必要だったのは、この単純な n:block="title" の属性だけです。素晴らしいでしょう。

テンプレートの 5 行め、つまり最後の行は、その記事の中身をすべて表示します。

記事の ID を確かめる

誰かが URL の ID を書き換えて、存在しない id を入れたらどうなるでしょうか。利用者にはきれいな「ページが見つかりません」のエラーを見せるべきです。PostPresenter の render のメソッドを少し書き換えましょう。

public function renderShow(int $id): void
{
	$post = $this->database
		->table('posts')
		->get($id);
	if (!$post) {
		$this->error('Post not found');
	}

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

記事が見つからなければ、$this->error(...) を呼ぶことで分かりやすいメッセージの付いた 404 のページが表示されます。開発の環境(localhost)では、このエラーのページは見えないことに注意してください。代わりに Tracy が例外を詳しく見せてくれるので、開発にはとても便利です。両方のモードを試したいなら、Bootstrap.phpsetDebugMode メソッドに渡す引数を変えるだけです。

まとめ

記事の入ったデータベースと、2 つのビューを持つウェブアプリケーションができました。ひとつはすべての記事の概観を、もうひとつはある記事ひとつを表示します。