ZF2 の ZFTool で Diagnostics を使う

ZFTool には Diagnostics（診断）の機能があります。コマンドラインで次のように実行すると診断結果が表示されます。

```console
$ php public/index.php diag
Starting diagnostics:

.

OK (1 diagnostic checks)
```

`-v` や `--debug` を付けるとより詳細な内容が表示されます。

```console
$ php public/index.php diag -v
Starting diagnostics:
  OK   ZF: PHP Version: Current PHP version is 5.5.11

OK (1 diagnostic checks)
```

```console
$ php public/index.php diag --debug
Starting diagnostics:
  OK   ZF: PHP Version: Current PHP version is 5.5.11
       -----------------------------------------------------
       '5.5.11'
       -----------------------------------------------------

OK (1 diagnostic checks)
```

デフォルトだと PHP のバージョンチェックだけ行われます。

## 診断項目を追加

これだけだとあまり役にたたないので診断項目を追加します。

診断項目を追加するには config/autoload/global.php とか、モジュールの module.config.php とかに次のように追加します。

```php:config/autoload/global.php
<?php
return array(
    'diagnostics' => array(
        'Directory' => array(
            'writable data/cache' => array('DirWritable', ROOT . '/data/cache'),
        ),
    ),
);
```

この例では data/cache が書き込み可能かどうかを診断します。

```console
$ php public/index.php diag -v
Starting diagnostics:
  OK   ZF: PHP Version: Current PHP version is 5.5.11
  OK   Directory: writable data/cache

OK (2 diagnostic tests)
```

配列のキーの *Directory* は診断のグループ名で、次のように特定の診断グループのみを実行することができます。

```console
$ php public/index.php diag Directory -v
Starting diagnostics:
  OK   Directory: writable data/cache: The path is a a writable directory.

OK (1 diagnostic checks)
```


## 診断項目をモジュールクラスのメソッドで定義

診断項目はモジュールクラスの getDiagnostics メソッドに書くこともできます。

```php:Module.php

    public function getDiagnostics()
    {
        return array(
            'writable data/cache' => array(
                'DirWritable', ROOT . '/data/cache'
            ),
        );
    }

```

この場合は診断グループ名（最初の例の *Directory* の部分）は必要ありません。モジュール名が診断グループ名になるためです。


## 診断の設定

config/autoload/global.php などに記載する診断の設定は次のようになります。

```php:config/autoload/global.php
<?php
return array(
    'diagnostics' => array(
        $checkGroupName => array(
            $checkLabel => $check,
             :
        ),
        :
    ),
);
```

各値は次のような意味です。

| value           | description
| ---             | ---
| $checkGroupName | 診断グループ<br>コマンドライン引数で指定すれば特定の診断だけ実行できる
| $checkLabel     | 診断結果の表示のラベル<br>診断結果と一緒に表示される
| $check          | 後述

$check には ZendDiagnostics\Check\CheckInterface をインプリメントしたクラスを書きます。次のように色々な書き方ができます。

```php:config/autoload/global.php
<?php
return array(
    'diagnostics' => array(
        'Hoge' => array(

            // 診断クラスとコンストラクタ引数を配列で指定
            'writable data/cache' => array(
                'ZendDiagnostics\Check\DirWritable', ROOT . '/data/cache'
            ),

            // コンストラクタが引数無しで呼べるなら文字列で指定可能
            'cpu performance 1' => 'ZendDiagnostics\Check\CpuPerformance',

            // ZendDiagnostics\Check 名前空間は省略可能
            'cpu performance 2' => 'CpuPerformance',

            // インスタンスを指定
            'cpu performance 3' => new \ZendDiagnostics\Check\CpuPerformance,

            // コールバックを指定
            'callback diagnostics' => function() {
                return new \ZendDiagnostics\Result\Success("callback ok");
            },
        ),
    ),
);
```

## Web のフロントエンド ##

コマンドラインだけではなく、Web のフロントエンドもあります。

Web のフロントエンドを有効にするためにはルーティングを追加する必要があります。ZFTool にサンプルのコンフィグが含まれているのでそれを使います。

```console
$ cp vendor/zendframework/zftool/config/zftool.global.php.dist config/autoload/zftool.global.php
```

config/autoload/zftool.global.php のコメントを解除します。

```php:config/autoload/zftool.global.php
<?php
return array(
    'router' => array(
        'routes' => array(
            'zftool-diagnostics' => array(
                'type'  => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route' => '/diagnostics',
                    'defaults' => array(
                        'controller' => 'ZFTool\Controller\Diagnostics',
                        'action'     => 'run'
                    )
                )
            )
        )
    ),
);
```

ブラウザで [/diagnostics](http://localhost/diagnostics) を開くと診断結果が表示されます。

![zftool-diagnostic.png](https://qiita-image-store.s3.amazonaws.com/0/8734/dc20162f-05cb-75b7-906d-cb69c88134f1.png)

## 独自の診断クラス

ZendDiagnostics\Check\AbstractCheck を継承すれば独自の診断を追加することができます。試しにデータベースへの接続が可能かどうかの診断を追加してみます。

まずは診断クラスを作成します。作る場所はどこでもいいですが、今回は OreOre モジュールに OreOre\Diagnostics\Check\DbAdapter クラスを追加することにします。

```php:OreOre/Diagnostics/Check/DbAdapter.php
<?php
namespace OreOre\Diagnostics\Check;

use ZendDiagnostics\Check\AbstractCheck;
use ZendDiagnostics\Result\Success;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\Db\Adapter\Driver\Pdo\Connection;

class DbAdapter extends AbstractCheck implements ServiceLocatorAwareInterface
{
    use ServiceLocatorAwareTrait;

    public function check()
    {
        /* @var $db \Zend\Db\Adapter\Adapter */
        $db = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');

        $stmt = $db->query("select 1");
        $res = $stmt->execute();
        $res->current();

        $conn = $db->getDriver()->getConnection();

        if (method_exists($conn, 'getConnectionParameters'))
        {
            /* @var $conn Connection */
            $params = $conn->getConnectionParameters();
        }
        else
        {
            $params = array();
        }

        return new Success("connection ok", $params);
    }
}
```

DbAdapter は当然 ServiceManager から取得することになるので、このクラスで ServiceManager が使用可能でなければなりません。そのために ServiceLocatorAwareInterface と ServiceLocatorAwareTrait を使っています。

このようにしておくと、このクラスを ServiceManager から取得したときに自動的に ServiceManager が注入されます。

次に OreOre の Module.php に getDiagnostics メソッドを追加します。

```php:Module.php

    public function getDiagnostics()
    {
        return array(
            'DbAdapter' => Diagnostics\Check\DbAdapter::class,
        );
    }

```

この時点で診断を実行してみると・・・

```console
$ php public/index.php diag -v
Starting diagnostics:
  OK   ZF: PHP Version: Current PHP version is 5.5.11
  OK   Directory: writable data/cache

Fatal error: Call to a member function get() on a non-object in ...
```

エラーになります。どうやら OreOre\Diagnostics\Check\DbAdapter に ServiceManager が注入されていないようです。

原因は簡単で OreOre\Diagnostics\Check\DbAdapter が ServiceManager 経由で取得されていないためです。

診断のコントローラー DiagnosticsController は診断クラスの取得の際、ServiceManager から取得できればそれを使い、取得できなければリフレクションでインスタンス化を試みます。
今はまだ ServiceManager に OreOre\Diagnostics\Check\DbAdapter を設定していないのでリフレクションからインスタンス化されます。

ServiceLocatorAwareInterface と ServiceLocatorAwareTrait はあくまでも ServiceManager からインスタンスが取得されるときに意味があるものなので、リフレクションでインスタンス化されると ServiceManager が OreOre\Diagnostics\Check\DbAdapter に注入されません。

そこで OreOre\Diagnostics\Check\DbAdapter が ServiceManager から取得されるように OreOre の Module.php の getServiceConfig メソッドを修正します。

```php:Module.php

    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                
                :

                Diagnostics\Check\DbAdapter::class => function ($sm) {
                    return new Diagnostics\Check\DbAdapter;
                },
            ),
        );
    }

```

これで診断が実行できるようになりました。

```console
$ php public/index.php diag -v
Starting diagnostics:
  OK   ZF: PHP Version: Current PHP version is 5.5.11
  OK   Directory: writable data/cache
  OK   OreOre: DbAdapter: connection ok

OK (3 diagnostic checks)
```

試しにデータベースの接続情報を適当に書き換えて診断を実行してみると、次のように失敗します。

```console
$ php public/index.php diag -v
Starting diagnostics:
  OK   ZF: PHP Version: Current PHP version is 5.5.11
  OK   Directory: writable data/cache
 FAIL  OreOre: DbAdapter: Uncaught Zend\Db\Adapter\Exception\RuntimeException: Connect Error: SQLSTATE[HY000] [1045] Access denied for user
       'xxxx'@'192.0.2.101' (using password: YES)

1 failures, 0 warnings, 2 successful checks.
```

### 参考

 - [ZFTool/docs/DIAGNOSTICS.md - zendframework/ZFTool](https://github.com/zendframework/ZFTool/blob/master/docs/DIAGNOSTICS.md "ZFTool/docs/DIAGNOSTICS.md at master · zendframework/ZFTool")
 - [ZendDiagnostics/README.md at master · zendframework/ZendDiagnostics](https://github.com/zendframework/ZendDiagnostics/blob/master/README.md "ZendDiagnostics/README.md at master · zendframework/ZendDiagnostics")