PHP
lumen

lumen ガイド 3 ルーティング

More than 3 years have passed since last update.

laravel謹製のマイクロフレームワーク lumenが出たので使ってみた。

ざっくり触りながらのまとめになりますが、ツッコミポイントとかアレば是非コメントお願いしまっす。


ルーティング

ルートはapp/Http/routes.phpに書く。

bootstrapで読み込んでるので、require読み込みでヤルのがいやなら、サービスプロバイダとか使うのもあり。

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

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

$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];
}

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

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


  1. parseActionで配列に自動変換される。文字列なら['use'=>$action]に、クロージャなら[$action]になる。配列は配列のまま。

  2. as設定はキャッシュされる。グループが用いられている時はそのミドルウェア設定が適用される。


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

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

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

$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してから実行する。

    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が取ってこれたりするのは多分そういう仕組なんだろう)