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

  • 9
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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

スレ 内容
インスパイヤされて掲示板を作りたくなった(1) PHPとComposer、あとべんりなライブラリ
インスパイヤされて掲示板を作りたくなった(2) SQLite3のスキーマ設計とTwigテンプレート
インスパイヤされて掲示板を作りたくなった(3) ルーティングとモデルの説明
インスパイヤされて掲示板を作りたくなった(4) ← イマココ

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

毎度のことながらライブラリを入れました。今回はApiGen | Smart and Readable Documentation for your PHP projectです。

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

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を実行してみるんじゃ。

> 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

スクリーンショット 2016-02-23 1.24.55.png

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

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

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

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

これをPHPDocって呼ぶんだけど、詳細はWEB+DB PRESS Vol.87に書いたから近所の本屋かAmazonで取り寄せて読んでくれよな! (宣伝)

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

ModelとTypedProperty

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

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

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

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

TypedPropertyのちょっと詳しい仕様はpoem.ja.mdに書きました。どのような理窟で実現されてるのかはprivate/protectedなプロパティを外部から読み込み可能にするの本文とコメントのを読んでいただけるとよろしいかとー。

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

SQL

これな。

    /**
     * @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のメソッドです。

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

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

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

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

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

    /**
     * @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
    ';

こんな感じっすかね。

本日のまとめ

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

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