phalcon における例外処理について

なんか空いてたので書きました．

# はじめに

ここに書いてる内容は簡単にいうと「アクションで発生した例外どうなんの」ってところをラフに書いてます．

他のフレームワーク，たとえば CakePHP では NotFoundException のようなクラスをスローすることで 404 Not Found 用のレスポンスを生成することができるようです．

例外を必ずしも使う必要なないと思いますが例外投げてレスポンス生成してくれるみたいなのが楽でいいですよね．

ということでとりあえず phalcon でアクションのとき例外出た場合どうなるかを書いておきます．


# 実装方法それぞれの例外処理について

## Mvc 

例外が投げられると dispatch:beforeException イベントが通知されます． このとき Phalcon\Events\Manager が設定されていると例外発生時の処理を記述することが出来ます．

例えば下記のように dispatcher を設定するときにイベントを拾うようにして処理をかけます．ここで例外の内容によって forward() するなり $response 生成してレスポンス返してしまうなりできると思います．


```php

$di->set('dispatcher', function() {
    $eventsManager = new \Phalcon\Events\Manager();
    $eventsManager->attach('dispatch:beforeException', function($event, $dispatcher, $exception) {
      // ここで例外を補足したときの処理をする
    }
	$dispatcher = new Phalcon\Mvc\Dispatcher();
    $dispatcher->setEventsManager($eventsManager);
    return $dispatcher;
}, true);

```

例として下記の投稿のようにコントローラやアクションが見つからなかった場合の Phalcon の例外拾って forward() するなどできます．他にもいろいろ飛んできそうなので下記投稿内容のように単に $exception->getCode() だけで判断すると， getCode() の中身を被らないようにしないといけなさそうなのでつらい未来が見えます． レスポンス生成用の例外基底クラスを作って insanceof などで処理を分ける感じだと良いんじゃないでしょうか．

> https://stackoverflow.com/questions/14071261/how-to-setup-a-404-page-in-phalcon

```php:getCode()が被るとつらそう
       $evManager->attach(
            "dispatch:beforeException",
            function($event, $dispatcher, $exception)
            {
                switch ($exception->getCode()) {
                    case PhDispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                    case PhDispatcher::EXCEPTION_ACTION_NOT_FOUND:
                        $dispatcher->forward(
                            array(
                                'controller' => 'error',
                                'action'     => 'show404',
                            )
                        );
                        return false;
                }
            }
        );
```

```php:instanceofで処理を分けられるようにすると独自の例外処理を追加しやすそう
       $evManager->attach(
            "dispatch:beforeException",
            function($event, $dispatcher, $exception)
            {
                if ($exception instanceof \Phalcon\Dispatcher\Exception) {
                    switch ($exception->getCode()) {
                        case PhDispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                        case PhDispatcher::EXCEPTION_ACTION_NOT_FOUND:
                            $dispatcher->forward(
                                array(
                                    'controller' => 'error',
                                    'action'     => 'show404',
                                )
                            );
                            return false;
                    }
                }
            }
        );
```

## CLI 

Mvc と同様に dispatch:beforeException イベントが通知されます． Phalcon\Events\Manager が設定されていると例外発生時の処理を記述することが出来るのも同様です．．

[昨日の記事](http://qiita.com/nise_nabe/items/6adf0b042876c483ee6f) には書いてないですが[コード](https://github.com/nise-nabe/phalcon-cli-mail-sender/blob/master/src/Bootstrap.php#L36)でしれっとやってました．

実装方法としては上記に書いてるように dispatch:beforeException を名前に指定して function を指定する方法もありますし，下記のようにクラスを作ってイベントの名前を持ったメソッドを作ることでそのイベントが通知されたときに動かす処理を記述することが出来ます．実際 phar ファイルをそのまま実行すると MySQL につながらないエラーが出るので下記部分を通って [error] というログが吐かれているのではないかと思います．

```php
$di->set('dispatcher', function() {
    $eventsManager = new EventsManager();
    $eventsManager->attach('dispatch', new \TaskLogListener());

    $dispatcher = new \Phalcon\CLI\Dispatcher();
    $dispatcher->setEventsManager($eventsManager);
    return $dispatcher;
}, true);
```

```php
<?php

class TaskLogListener
{
    // その他イベント部分省略

    public function beforeException($event, $dispatcher, $ex)
    {
        $di = $dispatcher->getDI();
        $task = $dispatcher->getTaskName();
        $action = $dispatcher->getActionName();
        $message = sprintf('%s#%s: aborted: "%s"', $task, $action, $ex->getMessage());
        $di['logger']->error($message);
        return false;
    }
}

```

## Micro

1.x 系では補足不可能です．

Phalcon\Mvc\Micro#get() や Phalcon\Mvc\Micro#post() などで登録したハンドラ部分で例外が発生すると Phalcon\Mvc\Micro#handle() を呼び出した所まで飛んでいきます．
たとえばサンプルコードでは下記のように書かれていますが handle() の部分で例外が投げられます．やりようはいくらでもあるんでしょうが，フレームワークとして例外をサポートしてくれないので人それぞれがんばる必要があるところになりそうです．

> http://docs.phalconphp.com/ja/latest/reference/micro.html

```php
<?php

$app = new Phalcon\Mvc\Micro();

$app->get('/say/welcome/{name}', function ($name) {
    echo "<h1>Welcome $name!</h1>";
});

$app->handle(); 
```

たとえばがんばる方法の例として [phest](https://github.com/ovide/phest) で実装されているような方法があります．
仕組みとしては単に $app に登録する設定を全部 _index() というハンドラを通るようにしてなかで try-catch してます．

該当部分: https://github.com/ovide/phest/blob/56eb81c1c296fc52bf4c71312ef380ac16bd27b8/src/Controller.php#L53

```php:Controller.php
        $method = $this->request->getMethod();
        try {
            $this->_call($method, $params);
        } catch (\Exception $ex) {
            //Check if is an internal exception
            //determines if the error message is visible or hidden
            $ix  = ($ex instanceof Exception\Exception);
            $code    = $ix ? $ex->getCode() : Response::INTERNAL_ERROR;
            $message = ($ix || self::$_devEnv) ?
                trim($ex->getMessage()) :
                Response::$status[$code];
            ...
```

個人的には別に Micro じゃなきゃいけないみたいなことがなければ，こういう方向でがんばるよりはもう Mvc 使ったらいいんじゃないかなとは思います．

ここで朗報というかなんというか 2.x 以上になるとできるようになるらしいという記事があります． Micro でうまいことやりたい人は 2.x を使ってみたらいいんじゃないでしょうか．

> Phalcon PHP Framework, Phalcon 2 (beta 3) released
http://blog.phalconphp.com/post/100083107700/phalcon-2-beta-3-released

```php
use Phalcon\Http\Response;
use Phalcon\Mvc\Micro;

$app = new Micro();

$app->map('/say/{name}', function ($name) {
    throw new \Exception("An exception has occurred");
});

$app->error(function($e) {
	return new Response('Internal error');		
});
```

# まとめ

この記事では Phalcon の Mvc/CLI/Micro についてアクションやタスクで例外が投げられた場合の挙動について書いてみました．

Mvc と CLI はイベントで拾えるのでうまいこと実装できるんじゃないかなと思います．
Micro は頑張って実装してください． 2.x を使うという手もあります．

という感じで以上です．