ブログのトップページ

最近の記事を並べるトップページを作りましょう。

始める前に、Model-View-Presenter の設計のパターン(MVCに似ています)の基本くらいは知っておくとよいでしょう。

  • Model – データを扱う層です。アプリケーションのほかの部分から完全に切り離されています。やり取りするのはプレゼンターとだけです。
  • View – 表側の層です。求められたデータをテンプレートで描き、利用者に見せます。
  • Presenter(あるいは Controller) – つなぎの層です。プレゼンターは Model と View を結び付けます。リクエストを扱い、Model にデータを求め、それを View へ渡します。

私たちのブログのような単純なアプリケーションでは、model の層はまるごとデータベースへの問い合わせだけになります。そのために余分な PHP のコードはまだ要りません。ですから手はじめにプレゼンターとテンプレートだけを作ります。Nette ではプレゼンターごとに自分のテンプレートがあるので、同時に作っていきます。

Adminer でデータベースを作る

データを保存するには MySQL のデータベースを使います。ウェブの開発者にもっともよく選ばれているからです。とはいえ使いたくないなら、お好きなデータベースを遠慮なく選んでください。

ブログの記事を保存するデータベースの構造を用意しましょう。ごく簡単に始められます。記事のテーブルをひとつ作るだけです。

データベースを作るには Adminerをダウンロードするか、お好みのほかのデータベース管理の道具を使えます。

Adminer を開いて、quickstart という新しいデータベースを作りましょう。

posts という新しいテーブルを作り、次の列を足します。

  • id int。autoincrement(AI)にチェックを入れます
  • title varchar、長さ 255
  • content text
  • created_at timestamp

次のようになるはずです。

CREATE TABLE `posts` (
	`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
	`title` varchar(255) NOT NULL,
	`content` text NOT NULL,
	`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARSET=utf8;

InnoDB の保存エンジンを使うことがとても大事です。理由はあとで分かります。今のところは、それを選んで保存を押してください。

アプリケーションから直接記事を足せるようにする前に、見本のブログの記事をいくつか手で足してみましょう。

INSERT INTO `posts` (`id`, `title`, `content`, `created_at`) VALUES
(1,	'Article One',	'Lorem ipsum dolor one',	CURRENT_TIMESTAMP),
(2,	'Article Two',	'Lorem ipsum dolor two',	CURRENT_TIMESTAMP),
(3,	'Article Three',	'Lorem ipsum dolor three',	CURRENT_TIMESTAMP);

データベースにつなぐ

データベースができて記事もいくつか入ったので、真新しいページにそれを表示するときです。

まず、どのデータベースを使うのかをアプリケーションに伝える必要があります。データベース接続の設定は config/common.neon ファイルで、DSNと資格情報を使って行います。次のようになるはずです。

database:
	dsn: 'mysql:host=127.0.0.1;dbname=quickstart'
	user: *ここにあなたのユーザー名を入れます*
	password: *ここにあなたのデータベースのパスワードを入れます*

このファイルを編集するときは字下げに気をつけてください。NEON 形式は空白もタブも受け付けますが、両方を混ぜてはいけません。Web Project の既定の設定ファイルはタブを使っています。

データベース接続を注入する

記事を並べるプレゼンター HomePresenter には、データベース接続が要ります。それを受け取るために、次のようにコンストラクタを使います。

<?php
namespace App\Presentation\Home;

use Nette;

final class HomePresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private Nette\Database\Explorer $database,
	) {
	}

	// ...
}

データベースから記事を読み込む

ではデータベースから記事を取り出して、HTML のコードを描くテンプレートへ渡しましょう。そのためにいわゆる render のメソッドがあります。

public function renderDefault(): void
{
	$this->template->posts = $this->database
		->table('posts')
		->order('created_at DESC')
		->limit(5);
}

これでプレゼンターには renderDefault() というひとつの render のメソッドができ、データベースのデータをテンプレート(View)へ渡します。テンプレートは app/Presentation/{プレゼンター名}/{ビュー名}.latte にあるので、この場合は app/Presentation/Home/default.latte です。テンプレートでは、データベースから取り出した記事の入った $posts という変数が使えるようになります。

テンプレート

ウェブサイト全体には主のテンプレート(layout と呼び、ヘッダー、スタイル、フッターなどが入っています)があり、ビューごとに専用のテンプレート(たとえばブログの記事を表示するもの)があって、主のテンプレートの一部を上書きできます。

既定では layout のテンプレートは app/Presentation/@layout.latte にあり、次のものを含みます。

...
{include content}
...

{include content} のタグは、content という名前のブロックを主のテンプレートに差し込みます。このブロックはビューごとのテンプレートで定義します。私たちの場合、Home/default.latte ファイルを次のように書き換えます。

{block content}
	Hello World
{/block}

これで contentブロックが定義され、主の layout に差し込まれます。ブラウザをもう一度読み込み直すと、「Hello World」という文のページが見えます(ソースコードでは、@layout.latte で定義された HTML のヘッダーとフッターも付いています)。

ではブログの記事を表示しましょう。テンプレートを次のように書き換えます。

{block content}
	<h1>私のブログ</h1>

	{foreach $posts as $post}
	<div class="post">
		<div class="date">{$post->created_at|date:'F j, Y'}</div>

		<h2>{$post->title}</h2>

		<div>{$post->content|truncate:256}</div>
	</div>
	{/foreach}
{/block}

ブラウザを読み込み直すと、ブログの記事の一覧が見えます。この一覧はまだ見た目も色味も味気ないので、きれいな CSS のスタイルwww/css/style.css ファイルに足して、layout から読み込んでかまいません。

	...
	<link rel="stylesheet" href="{$basePath}/css/style.css">
</head>
...

{foreach} のタグは、$posts 変数でテンプレートへ渡されたすべての記事を回り、記事ごとに HTML のコードの断片を表示します。PHP のコードとまったく同じように振る舞います。

|date: の書き方はフィルタと呼ばれます。フィルタは出力を整えるのに使います。このフィルタは日付(たとえば 2013-04-12)をもっと読みやすい形(April 12, 2013)に変えます。|truncate のフィルタは文字列を指定した長さの上限まで切り詰め、切り詰めたときは終わりに三点リーダ(…)を足します。ここは概要なので、記事の中身をすべて表示する意味はありません。そのほかの既定のフィルタはドキュメントにありますし、必要なら自分で作れます。

もうひとつあります。さっきのコードはもっと短く単純にできます。*Latte のタグ* を n:属性 に置き換えることでそうします。

{block content}
	<h1>私のブログ</h1>

	<div n:foreach="$posts as $post" class="post">
		<div class="date">{$post->created_at|date:'F j, Y'}</div>

		<h2>{$post->title}</h2>

		<div>{$post->content|truncate:256}</div>
	</div>
{/block}

n:foreach の属性は div のブロックを foreach のループで包みます(さっきのコードとまったく同じように働きます)。

まとめ

これで記事がいくつか入ったごく単純な MySQL のデータベースができました。アプリケーションはこのデータベースにつなぎ、テンプレートでその記事の単純な一覧を表示します。