LoginSignup
11
11

More than 5 years have passed since last update.

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

Posted at

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::classresolveFacadeInstanceの引数に入る

5.返されたクラスをインスタンス化して、$instance->$method(...$args)でメソッドを実行する

上記の4の部分でGateファサードの場合はGateContract::classがインスタンス化されましたが、例えばCacheファサードの場合や他のファサードの場合は、それぞれに応じたクラスがインスタンス化されます。

何が良いのか

ファサードを使う側が必要なクラスのインスタンス化を全く意識しなくて良い、というのは大きいと思います。
デフォルトで提供されているファサードだけでなく、自分で静的メソッドを使うようなクラスを作る場合、ファサードを継承して、サービスプロバイダに登録するだけでデフォルトのファサードと同様の形で使うことが出来ます。

(使う場合は、use Gateのようにファサードをuseする必要があります。ただ、config/app.phpaliasに登録すれば名前空間全部を記述しなくても、ファサード名だけを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

11
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
11