LaravelでAuthやGate等で何気なく使っているFacadeですが、実際どういう仕組みになっているのか調べてみました。
今回はGateを例にGateファサードのメソッドを呼び出した際の動きを追って行こうと思います。
Gateとは
GateとはLaravelで認可を行うための方法の一つです。
https://readouble.com/laravel/5.4/ja/authorization.html
Gateを使う側
Gateは例えば以下のように、App\Providers\AuthServiceProvider
内に記述します。
use Illuminate\Support\Facades\Gate;
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
Gateファサードの仕組み
上記のコードで、Gate::define()
のようにメソッドを呼び出しています。
このGateファサードですが、Illuminate\Support\Facades\Gate
クラスをuseしているため、defineの動きを見るためにIlluminate\Support\Facades\Gate
を見てみます。
Illuminate\Support\Facades\Gate
クラス
こちらがIlluminate\Support\Facades\Gate
クラスです。
<?php
namespace Illuminate\Support\Facades;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
/**
* @see \Illuminate\Contracts\Auth\Access\Gate
*/
class Gate extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return GateContract::class;
}
}
ムムム、、
defineメソッドを見ようと思ったのにdefineメソッドなんて無いじゃ無いか。。
Illuminate\Support\Facades\Facade
クラス
そこで、Gateクラスが継承しているFacadeクラスを見てみると、以下のような__callStatic()
マジックメソッドが記述されていました。
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
__callStatic()
マジックメソッドは自分のクラスに定義されていないstaticメソッドが呼び出された時に実行されるメソッドです。
今回Gate::define()
が呼び出されましたが、define()
メソッドはGateクラスにも基底クラスのFacadeクラスにも存在しなかったので、この__callStatic()
マジックメソッドが呼び出されています。
getFacadeRoot()
メソッド
__callStatic()
マジックメソッドをみると同じFacadeクラスのgetFacadeRoot()
というメソッドが呼び出され、戻り値が$instance
に格納され、$instance->$method(...$args)
がreturnされています。
想像するに、$instanceにdefineメソッドが定義されているインスタンスが渡されて、$instance->$method(...$args)
の$methodでdefineメソッドが実行されているようです。
getFacadeRoot()
メソッドの中身は以下のようになっています。
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
ここにある、getFacadeAccessor()
メソッドですが、FacadeクラスのメソッドをこクラスのGateクラスでoverrideしてあります。
getFacadeAccessor()
メソッド
protected static function getFacadeAccessor()
{
return GateContract::class;
}
上記のように、GateContract::class
が返されています。
FacadeクラスではこのGateContract::class
という文字列を使って必要なインスタンスを取得し、defineメソッドを実行しているようです。
(→このインスタンスの取得がどこで行われているかはよく分からなかった><)
全体の流れ
Gate::define()が実行された場合
1.Gateクラスや基底クラスのFacadeクラスにdefineメソッドは存在しないため、Facadeクラスの__callStatic()
マジックメソッドが呼び出される
2.__callStatic()
の中で、getFacadeRoot
が呼び出される
3.getFacadeRoot
内でresolveFacadeInstance
が呼び出され、その引数にgetFacadeAccessor
で返されるクラス名が入る
4.このgetFacadeAccessor
だけ継承クラスであるGateクラス内でoverrideされており、GateContract::class
がresolveFacadeInstance
の引数に入る
5.返されたクラスをインスタンス化して、$instance->$method(...$args)
でメソッドを実行する
上記の4の部分でGateファサードの場合はGateContract::class
がインスタンス化されましたが、例えばCacheファサードの場合や他のファサードの場合は、それぞれに応じたクラスがインスタンス化されます。
何が良いのか
ファサードを使う側が必要なクラスのインスタンス化を全く意識しなくて良い、というのは大きいと思います。
デフォルトで提供されているファサードだけでなく、自分で静的メソッドを使うようなクラスを作る場合、ファサードを継承して、サービスプロバイダに登録するだけでデフォルトのファサードと同様の形で使うことが出来ます。
(使う場合は、use Gate
のようにファサードをuseする必要があります。ただ、config/app.php
のalias
に登録すれば名前空間全部を記述しなくても、ファサード名だけをuseすれば使えるので便利です)
あと、テストの際にモックを使いやすくなるようです。
詳しくは古い記事ですが以下の記事をご参照下さい。
http://takyam.hateblo.jp/entry/2013/07/14/Laravel_4_%E3%81%A7%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%A6%E3%82%8B_Facade%28%E3%83%95%E3%82%A1%E3%82%B5%E3%83%BC%E3%83%89%29_%E3%81%8C%E7%B4%A0%E6%99%B4%E3%82%89%E3%81%97%E3%81%84%E4%BB%B6
大雑把な理解のため、細かい誤りがあるかもしれません。
ご指摘あればコメント欄にお願い致しますm(_ _)m