コード改善に役立ちそうなPHPライブラリ・ツール

最近PHPを使っていて、便利だなと思ったライブラリ・ツールを紹介します。
特定のフレームワークに依存しないので、既存のコードに組み込んで利用することもできます。

なお、この記事内のサンプルコードは、カレントディレクトリに[Composer](https://getcomposer.org/)がインストールされているのを前提としています。[^1]
実際にコードを書いて試してみたい人は、`composer.phar`があるディレクトリで、以下のコマンドを入力して下さい。

```text
mkdir public
php composer.phar init -n
```

以下のようなファイル・ディレクトリ構成ができたはずです。

```text
├── composer.json
├── composer.phar
└── public
```

## Monolog

「運用中のサービスにバグが見つかったけど、再現方法がわからない」。そんな困った事態にならないよう、[Monolog](https://github.com/Seldaek/monolog)で手がかりになる値を残しておきましょう。

Monologは`php composer.phar require monolog/monolog` でインストールできます。

試しに簡単なログ出力をやってみましょう。`public/index.php`を以下のように書いてください。

```php:index.php
<?php
require '../vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

// Monologでログを出力してみる
$log = new Logger('cool-php-libraries');
$log->pushHandler(new StreamHandler('../logs/app.log', Logger::DEBUG));
$log->addInfo('トップページの表示');
$log->addError('エラーメッセージ');
$log->addDebug('デバッグ用のメッセージ');
```

`php -S localhost:8080 -t public`で[ビルトインウェブサーバー](http://php.net/manual/ja/features.commandline.webserver.php)を立ち上げた状態で http://localhost:8080/ にアクセスしてください。`logs/app.log`に「トップページの表示」、「エラーメッセージ」、 「デバッグ用のメッセージ」というメッセージが出力されているはずです。

`pushHandler`メソッドに渡した`StreamHandler`がログの出力先を表すHandlerクラスです。これを書き換えることで出力先をファイルにしたり、メールで送ったり、DBに登録したりと、色々変更することができます(複数指定することもできます)。

`StreamHandler`の第二引数に指定しているのがログの出力レベルです。Monologでは、レベルの低い順に以下が規定されています。

* DEBUG
* INFO
* NOTICE
* WARNING
* ERROR
* CRITICAL
* ALERT
* EMERGENCY

ログ出力に使っている`addほにゃらら`メソッドの`ほにゃらら`は上記のレベル名で、Handlerクラスで指定したレベル未満のメソッドは、たとえ書いてあってもどこにも出力されません。
例えば、上のコード例では`StreamHandler`に`Logger::DEBUG`を指定しているので、すべてのレベルのログが`logs/app.log`に出力されますが、`Logger::INFO`に変更すれば、`addDebug`メソッドの内容は出力されなくなります。
つまり、Handlerクラスに指定するログの出力レベルを環境によって変更できるようにしておけば、簡単に出力する/しないを切り替えることができます。

## Chrome Logger & FirePHP

Chrome or Firefoxなら、`echo`を使うよりスマートなやり方でデバッグできます。

Chromeなら[Chrome Logger](http://craig.is/writing/chrome-logger)、Firefoxなら[FirePHP](https://addons.mozilla.org/ja/firefox/addon/firephp/)[^2]をインストールすると、開発者向けコンソールでMonologのログ出力を表示させることができます。PHPコードの変更は、以下のように、`ChromePHPHandler` [^3]、`FirePHPHandler`を加えるだけです。

```php:index.php
<?php
require '../vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\ChromePHPHandler;
use Monolog\Handler\FirePHPHandler;

// Monologでログを出力してみる
$log = new Logger('cool-php-libraries');
$log->pushHandler(new StreamHandler('../logs/app.log', Logger::DEBUG));
$log->pushHandler(new ChromePHPHandler(Logger::DEBUG));
$log->pushHandler(new FirePHPHandler(Logger::DEBUG)); 
$log->addInfo('トップページの表示');
$log->addError('エラーメッセージ');
$log->addDebug('デバッグ用のメッセージ');
```

ただ、FirePHPは、私の環境(Mac 10.9.5、Firefox 35.0.1、FirePHP 0.7.4、Firebug 2.0.8)では日本語のログが文字化けしました。日本語のログは出力しないようにした方が無難かもしれません。

## FluentPDO

ユーザーの入力値によって文字列でSQL文を作る仕様のコードを書いたら、リリース後に条件によってSQLが構文エラーになるバグが見つかった、なんてことありませんか？
[FluentPDO](https://github.com/envms/fluentpdo)なら、文字列ではなくPHPのメソッドでSQL文をつくることができます。
インストールは`php composer.phar require lichtner/fluentpdo:dev-master`でできます。
以下のように、`from`、`select`、`where`、`orderBy`といったSQL文と似た名前のメソッドでSQL文を作れます。

```html+php:index.php
<?php
require '../vendor/autoload.php';
$db = '../example.db';
$dsn = 'sqlite:' . $db;
if (!file_exists($db)) {
    // データベースの初期化
    $pdo = new PDO($dsn);
    $sql = <<<EOT
create table record(
    id integer primary key autoincrement,
    name text,
    point integer
);
EOT;
    $pdo->query($sql);

    $fpdo = new FluentPDO($pdo);
    $values = [
        ['name' =>'山田太郎', 'point' => 500],
        ['name' =>'山田花子', 'point' => 300],
        ['name' =>'鈴木一郎', 'point' => 890],
        ['name' =>'山本五郎', 'point' => 90],
    ];
    foreach ($values as $value) {
        $fpdo->insertInto('record', $value)->execute();
    }
} else {
    $pdo = new PDO($dsn);
    $fpdo = new FluentPDO($pdo);
}
$query = $fpdo->
    from('record')->
    select(null)->
    select('name')->
    select('point')->
    where('point > ?', 100)->
    orderBy('point DESC');
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>cool-php-libraries</title>
</head>
<body>
   <h1>cool-php-libraries</h1>
    <ul>
    <?php foreach ($query as $q): ?>
    <li><?php echo htmlspecialchars($q['name']); ?> <?php echo htmlspecialchars($q['point']); ?>点</li>
    <?php endforeach; ?>
    </ul>
</body>
</html>
```

`select(null)`と書いていますが、FluentPDOのselect文はデフォルトでは`select *`になるようで、特定のカラムのみ呼び出したい場合は、このコードで一旦デフォルトの設定を無効にする必要があるようです。

デバッグ機能も用意されていて、どんなSQL文が生成されるのかを見ることができます。先に紹介したMonologと組み合わせるなら、こう書きます。

```php:index.php
<?php
require '../vendor/autoload.php';
// Monologの初期化処理は省略
$dsn = 'sqlite:../example.db';
$pdo = new PDO($dsn);
$fpdo = new FluentPDO($pdo);
$fpdo->debug = function ($query) use ($log) {
    $log->addDebug($query->getQuery());    
};
```

## phpdotenv

PHP 5.4以降では[ビルトインウェブサーバー](http://php.net/manual/ja/features.commandline.webserver.php)という機能があるので、Apacheやnginxをインストールしなくても開発することができるようになりました。
ただ、環境変数をApacheやnginxの設定ファイルに書く前提で設計してしまうと、開発環境でどうするのかという問題が発生します。
そんな時、[phpdotenv](https://github.com/vlucas/phpdotenv)を使うと、どの環境でも共通の方法で環境変数を参照できるようになります。
インストールは`php composer.phar require vlucas/phpdotenv`でできます。
使い方は、`load`というメソッドに`.env`ファイルがあるディレクトリを渡すだけです。

```php:index.php
<?php
require '../vendor/autoload.php';
// 環境変数の読み込み
Dotenv::load(__DIR__ . '/..');
```

なお、本番環境でDocumentRootの下に`.env`を置くと、当然ブラウザからアクセスできてしまうので、`.env`だけ403 Forbiddenになるようにしておくか、DocumentRootの上に置くように設計しておいたほうがいいでしょう。

`.env`ファイルは、以下のように変数名と値をイコールで繋いで書きます。

```text:.env
S3_BUCKET=dotenv
SECRET_KEY=souper_seekret_key
```

必須の環境変数がある場合は、以下のように書いておくと書き忘れにすぐ気づけます。

```php:index.php
require '../vendor/autoload.php';
// 環境変数の読み込み
Dotenv::load(__DIR__ . '/..');
// DATABASE_DSNが定義されていないとエラーになる
Dotenv::required('DATABASE_DSN');
```

## 2016/07/04追記

FluentPDO公式サイトのURLが変わっていたので変更しました。@kt81 さんご指摘ありがとうございました。

## 2016/09/13追記 ##

2016/07/04に変更したFluentPDO公式サイトのURLがまた変更されたようです。また変更されたとき修正するのが面倒なので、この記事ではGitHubのURLを貼っておきます。

[^1]: ... Composerについて詳しくはこちらを参照してください。http://qiita.com/tadsan/items/a0a14e58aa4d88f19624#composer
[^2]: ... [FireBug](https://addons.mozilla.org/ja/firefox/addon/firebug/)も併せてインストールしてください
[^3]: ... ChromePHP=Chrome Loggerの旧名