記事ページ

次に、特定の 1 つの記事を表示する別のブログページを作成します。

特定の 1 つの記事を取得し、それをテンプレートに渡す新しい render メソッドを作成する必要があります。このメソッドを HomePresenter に置くのは、記事について話しているのであって、トップページについてではないため、あまりきれいではありません。そこで、app/Presentation/Post/PostPresenter を作成しましょう。この Presenter もデータベースに接続する必要があるため、ここでもデータベース接続を要求するコンストラクタを記述します。

したがって、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);
	}
}

Presenter のマッピング の設定に従う正しい名前空間 App\Presentation\Post を指定することを忘れないでください。

renderShow メソッドは 1 つの引数、つまり表示する特定の記事の ID を必要とします。次に、この 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 アクションのテンプレートはまだ存在しません。この投稿へのリンクを開いてみることができます。Tracy は、Post/show.latte テンプレートがまだ存在しないため、エラーを表示します。別のエラーメッセージが表示される場合は、おそらく Web サーバーで 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 が自動的にリンクの生成を処理します。リンクは Home Presenter の default アクションを指します(default という名前のアクションは省略でき、自動的に補完されるため、n:href="Home:" と書くこともできます)。

3 行目は、すでにおなじみのフィルタを使用して日付の出力をフォーマットします。

4 行目は、HTML タグ <h1> でブログの タイトル を表示します。このタグには、おそらく知らない属性 (n:block="title") が含まれています。何をするか推測できますか?前の部分を注意深く読んだ場合、これが n:属性 であることはすでに知っています。これは、次と同等の別の例です。

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

簡単に言えば、このブロックは title という名前のブロックを再定義します。このブロックはすでにメイン layout テンプレート (/app/Presentation/@layout.latte:11) で定義されており、OOP でのメソッドのオーバーライドと同様に、メインテンプレートのこのブロックは完全にオーバーライドされます。したがって、ページの <title> には表示されている記事のタイトルが含まれるようになり、これには 1 つの単純な属性 n:block="title" を使用するだけで十分でした。素晴らしいですね。

テンプレートの 5 行目と最後の行は、特定の 1 つの記事の全文を表示します。

記事 ID の確認

誰かが URL の ID を変更して、存在しない id を入力したらどうなるでしょうか?ユーザーに「ページが見つかりません」という種類のわかりやすいエラーを提供する必要があります。そこで、PostPresenter の render メソッドを少し変更します。

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 が表示されます。これは開発には非常に便利です。両方のモードを表示したい場合は、Bootstrap.php ファイルの setDebugMode メソッドの引数を変更するだけです。

まとめ

記事を含むデータベースと、2 つのビューを持つ Web アプリケーションがあります。1 つ目はすべての記事の概要を表示し、2 つ目は特定の 1 つの記事を表示します。