LaravelのAuth Facadeのuser()は一体何をしているのか?が気になりすぎて夜しか眠れなかったのでコードを読んでみた。
はじめに
こんにちは、まさき。です。
この記事はNEアドベントカレンダー5日目の記事になります。
えっ?昨日もこの人の記事見た?ありがとうございます!
突然ですが、LaravelにはFacade(ファサード)という仕組みがあり、認証系はAuth Facadeを呼び出すことで簡単に実装することができます。
たとえば、Auth::user()を呼び出すとログインしているユーザーを取得することができます。
では、なぜこれでログイン中のユーザーが取得できるか考えたことはありますか?私は考えたことがなかったので、今回はコードを追いながらその仕組みを解明していきます!
本記事を読むことで、Auth::user()がどのように動作しているかを理解することができます。また、その過程でFacadeがどのように特定のクラスのメソッドを呼び出しているのかにも触れています。
きっかけ
私は、副業でPHPでのWebアプリケーション開発を教えています。誰かに教えることで自分の理解が深まったり、教えるためにはもっと知ってなきゃいけないなーとか、この人に教わりたいと思ってもらえるようなエンジニアにならなきゃなーとか思うようになったりと、思わぬ副産物が得られています。
先日、PHPのWebフレームワークであるLaravelを教えていて、Auth Facadeでログインしているユーザーを取得する処理の説明に詰まってしまいました。
この機会に、今までなんとなく使っていたAuth Facadeをコードを読んで理解してみることにしました。
コードリーディング
前提
今回のコードリーディングの目的は、Auth::user()の実装を理解することです。したがって、それ以外のことはあまり深追いしません。
今回は、Laravel 10.x系のコードを読みます。
Laravel
Laravelは、PHPのWebアプリケーションフレームワークです。
laravel/frameworkにコードがあるので、これをコードリーディングしていきます。
github.devでVSCodeで読めます!
Facadeとは?
【Laravel】ファサードとは?何が便利か?どういう仕組みか?
ファサードとは、
クラスをインスタンス化しなくてもstaticメソッドのように
メソッドを実行できるようにしてくれる機能のこと。
Facadeは、Laravelのサービスコンテナを通じて基盤クラスにアクセスするためのシンプルなインターフェースです。たとえば、DB::table('users')->get()でデータベースにアクセスできるのも、DBというFacadeが背後でサービスコンテナからデータベース接続を解決しているおかげです。この仕組みを理解することで、Facadeを使った簡潔なコードの背景にある強力な設計思想を学ぶことができます。
Auth::user()を紐解く その1 Facade
// ログインしているユーザーを取得する
$login_users = Auth::user();
このような処理でログインしているユーザーをどうやって取得しているのかを理解するためにコードを読んでいきます。
まず、Illuminate\Support\Facades\Auth
を見ていきます。
laravel/framework/src/Illuminate/Support/Facades/Auth.php
とりあえず、AuthFacadeのファイルをuser()で検索してみると上記のような記述が見つかります。が、肝心のメソッドが見つかりません。
AuthクラスはFacadeを継承していて、getFacadeAccessorが以下のようにオーバーライドされていました。
そこで、Facadeクラスの方を見てみます。このファイルの中でまず、user()がないかを見てみますが、やはり見当たりません。
が、一番下にマジックメソッド __callStaticが実装されてました。
__callStatic() は、 アクセス不能メソッドをstatic メソッドとして呼び出した場合に起動します。
引数 $name は、 コールしようとしたメソッドの名前です。 引数 $arguments は配列で、メソッド $name に渡そうとしたパラメータが格納されます。
https://www.php.net/manual/ja/language.oop5.overloading.php#object.callstatic
AuthにもFacadeにもuser()というメソッドは定義されておらず、まさにアクセス不能なuserメソッドをStaticメソッドとして呼び出しています。(これに気づくのにだいぶ時間使った、、、のはココだけの話)
resolveFacadeInstanceの中でddでデバッグしたところ、static::$app[$name];
が呼ばれてそうということがわかりました。$nameは、getFacadeAccessorで'auth'であることがわかります。
static::$appってなんだ?とコードジャンプすると、Containerインスタンスへのヘルパーメソッドだったことがわかります。引数の指定もないので、このContainerインスタンスは、\Illuminate\Foundation\Applicationであることがわかりました。(実際にtinkerで確認できた)
ちょっと邪道ですが、このApplication.phpの中で'auth'で検索してみると、以下のようにregisterCoreContainerAliasesの中で見つかります。
以下のメソッドでは、authというキーでAuthManager::classを呼び出せるようにエイリアスを設定しています。
/**
* Alias a type to a different name.
*
* @param string $abstract
* @param string $alias
* @return void
*
* @throws \LogicException
*/
public function alias($abstract, $alias)
{
if ($alias === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
$this->aliases[$alias] = $abstract; // '\Illuminate\Auth\AuthManager::class' => 'auth'
$this->abstractAliases[$abstract][] = $alias; // 'auth' => [\Illuminate\Auth\AuthManager::class]
}
Applicationのコンストラクタでこのメソッドが呼ばれていることも確認できました。
ということで、AuthFacadeの正体は、\Illuminate\Auth\AuthManagerであることが判明しました。
つまり、Facadeでメソッドを呼び出しをすると、Laravelのサービスコンテナ(app)を通じて、AuthManagerのような実装クラスを取得し、そのクラスのメソッドを呼び出す仕組みになっています。
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
で、これは、LaravelのFacadeの説明のところに書いてあるものと一致します。コード読む必要なかった。
Auth::user()を紐解く その2 AuthManager
AuthManagerクラスにきっとuser()はいるはず!ということでuser()を探しましたが、やはり見つかりませんでした。
そして案の定、マジックメソッド__call()が定義されていました。
__call() は、 アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動します。https://www.php.net/manual/ja/language.oop5.overloading.php#object.call
引数なしでguard()がコールされているのでgetDefaultDriver()から$nameを取得します。
ちなみに、取得される値は以下の通り。App\Models\User::classがここで登場することがわかります。
特にCustomCreatorsを定義してないので、configのdriverに従い、createSessionDriverメソッドを呼び出します。
guardの正体がSessionGuardであることが判明しました。
Auth::user()を紐解く その3 SessionGuard
user()はSessionGuardに実装されていたようです。
このメソッドに渡ってきたLaravelのSessionをdd()で確認したものが以下の図になります。これによるとlogin_xxxxx => 1
がattributesにあることが確認できます。この1こそが、現在ログイン中のuserのidであり、最終的に返却されるApp\Models\Userのidです。
SessionGuardインスタンスに渡されているProviderは、AuthManagerのcreateSessionDriverで、以下のcreateUserProvider()によって返却されています。configにある通り、driverはEloquentで、modelはApp\Models\Userが指定されています。
これにより、以下のretrieveByIdより、Userモデルが取得されるわけですね。
Auth::user()を紐解く まとめ
- Auth::user()呼び出し → Facadeの__callStaticに転送
- getFacadeAccessorでauthを取得 → サービスコンテナからAuthManagerを解決
- AuthManagerがSessionGuardを生成 → SessionGuardのuserメソッドを実行
- SessionGuardがセッション情報を使い、EloquentUserProviderからUserモデルを取得
Auth::user()でログイン中のユーザーが取得できるのは、セッション情報にログイン中のuser_idが保持されており、そのuser_idからApp\Models\Userを取得しているからと言えそうですね。
まとめ
この記事では、LaravelのAuth Facadeのコードリーディングを行いました。最終的に、Auth::user()がいかにしてAuthenticatableなApp\Models\Userを返却するのかをコードを提示しながら説明しました。
LaravelのFacadeはマジックメソッドを活用することで、Staticメソッドとして気軽に呼び出せるという利点を持ちつつ、内部的には動的に依存関係を解決することができ、高い拡張性やテストのしやすさを実現していると考えられます。
Facadeの仕組みを理解することにより、オリジナルなFacadeを定義できるようになったり、configを通じてAuthの挙動を変更したりすることができるようになります。
この記事がFacadeやAuthFacadeを理解する一助になれば幸いです。
NE株式会社のエンジニアを中心に更新していくPublicationです。 NEでは、「コマースに熱狂を。」をパーパスに掲げ、ECやその周辺領域の事業に取り組んでいます。 Homepage: ne-inc.jp/
Discussion