大きなフレームワークやシステムに比べると、僕は小さくて軽い、差し替えのきくパッケージのほうが好みです。Idiorm & Paris や Phinx のような。まず、構造が理解しやすく、抽象化の層が少ない。そしてプロジェクトにとっては、各機能の結合度も下がるので、必要なときにそのまま差し替えたり調整したりできます(たとえば、あるパッケージがメンテされなくなったとき)。
もちろん欠点もあります。何でも自分で手作業でつなぎ込む必要があるし、独立したパッケージはコアシステムとの相性がそこまでよくなかったり、パッケージどうしがぶつかったり(たとえば、共通の依存を違うバージョンで要求している場合)します。しかも、一つのプロジェクトの中にいろいろな設計思想が入り混じるので、一つずつ理解していかないといけません。
それでも、いまの主要なフレームワークの設計思想は、大いに参考にする価値があります。システムがある規模まで育つと、アーキテクチャの抽象化やインターフェース化で整理し、柔軟性を高めることが、どうしても必要になります。これは避けられない発展の道すじです。
Auth・Guard・User、実際の流れ
本題に戻りましょう。ここからは、認証の全行程を一度ていねいにたどっていきます。図には Lumen と AuthServiceProvider の 2 つを書き足して、つながりを見やすくしました。
Lumen → ServiceProvider → AuthManager

Lumen で認証機能を有効にするには、まず AuthServiceProvider を Lumen Application —— つまり $app —— に登録する必要があります。ここが流れの出発点でもあります:
$app->register(\App\Providers\AuthServiceProvider::class);
AuthServiceProvider は、関連するサービスをどうやってシステムに結びつけているのでしょう?ソースを見ると、boot メソッド に次のようにあります:
$this->app['auth']->viaRequest('api', function ($request) {
...
});
まず、ここで $this->app['auth'] を取得しようとしています。app['auth'] は本来存在しないオブジェクトで、アクセスされたのをきっかけに、AuthManager がインスタンス化されます。でも、なぜ Illuminate\Auth\AuthManager なのでしょう?これは、コアの Illuminate\Auth\AuthServiceProvider が指定しているものです。具体的な流れは次のとおりです:
- コードが
app['auth']にアクセスする。 appはauthがすでに作られているかを自分でチェックし、まだなら、インスタンス化する。- Lumen Application —— Laravel\Lumen\Application —— が
authを特別なキーワードとしてバインドしているので、authをインスタンス化しようとするとregisterAuthBindings関数が呼ばれる。 registerAuthBindings関数が、コアサービスの Illuminate\Auth\AuthServiceProvider を登録する。- Illuminate\Auth\AuthServiceProvider が、その
registerメソッド の中で、シングルトンパターンapp->singletonを使って Illuminate\Auth\AuthManager クラスをインスタンス化し、app['auth']に割り当てる。
というわけで、app['auth'] を呼び出して少し手を加えるくらいで、App\Providers\AuthServiceProvider 自体はとくに何かサービスを読み込んでいるわけではありません。処理の大半は、コアの auth まわりのバインドが担っています。
後ろの app['auth']->viaRequest というコードは Guard に関わるので、次の節でまとめて説明します。
AuthManager → XXX (Guard) → GuardDriver → Guard

では、AuthManager はどうやって Guard とやり取りするのでしょう?Lumen には、3 種類の Guard クラスが組み込まれています:
- RequestGuard:リクエストをベースにした、拡張用の認証モード。オブジェクト生成時に callback 関数を渡す必要があります。この callback は
$requestとuserProviderの 2 つを受け取り、使えるユーザーオブジェクト を返すことが求められます(認証結果を返すだけではありません)。 - TokenGuard:同じくリクエストをベースに、渡された内容で認証するモード。たとえば header に特定の token や password が入っているか、などです。
- SessionGuard:Session(と Cookie)のデータをもとに認証するモード。ふつうの PHP プログラムで、ユーザーのログインを記録・識別するときに使う、あのやり方です。
app['auth'] —— AuthManager がインスタンス化されれば、それを通じて、上の Guard オブジェクト(あるいは自作の Guard)を取得できます:
app['auth']->guard('XXX')を呼ぶ。XXX という名前の Guard オブジェクトがすでにあれば、それを返す。なければ、Guard オブジェクトを作る準備に入る。名前を渡さないときは、config/auth.php のdefaults.guardが自動で使われる点に注意。- Guard オブジェクトを作る前に、config/auth.php の
guardsに XXX Guard の設定があるかをまず確認する。なければエラーを投げる。 - XXX Guard の設定をまとめて、この Guard に次のものがあるかを順に確認する:
- カスタムの callback 生成関数:コード内で
app['auth']->extend()を使い callback で定義した生成関数。この定義の名前を、設定ファイルにも設定しておく必要がある。 - AuthManager 内のドライバメソッド:
- AuthManager を自分で拡張していれば、createXXXDriver というメソッドを定義して XXX Guard を動かせる。
- 組み込み Guard の デフォルトのドライバメソッド も同じ方式で、たとえば SessionGuard は createSessionDriver、TokenGuard は createTokenDriver です。
- カスタムの callback 生成関数:コード内で
- 生成関数/ドライバメソッド を呼んで対象の Guard をインスタンス化し、そのオブジェクトを XXX をキーにして AuthManager 自身の
guards arrayに入れる。 - Guard オブジェクトを返す。
この流れから分かるとおり、自分で Guard を定義したいなら、いちばん簡単なのは app['auth']->extend() で Guard を生成する関数を定義し、config/auth.php に設定を書いておくことです。
app['auth']->viaRequest に戻ると、viaRequest は実のところ RequestGuard をベースにした extend() を応用したものです。かみくだいて言えば、RequestGuard オブジェクトを作ってくれて、user callback を渡すための関数を用意してくれる、というものです。この 2 つのコードを見てみましょう:
AuthServiceProvider の boot メソッド:
$this->app['auth']->viaRequest('api', function ($request) {
if ($request->input('api_token')) {
return User::where('api_token', $request->input('api_token'))->first();
}
});
public function viaRequest($driver, callable $callback) {
return $this->extend($driver, function () use ($callback) {
$guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());
$this->app->refresh('request', $guard, 'setRequest');
return $guard;
});
}
AuthServiceProvider が起動すると、viaRequest() は extend() を通じて api という名前の生成/ドライバメソッドを定義し、リクエストの api_token をデータベースと突き合わせてユーザーを取得する callback を、RequestGuard のコンストラクタ引数として渡します。次に app['auth']->guard('api') が呼ばれたときにインスタンス化され、callback にしたがってユーザーオブジェクトを返します。
おわりに
AuthManager は、認証アーキテクチャの中でいちばん役回りが複雑なオブジェクトでしょう。本来の機能に加えて、__call を使って Guard への動的な呼び出しも実装しています。つまり、app['auth']->guard()->guardFunc() の代わりに app['auth']->guardFunc() と直接書ける、ということです。これ、本当にアーキテクチャを追いかけづらいんですよね…
このシリーズ、書けば書くほど増えていく気がします。次の記事では、AuthManager と UserProvider の関係にもう少し踏み込んでいきます。読んでくださって、ありがとうございます。また次回に。


