lumen ガイド 3 ルーティング


laravel謹製のマイクロフレームワーク `lumen`が出たので使ってみた。
ざっくり触りながらのまとめになりますが、ツッコミポイントとかアレば是非コメントお願いしまっす。

## ルーティング

ルートは`app/Http/routes.php`に書く。
`bootstrap`で読み込んでるので、require読み込みでヤルのがいやなら、サービスプロバイダとか使うのもあり。

形式はシンプルになっててsilexみたいな感じ。ただcontroller系のあれがいろいろなくって、いわゆる`implict controller`すら使えないっぽいので困った。

クラス化したコントローラで使えるのは、こんな感じの`@`系ルートだけ?

````php
$app->get('user/profile', [
    'as' => 'profile', 'uses' => 'UserController@showProfile'
]);
````

コンストラクタの自動注入が使える位の恩恵?とおもうと微妙な所。



ただルートに正規表現っぽいのが使えるのは嬉しい。
http://lumen.laravel.com/docs/routing

laravelにはない機能なので、lumenからlaravelに移行する時には注意、ってドキュメントにも書いてある。

### サービスプロバイダでルートを登録

````
<?php namespace App\Providers;

use Illuminate\Support\ServiceProvider;

/**
 * アプリ側のルーティングを記述
 * @package App\Providers
 */
class AppRoutesServiceProvider extends ServiceProvider
{

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $app = $this->app;
        $this->app->get('', function()use($app) {
			  // return $this->app->welcome(); これはダメ
            return $app->welcome();
        });
    }
}
````

デフォルト画面で表示されるwelcom画面は、lumenが **コアで** 持っているwelcomeメソドで生成しているHTMLコードです。

クロージャの中で`$this`を使うのはおすすめしない。というのも、ルートで渡した`closure`は、全て`Closure::bindTo`を使って、`Laravel\Lumen\Routing\Closure`にバインドされる。要は`$this`が書き換わる。JSみたいなことがPHPでもできるみたい。

ちなみに、ルートに`callable`を採用するsilexとは違い、lumenでは厳密に`Closure`を取るので、

````
$app->get('',[$obj,"methodName"]);
````

みたいな書き方は出来ない。

1ルート1行みたいな構成は避けられそうにもなくて、色々考えてるけど…せめてsilexのmountみたいなのアレばいいのだが。

### ちょっと特殊なルーティング問題

手許のPHPビルトインサーバで試してみたところ、
インデックスファイルをルートの`index.php`にした場合、例えば`public/api/index.php`みたいなファイルを置いた場合、
ルート文字列のマッチが`/api`からスタートする。インデックスファイルを変更するのはあまりオススメ出来ないのかも。

## コントローラとは

じゃあコントローラってなんなのよって所になるけど、コントローラの恩恵は今のところ、コンストラクタ注入が使えるって所だけにあるっぽい。
このへんはコンテナの`make`メソドを使ってクラスインスタンスが生成されるからであって継承関係とかは関係なく、全てのクラスがコンストラクタ注入の対象となる。

よって、Controllerクラスにはデフォルト値の無いスカラのコンストラクタ引数をセットしてはいけない(逆にデフォルト値がアレばいいということ)。

ちなみにインスタンス化は`make`経由で行われるので、コンテナにインスタンスを登録しとけばそれを使ってもらえるし、HMVC的な仕組み作ってもちゃんとシングルトンしてくれる。

### lumen Controllerの利用

コードサンプルにあるような`Laravel\Lumen\Routing\Controller`を継承すると登録したミドルウェアが自動でよみこまれる、みたいです。継承してなかったらダメです。


## [内部処理] ルートの登録

ルート登録においては、`Application::addRoute($method, $uri, $action)`がほぼ全てを司る。`get`とか`post`はそのエイリアスみたいな感じ。

````
    protected function addRoute($method, $uri, $action)
    {
        $action = $this->parseAction($action);

        $uri = $uri === '/' ? $uri : '/'.trim($uri, '/');

        if (isset($action['as'])) {
            $this->namedRoutes[$action['as']] = $uri;
        }

        if (isset($this->groupAttributes)) {
            $action = $this->mergeGroupAttributes($action);
        }

        $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
    }
````

見ての通り、`method`も`uri`もほぼ加工されること無く、`$action`のみに様々な加工がかかり、クラスプロパティ`$routes`へと格納されていく。

`$action`の加工は次のような形。

1. parseActionで配列に自動変換される。文字列なら`['use'=>$action]`に、クロージャなら`[$action]`になる。配列は配列のまま。
2. as設定はキャッシュされる。グループが用いられている時はそのミドルウェア設定が適用される。

## [内部処理] でぃすぱっち

クラスプロパティの`$routes`には、`method`,`uri`,`action`の3つが登録されるが、`Application::dispatch`を見てもわかるように、ルート情報として持ち回しされるのは`action`のみ。

`Application::dispatch`に記載のある`handleFoundRoute`がルートの処理を開始する。ここで3要素からなる配列引数が以降続くRoute処理系メソドで引数として渡る`$routeInfo`である。

````php
$this->handleFoundRoute([true, $this->routes[$method.$pathInfo]['action'], []])
````

通常のルート検索でヒットがなかった場合は、`FastRoute\Dispatcher\RegexBasedAbstract::dispatch`が生成してくれる`$routeInfo`を使用する。

この`$routeInfo`、3要素の配列として、以下の様な形で出来ているっぽい。

1. ステータス(FastRoute経由のルートでのみ、ルート処理を始める前の判定として仕様)
2. アクションの実体を配列で定義したもの
3. アクションに渡すパラメータ

つまりFastRoute経由でない単純なルートでは、アクションにパラメータはわたらないっぽいです。

`$routeInfo`の旅は概ね以下の感じで流れていく。

1. `handleFoundRoute` グローバルミドルウェアを先に処理する
2. `handleArrayBasedFoundRoute` `action`が配列情報として持つミドルウェアを処理する。
3. `callActionOnArrayBasedRoute` 色々な処理。

### callActionOnArrayBasedRoute

ここで本格的な処理に入る。

`use`エントリがある時はコントローラ処理に割り振り、それ以外の時は、`$action`からクロージャを探して、lumen独自クラスに`bindTo`してから実行する。

````PHP
    protected function callActionOnArrayBasedRoute($routeInfo)
    {
        $action = $routeInfo[1];

        if (isset($action['uses'])) {
            return $this->callControllerAction($routeInfo);
        }

        foreach ($action as $value) {
            if ($value instanceof Closure) {
                $closure = $value->bindTo(new Routing\Closure);
                break;
            }
        }

        try {
            return $this->call($closure, array_values($routeInfo[2]));
        } catch (HttpResponseException $e) {
            return $e->getResponse();
        }
    }
````

コントローラ処理の時にtry...catchしてないっぽく見えるが、上手いこと中でヤってくれてる。

### コントローラの実行

`callControllerAction`では文字列として渡されている`use`エントリを`@`で分割し、
クラス名は`make`してインスタンス化する。

インスタンス化したメソドがlumen controllerの継承インスタンスなら、ミドルウェアの処理をはさみ、最終的には

````
$this->call([$instance, $method], $parameters)
````

として、クロージャと同じく`call`メソドに渡される。

### クロージャの実行 / `call`メソド

`call`メソドはコンテナ由来のメソドで、メソド引数の依存解決をしながら`callable`を実行してくれる。

なのでメソドでもコンテナベースの依存注入は可能っぽいです。試してない。
(けど`Request`が取ってこれたりするのは多分そういう仕組なんだろう)