Try T.M Engineer Blog

多摩市で生息するエンジニアが「アウトプットする事は大事だ」と思って始めたブログ

(Laravel第6回目)ファサード(Facade)からサービスコンテナを学ぶ

ファサード(Facade)とは?

前回でも書きましたが、デザインパターンの1つ。
ある処理や機能をClassに纏めておいて、どこからでも呼び出せる状態にしておく方法。これにより、メイン処理で使われるClassの独立性を高めることができます。

これは私が自作したファサード(Facade)ですが、以下のように、どこからもuse宣言されていないのに、HogeクラスのechoFuga()メソッドを呼ぶことができます。

■ welcome.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <title>HogePage</title>
    </head>
    <body>
        {{ Hoge::echoFuga() }}
    </body>
</html>

では、なぜこんなことができるのか、(自作した)上記ソースを追ってみましょう!

Hogeクラスはクラスではなく、エイリアス

上記でHogeクラスと書きましたが、実はこれはクラスではなく、エイリアスです。
config/app.configに以下のように定義しています。

■ config/app.conf

    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        ・
        ・
        ・
        'Hoge' => App\Facades\Hoge::class,
    ]

'Hoge'というエイリアス名で、'Hoge'クラスを登録しています。
この'Hoge'クラスにロジックが書かれて・・・いないです。

■ app/Facades/Hoge.php

<?php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;


class Hoge extends Facade
{
  protected static function getFacadeAccessor() {
    return 'fuga';
  }
}

この'Hoge'クラスは、getFacadeAccessorメソッドで'fuga'を返しています。この'fuga'は何なのでしょうか。
ここで出てくるのが、サービスコンテナです。

サービスコンテナとは?

クラスのインスタンスインスタンスの生成方法を記入しておいて、呼び出しがあった際にインスタンスを作れる状態にしておくことです。
このインスタンスインスタンスの生成方法を記入しておく場所ですが、Laravelだとサービスプロバイダーになります。

■ app/Providers/TestServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class TestServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(
            'fuga',
            'App\Http\Components\Fuga'
        );
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

サービスコンテナにインスタンス生成方法を登録することをbind(バインド)と呼びます。
インスタンスをサービスコンテナから返すことをresolve/resolving(解決する)と呼びます。

bind方法は色々とありますが、単純な方法は上記のようにapp->bindメソッドを使用して、第一引数に文字列(登録名)、第二引数にクロージャ(生成方法)を記入すればbindできます。
今回は、第二引数にクロージャを使用せずにFugaクラスを指定しました。最初に見たechoFuga()メソッドは、FugaクラスにあるechoFuga()メソッドだったのです。

■ app/Http/Components/Fuga.php

<?php
namespace App\Http\Components;

class Fuga
{
  public function echoFuga()
  {
    return 'HOGEと見せかけたFUGA';
  }
}

これで、上記'Hoge'クラスのgetFacadeAccessorメソッドで'fuga'を返している理由がわかりました。
このサービスコンテナの'fuga'を呼んでいたんですね。

あれ?サービスコンテナのインスタンスってどうやって作ってるの?

'Hoge'クラスの継承元のFacadesでインスタンスを作っています。
Facadesクラスには以下__callStatic()と呼ばれるマジックメソッドがあり、アクセス不能メソッドがあった場合にインスタンスを作るようになっています。

__callStatic() は、 アクセス不能メソッドを静的コンテキストで実行したときに起動します。

■以下より引用
https://www.php.net/manual/ja/language.oop5.overloading.php#object.callstatic

■ Illuminate\Support\Facades\Facade.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);
    }

なので、サービスコンテナ内でインスタンスを作るのではなく、サービスコンテナの呼び出し先でインスタンスを作るのが良さそう。

最後に整理

さて、ここまでファサードからサービスコンテナ、エイリアスを学んできました。
最後に図にしてみます。だいたいこんな感じ?

f:id:special-moucom:20191007231155p:plain

Laravelを学んでいると必ず出てくるファサードとサービスコンテナについて、なんとなくわかった気がしました。うーん、Laravelは奥が深い(= = ;;