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];
}
見ての通り、method
もuri
もほぼ加工されること無く、$action
のみに様々な加工がかかり、クラスプロパティ$routes
へと格納されていく。
$action
の加工は次のような形。
- parseActionで配列に自動変換される。文字列なら
['use'=>$action]
に、クロージャなら[$action]
になる。配列は配列のまま。 - 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要素の配列として、以下の様な形で出来ているっぽい。
- ステータス(FastRoute経由のルートでのみ、ルート処理を始める前の判定として仕様)
- アクションの実体を配列で定義したもの
- アクションに渡すパラメータ
つまりFastRoute経由でない単純なルートでは、アクションにパラメータはわたらないっぽいです。
$routeInfo
の旅は概ね以下の感じで流れていく。
-
handleFoundRoute
グローバルミドルウェアを先に処理する -
handleArrayBasedFoundRoute
action
が配列情報として持つミドルウェアを処理する。 -
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
が取ってこれたりするのは多分そういう仕組なんだろう)