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

せっかくだから俺は形から入るぜ！

| スレ | 内容 |
|----|----|
| [インスパイヤされて掲示板を作りたくなった(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) | ← イマココ |

## せっかくだけどドキュメントを生成したくなった

毎度のことながらライブラリを入れました。今回は[ApiGen | Smart and Readable Documentation for your PHP project](http://www.apigen.org/)です。

例によってコマンドラインからコマンドを実行しよう。

```
composer require --dev apigen/apigen
```

次に設定ファイルを用意します。

```apigen.neon
source: src
destination: docs
main: InspireBBS
title: いんすぱいやーBBS
download: no
php: yes
tree: yes
```

で、次は`composer.json`にちょっと細工をしてみます。

```composer.json
{
    "autoload": {
        "files": ["src/functions.php"],
        "psr-4": {
            "InspireBBS\\": "src/"
        }
    },
    "require": {
        "vlucas/phpdotenv": "^2.2",
        "twig/twig": "^1.24",
        "twig/extensions": "^1.3",
        "zonuexe/simple-routing": "^0.5.0",
        "zonuexe/objectsystem": "^0.5.3",
        "zonuexe/tetosql": "^0.0.1",
        "apigen/apigen": "^4.1"
    },
    "require-dev": {
        "filp/whoops": "^2.0"
    },
    "scripts": {
        "doc": "apigen generate"
    }
}
```

一番下の`"scripts"`と`"doc"`のあたりがポイント。

で、コマンドラインで`composer doc`を実行してみるんじゃ。

```sh
> apigen generate
Scanning sources and parsing
Found 1 classes, 0 constants and 1 functions
Generating API documentation
  0 %
 11 %
 22 %
 33 %
 44 %
 55 %
 66 %
 77 %
 88 %
100 % - Finished!
```

するとどうでしょう、`docs`ディレクトリの中に、なにやらファイルが増えてるじゃないですか。

![スクリーンショット 2016-02-23 1.24.38.png](https://qiita-image-store.s3.amazonaws.com/0/4121/bc9e677a-14cb-1a91-36c4-32aba2a8496b.png)

![スクリーンショット 2016-02-23 1.24.55.png](https://qiita-image-store.s3.amazonaws.com/0/4121/1d73c752-e066-e81e-60ca-8e84e957f611.png)

カッコイイドキュメントが生成されたぞー。

前回用意した`Board.php`の上のコメント

```php
    /**
     * @param  string $id
     * @return Board|false
     */
    public static function find($id)
    {
```

↑ これをいい感じに読み取って綺麗なドキュメントを生成してくれるスゴイやつだよ

これをPHPDocって呼ぶんだけど、詳細は[WEB+DB PRESS Vol.87](http://gihyo.jp/magazine/wdpress/archive/2015/vol87)に書いたから近所の本屋かAmazonで取り寄せて読んでくれよな！ **(宣伝)**

自分が書いたコードがわかりやすいドキュメントとして形になると仕事した気持ちになるので、形から入るのも案外ばかにしたもんじゃないよ ヾ(〃＞＜)ﾉﾞ

## ModelとTypedProperty

さて、前回の記事では唐突に実装された`Board.php`を投下していったので、これを順に説明していきます。

```php
final class Board
{
    // ↓ これはトレイト
    use \Teto\Object\TypedProperty;

    // プロパティと型の対応表
    private static $property_types = [
        'id'    => 'string',
        'name'  => 'string',
        'text'  => 'string',
    ];
```

`Teto\Object\TypedProperty`は@tadsan氏謹製のライブラリ[zonuexe/objectsystem](https://github.com/BaguettePHP/objectsystem)に含まれるトレイトで、クラスの`$property_types`にプロパティ名と型名orクラス名を書いてやると型検査を強制することができます。~~謹製は謙譲語。~~

TypedPropertyのちょっと詳しい仕様は[poem.ja.md](https://github.com/BaguettePHP/objectsystem/blob/master/poem.ja.md)に書きました。どのような理窟で実現されてるのかは[private/protectedなプロパティを外部から読み込み可能にする](http://qiita.com/tadsan/items/2049243e47495764b191)の本文とコメントのを読んでいただけるとよろしいかとー。

さておき、これで`$board->id`とか`$board->name`みたいな感じでオブジェクトにアクセスできるようになったわけです。

## SQL

これな。

```php
    /**
     * @return Board[]
     */
    public static function findAll()
    {
        $data = SQL\Query::execute(db(), self::findAll_query, [])
            ->fetchAll(\PDO::FETCH_ASSOC) ?: [];

        $boards = [];
        foreach ($data as $b) {
            $boards[] = new Board($b);
        }

        return $boards;
    }
    const findAll_query = '
        SELECT `id`, `name`, `text` FROM `boards`
    ';
    // ↑ SQL文を定数として持つ
```

順に説明すると、`static function`これは`$obj->hoge()`みたいな形式じゃなくて`Board::hoge()`みたいな形式でメソッド呼び出しできるようにします。このようなメソッドをPHPでは「静的メソッド」と呼びます。Rubyの「クラスメソッド」とだいたい同じようなものです。

次に`SQL\Query::execute`、これは[zonuexe/tetosql](https://github.com/BaguettePHP/TetoSQL)のメソッドです。

このライブラリの特徴は以下の通りです。

* [PDO](http://php.net/manual/ja/class.pdo.php)のラッパーで、同じような感覚で利用できる
* SQL文をクラス定数(`const`)で持つスタイルを推奨し、実行時に不意に加工されることを防ぐ
* SQLインジェクションの耐性を持つ
   * 変数の型をクエリ内部に明示的に持つ
* PDOが苦手とする可変個の`IN`句に対応する
   * `:values@string[]`のように記述することで「文字列の配列」を意味する
* 特定のSQLに依存しない
   * もともとはMySQLで利用するために書かれたが、SQLiteでも動く
* 現在のところ対応してないもの
   * 64ビット符号付き整数以外の数値型
* アノテーションやメタプログラミングに依存しない
   * 特に必要ない

そんな感じで、現在PDOに依存してる箇所なら割と簡単に移行できて、安全性が高められるんじゃないかなー、と。もちろん制約もあるんですけど。

## もう一個メソッドを定義してみる

現在あるのはレコードを全部取得する`findAll()`なので、特定のIDの板だけ取得する`find($id)`を実装してみよう。

```php
    /**
     * @param  string $id
     * @return Board|false
     */
    public static function find($id)
    {
        $data = SQL\Query::execute(db(), self::find_query, [
        // 連想配列でクエリに当て嵌める値を代入
            ':id' => $id
        ])->fetch(\PDO::FETCH_ASSOC);

        if ($data === false) {
            return false;
        }

        return new BoardModel($data);
    }
    const find_query = '
        SELECT `id`, `name`, `text` FROM `boards` WHERE `id` = :id@string
    ';
```

こんな感じっすかね。

### 本日のまとめ

こんな感じって、どうでもいい周り道と前回までの説明しかしてない気がしますね＞＜ こんなことでは完成はいつになることやら…

たぶんこの記事、読むだけじゃ得られることあんまりないので、自分で手を動かしてみるのが良いのではー