個別記事のページ
ブログにもうひとつページを足しましょう。ある記事ひとつの中身を表示するページです。
ひとつの記事を取り出してテンプレートへ渡す、新しい 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.php
の setDebugMode メソッドに渡す引数を変えるだけです。
まとめ
記事の入ったデータベースと、2 つのビューを持つウェブアプリケーションができました。ひとつはすべての記事の概観を、もうひとつはある記事ひとつを表示します。