Phalcon のルーティングについてメモ

ドキュメントとコードリーディングだけであまり動作確認してないので事実と異なるかもしれない．

# デフォルトルーティング

DI に何も設定しない場合， DI で `Phalcon\Mvc\Router` が設定されたものをつかう．
 （ Router のコンストラクタで true を指定する）[router.c#L238](https://github.com/phalcon/cphalcon/blob/phalcon-v1.3.2/ext/mvc/router.c#L238)
Routerのコンストラクタに true が指定された場合には Route が二つ登録されるようになっており下記二つとなっている．

* /:controller/:action
* /:controller/:action/:params

モジュールを使っている場合（ config/modules.php などで `registerModules()` をしている場合）はおそらくデフォルトモジュールが設定されていないのでいろいろ方法はあると思うが下記のように対応するのが簡単だと思う．

* 設定するモジュールが一つの場合は `Router#setDefaultModule()` して使用するモジュールを固定する
* 下記のように URI 自体にモジュールを必要とする設定を追加する．ちなみに第二引き数の数字は URI の正規表現のグループで一致したグループ番号．[ルーティングの定義](http://docs.phalconphp.com/ja/latest/reference/routing.html#id2)
 
```php
$router->add('/:module/:controller/:action', [
    'module' => 1,                            
    'controller' => 2,                        
    'action' => 3,                            
]); 
```

# ルーティング設定の追加方法

もう上で書いてしまったけど簡単には `Router#add()` を使う．また `Phalcon\Mvc\Router\Group#add()` でグループ毎に設定してから `Router#mount()` でグループごと一気に追加する方法などもあるはず．

`add()` は `Phalcon\Mvc\Router\Route` を返すので後述する `beforeMatch()` や `convert()` などを使うメソッドチェーンなどが使える．

# 実行タイミング

Router#handle() で Route が一致するかの処理をするときの動きの擬似コード．

```
（Application 内）
application:boot
  （Router 内）
  remove_extra_shashes が設定されていれば除く
  foreach ルートのリスト
    HTTP メソッドのチェック  
    Hostname のチェック
    パスが一致するかのチェック
    beforeMatch() 
    converter() 
  Route がなければ notFound を設定
application:beforeStartModule
ここで registerAutoloaders() や registerServices() が呼ばれる
application:afterStartModule
もろもろあって Dispatcher#dispatch()
```

モジュールの読み込みが Router の動作後なのでおそらくモジュール単位でのルーティング設定はモジュール単位での動作に移った後にはできなさそうな雰囲気．
モジュールごとに定義したい場合は `Phalcon\Mvc\Router\Group` を使ってそれぞれまとめておき，それをプロジェクト全体のところで呼び出すような形で実装するのがよい？

# beforeMatch について

`Palcon\Mvc\Router#add()` の返り値が `Phalcon\Mvc\Router\Route` オブジェクトになるわけだが，これに `beforeMatch()` でコールバックを指定すると URI によるマッチだけでなくほかにもいろいろな条件でマッチさせられるらしい．

また， `Phalcon\Mvc\Router\Group` にも `beforeMatch()` は設定できるが，これはそのグループに登録されている全てのルートに対してそれぞれ `beforeMatch()` が設定されていることと同じと考えてよさそう．ただし `Group` 側に記述している場合はルート個々に設定する `beforeMatch()` は `Router#mount()` の時点でおそらく上書きされる． [router.c#L1187](https://github.com/phalcon/cphalcon/blob/phalcon-v1.3.2/ext/mvc/router.c#L1187) 

[マッチング時のコールバック](http://docs.phalconphp.com/ja/latest/reference/routing.html#id15)には第三引き数の存在は書かれていないが下記のようにコールバックを設定できる様子． `Phalcon\Mvc\Router` は `Phalcon\DI\InjectionAwareInterface` を実装しているため `URI` によるマッチだけでなく request などを取ってきて GET パラメータによるルーティングなども可能の様子．

`$uri` は現在チェック中の URI で `$route` は `beforeMatch()` が設定されるその Route オブジェクトで `$router` はその `Route` が設定されてる `Router` オブジェクト．[router.c#L769](https://github.com/phalcon/cphalcon/blob/phalcon-v1.3.2/ext/mvc/router.c#L769) 

```php
/**
 * Registering a router
 */
$di['router'] = function () use ($di) {

    $router = new Router(true);

    $router->add('/admin/index', array(
        'module' => 'backend',
        'controller' => 'admin',
        'action' => 'index'
    ))->beforeMatch(function ($uri, $route, $router) {
        // なんか処理する（例えば is_match パラメータが true のときだけとか）
        $isMatch = $router->getDI()->get('request')->get('is_match', null, 'false');
        return $isMatch === 'true';
    });
```

`beforeMatch()` ではマッチの結果として true/false しか返せ無さそうだし上記の「実行タイミング」を見てもらえればわかるが `Dispacher` の処理の前なのでここで ACL 噛ませて `forward()` や `redirect()` は筋が悪そうな気がする．（実際には動きそう）

# convert について

[ルーティングパラメータの変換](http://docs.phalconphp.com/ja/latest/reference/routing.html#id7) に書いてある．第二引き数で指定したキーの名前に対して変換処理などを行うことができる．これは第二引き数に指定したものがコンバート対象となるだけな様子なので，例えば module をコンバート対象にした場合第二引き数に指定されていれば convert が起動するし指定せずに `setDefaultModule()` で設定されたものが選択された場合は convert は起動しない．またワイルドカードなどで指定できるわけではないので一気に変換等はできなさそう．

ここを使えば例えば symfony1.4 の `sfDoctrineRoute` のようなものが実現できる気がする．

# 参考文献

> ルーティング — Phalcon 1.3.1 documentation
> 
> http://docs.phalconphp.com/ja/latest/reference/routing.html

動作とかは大抵ソース見てた

> phalcon/cphalcon
> 
> https://github.com/phalcon/cphalcon