インスパイヤされて掲示板を作りたくなった(5)


なあ… そろそろ掲示板しようや…

## 過去ログ

| スレ | 内容 |
|----|----|
| [インスパイヤされて掲示板を作りたくなった(1)](http://qiita.com/tadsan/items/cdbbb5b08591af2b110d) | PHPとComposer、あとべんりなライブラリ |
| [インスパイヤされて掲示板を作りたくなった(2)](http://qiita.com/tadsan/items/adaade6b675ac344f373) | SQLite3のスキーマ設計とTwigテンプレート |
| [インスパイヤされて掲示板を作りたくなった(3)](http://qiita.com/tadsan/items/498710c5199349e6d7a8) | ルーティングとモデルの説明 |
| [インスパイヤされて掲示板を作りたくなった(4)](http://qiita.com/tadsan/items/c9798a6eddaea153f42e) | ドキュメント生成とモデル、SQL |
| [インスパイヤされて掲示板を作りたくなった(番外篇)](http://qiita.com/tadsan/items/5717befa36572dd5d86c) | PsySHでアプリケーションと対話する |
| [インスパイヤされて掲示板を作りたくなった(番外篇2)](http://qiita.com/tadsan/items/2adc867e092a7496215d#_reference-5381c0a7d06f5bf0578e) | Composer 依存関係の更新(メンテナンス) |
| インスパイヤされて掲示板を作りたくなった(5) | ← イマココ |

## おさらひ

 1. この連載のソースコードは https://github.com/zonuexe/inspire-bbs/ にあります
 2. この連載に含まれるソースコードは[WTFPL](https://ja.wikipedia.org/wiki/WTFPL)なので、煮るなり焼くなり好きにしてください。
	 * 許可とか条件とか表示とか要らないし
	 * なんだったら著作権表示を外して自分のものにしておｋ、の意
 3. よくわかんないこと、気になったこと、感想などあればコメントしてくださいね ヾ(〃＞＜)ﾉﾞ

```sh
rm cache/db.sq3
sqlite3 ./cache/db.sq3 < ./tests/db/init.sql
sqlite3 ./cache/db.sq3 < ./tests/db/seed.sql
```

↑ ここまでの説明通りに構成してきた場合の、DBを復活させるじゅもん。

## URL設計

URL設計については[インスパイヤされて掲示板を作りたくなった(3)](http://qiita.com/tadsan/items/498710c5199349e6d7a8#url%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0)などで言及してきましたが、ここで一度決めます。

| ページ | 内部名 | URL | 2ch |
|--------|--------|-----|-----|
| トップ | `top_index` |  `/` | http://2ch.sc/bbsmenu.html |
| 板 | `board_index` | `/news4vip` | http://viper.2ch.sc/news4vip/ |
| スレッド | `thread_index` | `/news4vip/12345` | http://viper.2ch.sc/test/read.cgi/news4vip/1455901382 |
| レス | `thread_res` | `/news4vip/12345/678` | http://viper.2ch.sc/test/read.cgi/news4vip/1455901382/12  |
| スレ作成 | `thread_new` | `/news4vip/new` | http://viper.2ch.sc/test/bbs.cgi?new |
| スレ作成(POST) | `thread_create` | `/news4vip/create` | http://viper.2ch.sc/test/bbs.cgi |

「内部名」はソースコード上でページを一意に定めるためのもので、テンプレートのファイル名や名前付きルーティング(後述)に利用します。

## トップページ(板一覧)の作成


改めて、以下のようなルーティングを設定します。

```index.php
// この配列にHTTPメソッド、URLと返したい値(クロージャ)を追加してしていくよ。
$routing_map = [];

// クロージャは、Actionを受け取って表示内容を文字列で返す
$routing_map['top_index'] = ['GET', '/', function (Action $action) use ($twig, $now) {
    return $twig->render('top_index.tpl.html', [
        'boards' => Model\Board::findAll(),
    ]);
}];

$routing_map['board_index'] = ['GET', '/:board', function (Action $action) use ($twig, $now) {
    return 'あとで実装するよ…';
}, ['board' => '/\A[a-z0-9]{1,16}\z/']];


// ...
// このへんにいろんな機能を追加していくよ
// ...


$routing_map['#404'] = function (Action $action) use ($twig) {
    return "そんなページありませんよ…";
};
```

これは、`top_index`はデータを渡してレンダリングするだけの超シンプルなページです。`board_index`は文字列を表示するだけの仮実装です。[この状態の`index.php`はこちら](https://github.com/zonuexe/inspire-bbs/blob/3c9cd83ef350a957e410197535bb8d4bbc826658/public/index.php)。

### Twigテンプレート

Twigに関しては[インスパイヤされて掲示板を作りたくなった(2)](http://qiita.com/tadsan/items/adaade6b675ac344f373#%E3%81%AA%E3%81%9Ctwig%E3%81%8B)に書きましたので、再確認してください。

```jinja:top_index.tpl.html
{% extends "base.tpl.html" %}
{% block title %}いんすぱいやーBBSへようこそ☆{% endblock %}
{% block content %}
<h1>いんすぱいやーBBS 板一覧</h1>

<ul>
{% for board in boards %}
    <li>{{ board.name }}</li>
{% endfor %}
</ul>
<p>ヾ(〃＞＜)ﾉﾞ＜ ようこそ</p>
{% endblock %}
```

## 板(スレッド一覧)の作成

その前に、スレッドを表現するクラス[`Thread.php`](https://github.com/zonuexe/inspire-bbs/blob/fdf87416eed40ca6c0f4ba871e7890630d431ae9/src/Model/Thread.php)とスレ内の発言を表現するクラス[`Post.php`](https://github.com/zonuexe/inspire-bbs/blob/fdf87416eed40ca6c0f4ba871e7890630d431ae9/src/Model/Post.php)を用意しておきます。

`Post`クラスの方はカラムが多いのに伴って、行数も多めになってます。また、日時情報を[`DatetimeImmutable`]([`DateTimeImmutable`](http://php.net/manual/class.datetimeimmutable.php))クラスで持つことにも注意です。

### ルーティング

`$routing_map['board_index']`と`$routing_map['thread_index']`を継ぎ足していきます。

```index.php
$routing_map['board_index'] = ['GET', '/:board', function (Action $action) use ($twig, $now) {
    $board = Model\Board::find($action->param['board']);
    $threads = Model\Thread::findAllByBoard($board);

    return $twig->render('board_index.tpl.html', [
        'board'   => $board,
        'threads' => $threads,
    ]);
}, ['board' => '/\A[a-z0-9]{1,16}\z/']];

$routing_map['thread_index'] = ['GET', '/:board/:timestamp', function (Action $action) use ($twig, $now) {
    return 'あとで実装…';
}, ['board' => '/\A[a-z0-9]{1,16}\z/', 'timestamp' => '/\A[1-9][0-9]*\z/']];

```

### Twigテンプレート

ここからTwigを使ってHTMLテンプレートを組んでいきます。

```jinja:board_index.tpl.html
{% extends "base.tpl.html" %}
{% block title %}いんすぱいやーBBSへようこそ☆{% endblock %}
{% block content %}
<h1>いんすぱいやーBBS 「{{ board.name }}」</h1>

<ol>
{% for thread in threads %}
    <li>{{ thread.title }}</li>
{% endfor %}
</ol>
<p>ヾ(〃＞＜)ﾉﾞ＜ わいわい</p>
{% endblock %}
```

### リバースルーティング

さて、複数のページを持つページの開発時にいちばんめんどくさいのは**URLの管理**です。テンプレート内で文字列組立をすると、URLを変更しようと思ったときに氏ぬほどつらい目に遭ひます。また、純粋にテンプレートのコードが読みにくい。

今回ルーティングエンジンとして使ってる[TetoRouting](https://github.com/BaguettePHP/TetoRouting)には幸運にもリバースルーティング機能、つまりパラメータをもとにURLを作成することができます。

これとTwigの拡張機能を利用すれば、いとも簡単に実現できますね ヾ(〃＞＜)ﾉﾞ
Twigの拡張方法についてちゃんと知りたければ[Extending Twig](http://twig.sensiolabs.org/doc/advanced.html)を読んでください。

```php
$twig->addFunction(new \Twig_SimpleFunction('url_to', function ($name, array $param = []) use ($router) {
    return $router->makePath($name, $param, true);
}));
```

実際には、以下のように`$router = new \Teto\Routing\Router($routing_map)`の次の行に書くのが大事です。

```index.php
// ルーターを起動する
$router = new \Teto\Routing\Router($routing_map);
$twig->addFunction(new \Twig_SimpleFunction('url_to', function ($name, array $param = []) use ($router) {
    return $router->makePath($name, $param, true);
}));

// Actionオブジェクトにはいろんな情報が詰まってるよ
$action = $router->match($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
$content = call_user_func($action->value, $action);
```

> #### クロージャについて
>
> 閑話休題。「クロージャ」と呼ばれることもあるPHPの`function()`式、つまり`$f = function ($a, $b) use ($v) { ... };` のような式を「ローカル変数に代入できる函数」としか見てないのだとしたら、それはクロージャの実力を安く見積もりすぎです。
>
> もし興味があれば、[クロージャって何? どこがオブジェクト指向と似てるの? をRubyで説明するために書いたコード](http://qiita.com/tadsan/items/cf30eb45312b498c97b0)を読んでください。なんかめんどくさいな、と思ったらスルーしてくだされ。

### テンプレートから函数を呼び出す

さて、今回作った自作の函数を呼び出すには以下のようにします。

```jinja:board_index.tpl.html
    <li><a href="{{ url_to('thread_index', {"board": thread.board_id, "timestamp":  thread.timestamp}) }}">{{ thread.title }}</a></li>
```

`{"board": thread.board_id, "timestamp":  thread.timestamp}`はTwigの中で連想配列を作成するための記法です。

## 次回予告

そろそろ掲示板っぽくなるとイイナ。次回はまた半年後、とかにならなければイイナ。