Laravelのファサード(Facade)を使うことでLaravel内のサービスを簡単に利用することができます。複雑な依存関係を持つサービスもファサードを利用することでシンプルな記述で済むため、入門者にとっても便利な機能です。

その反面簡単に利用できることで、どのような仕組みで動いているかわからない人も多いのが現状です。今回は、ファサードがどうような仕組みで動作するのか確認を行い、自作のファサードを追加する方法について説明を行なっています。

ファサードを理解するためには、事前にサービスコンテナの仕組みとサービスプロバイダーの設定方法を理解していれば簡単です。下記の文書を参考にしてください。

ファーサードの仕組みを理解する

Routeファサード

Laravelをインストール後に一番最初に目にするファサードは、ルーティングファイルweb.phpに記述されているRouteファサードです。最も身近なRouteファサードを通してファサードがどのように実行されているか仕組みを確認していきます。


Route::get('/', function () {
    return view('welcome');
});

Routeファサードに関するコードの確認

ここから少しコードを追っていきますが、複雑なコードは全く出てこないので安心して読み進めてください。

Routeファサードで使用されているRouteクラスはconfig\app.phpに記述されているaliasesの中で’Route’ => Illuminate\Support\Facades\Route::classにひもづけられているため、Routeファサードを利用するとIlluminate\Support\Facades\Route::classが実行されます。


'aliases' => [
    'App' => Illuminate\Support\Facades\App::class,
    'Arr' => Illuminate\Support\Arr::class,
    'Artisan' => Illuminate\Support\Facades\Artisan::class,
    'Auth' => Illuminate\Support\Facades\Auth::class,
    'Blade' => Illuminate\Support\Facades\Blade::class,
    'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
    'Bus' => Illuminate\Support\Facades\Bus::class,
    'Cache' => Illuminate\Support\Facades\Cache::class,
    'Config' => Illuminate\Support\Facades\Config::class,
    'Cookie' => Illuminate\Support\Facades\Cookie::class,
      ・
    'Route' => Illuminate\Support\Facades\Route::class,
      ・
config/app.phpのaliasesにはRoute以外にもさまざまなファサードの設定が行われています。

Routeクラスには、getFacadeAccessorメソッドのみ記述されており、Facadeクラスを継承していることがわかります。


class Route extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}   

下記は、Routeファサードを使ってstaticメソッドgetを実行していますが、Illuminate\Support\Facades\Facadeクラスの中身を見てもgetメソッドに関係する記述を見つけることができます。


Route::get('/',・・・);

しかしFacade.phpファイルの一番下にマジックメソッドである__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メソッドを使ってそのクラスに存在しないメソッドを使おうとした時に呼ばれるメソッドです。

Route::get()メソッドを使った時、lluminate\Support\Facades\Routeクラスにも継承しているIlluminate\Support\Facades\Facadeクラスにもgetメソッドはありません。そのため、マジックメソッドである__callStaticメソッドが実行されます。

Facade.phpクラスにstaticメソッドでgetメソッドを追加すると__callStaticメソッドではなくgetメソッドが実行されます。このようにstaticメソッドがそのクラスに存在すると__callStaticメソッドは呼ばれません。

__callstaticメソッドの中身を確認していくと最初にgetFacadeRootメソッドが実行されています。


$instance = static::getFacadeRoot(); 

getFacadeRootメソッド内では、最初に確認したIlluminate\Support\Facades\RouteクラスのgetFacadeAccessorメソッドを使っていることを確認できます。getFacadeAccessorメソッドの戻り値はrouterです。


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

static::getFacadeAccessor()にはrouterが入るので、理解しやすいようにrouter文字列を引数に入れてresolveFacadeInstanceメソッドを確認します。


public static function getFacadeRoot()
{
    return static::resolveFacadeInstance('router');
}

resolveFacadeInstanceメソッドのコードは下記の通りです。$nameには’router’が入っているため、オブジェクトではないので、後半にあるstatic::$app[‘router’]が何を行なっているかが重要になります。


protected static function resolveFacadeInstance($name)
{

    if (is_object($name)) {
        return $name;
    }

    if (isset(static::$resolvedInstance[$name])) {
        return static::$resolvedInstance[$name];
    }

    return static::$resolvedInstance[$name] = static::$app[$name];
}

static::$appはヘルパーメソッドapp()と同じ動作を行うので、static::$app[‘router]はapp()[‘router’]と同じことを意味します。これはサービスコンテナからrouterサービスのインスタンスが起動させる下記のコードと同じことを表しています。


app()->make('router')

__callStaticメソッドの$instanceがapp()->make(‘router’)に置き換えることができます。Router::getではgetはstaticメソッドでしたが、実際はファサードを使用してrouterインスンスを起動して、router->get()を実行しているのです。


public static function __callStatic($method, $args)
{

    $instance = app()->make('router'); // static::getFacadeRoot()

    if (! $instance) {
        throw new RuntimeException('A facade root has not been set.');
    }

    return $instance->$method(...$args);
}   ・

ここまで確認できれば、Router::get()がどのような仕組みで動作しているかが理解できたと思います。また、ファーサードはLaravelのコアであるサービスコンテナを利用しているため、サービスコンテナと深く結びついていることがわかります。

このためファサードを理解するためには、事前にサービスコンテナの知識が必要になります。

ファサードを作成する

ファサードの動作の仕組みが理解できれば自分で作ったサービスをファサードに登録することはそれほど難しいものではありません。先程までの説明を元にファーサードを新規で追加します。

サービスプロバイダーの作成

ファサードを登録するためにサービスプロバイダーを作成します。サービスプロバイダーの作成については下記の文書を参考にしてください。

追加するサービスプロバイダーファイルの名前はOwnServiceProviderでphp artisan make:provider OwnServiceProviderで作成します。中身はmyNameとい名前でサービスコンテナへ登録を行い、実行するとMyNameというクラスがインスタンス化されます。


namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class OwnServiceProvider extends ServiceProvider
{

    public function register()
    {
         $this->app->bind('myName', \Reffect\MyName::class);
    }

    public function boot()
    {
        //
    }
}

MyName.phpクラスにはrunメソッドを追加し、実行するとJohn Doeという名前が返されるシンプルなものです。


namespace Reffect;

class MyName
{
    
    public function run(){

        return 'John Doe';

    }
   
}

ファサードファイルの作成

ファサードファイルを作成する場所はLaravelのインストールフォルダの下にあるvendar/laravel/framework/src/illuminate/Supprt/Facadesの下です。Own.phpという名前でファイルを作成します。

中身はgetFacadeAccessorメソッドのみ記述し、戻り値にはサービスプロバイダーで設定したmyNameを設定します。


namespace Illuminate\Support\Facades;

class Own extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'myName';
    }
}

app.phpファイルへの追加

config/app.phpファイルに作成したファサードについての情報を追加します。


'aliases' => [

    'App' => Illuminate\Support\Facades\App::class,
    'Arr' => Illuminate\Support\Arr::class,
    ・
    ・
    'View' => Illuminate\Support\Facades\View::class,
    'Own' => Illuminate\Support\Facades\Own::class,  //これを追加

],

これでファサードを使用するための準備は完了です。

ファサードを実行

ファサードを使って下記のようにrunメソッドを実行するとJohn Doeが表示されます。


Own::run()
Class Not Foundのエラーが出る場合はuse Ownを設定してください。