LaravelのFacadeの仕組みを調べてみた

LaravelでAuthやGate等で何気なく使っているFacadeですが、実際どういう仕組みになっているのか調べてみました。

今回はGateを例にGateファサードのメソッドを呼び出した際の動きを追って行こうと思います。

##Gateとは
GateとはLaravelで認可を行うための方法の一つです。
https://readouble.com/laravel/5.4/ja/authorization.html

##Gateを使う側
Gateは例えば以下のように、`App\Providers\AuthServiceProvider`内に記述します。

```php
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
<?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()`マジックメソッドが記述されていました。

```php
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()`メソッドの中身は以下のようになっています。

```php
public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}
```

ここにある、`getFacadeAccessor()`メソッドですが、FacadeクラスのメソッドをこクラスのGateクラスでoverrideしてあります。


###`getFacadeAccessor()`メソッド

```php
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