【Laravel】ファサードの仕組み – 実装を読んで理解する

Laravel

こんにちは、aiiro(@aiiro29)です。

Laravelのファサードは初めて見た人の中には、staticクラスをファサードと呼んでいるのかと誤解してしまう人がいるかもしれません。

実際私は最初そう思いました。

ですが、ファサードはstaticクラスではありません。

それではファサードとは何なのでしょうか?

この記事では、ファサードがどのように実装されているかを読み、その動作について確認したいと思います。

スポンサー

要約

  • Facadeはstaticクラスではない
  • FacadeはgetFacadeRoot()を使って、コンテナから取得したインスタンスを返却している
  • Facade::__callStatic()を使って、返却されたインスタンスのメソッドを呼び出している

説明

前回は自作ヘルパー関数の追加方法について紹介しました。

今回はLaravelで特徴的な機能の一つであるファサードについて、仕組みを追いかけつつ説明したいと思います。

ファサードのエイリアスはどこで定義されているか?


'aliases' => [

    'App' => Illuminate\Support\Facades\App::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,
    'Crypt' => Illuminate\Support\Facades\Crypt::class,
    'DB' => Illuminate\Support\Facades\DB::class,
    'Eloquent' => Illuminate\Database\Eloquent\Model::class,
    'Event' => Illuminate\Support\Facades\Event::class,
    'File' => Illuminate\Support\Facades\File::class,
    'Gate' => Illuminate\Support\Facades\Gate::class,
    'Hash' => Illuminate\Support\Facades\Hash::class,
    'Lang' => Illuminate\Support\Facades\Lang::class,
    'Log' => Illuminate\Support\Facades\Log::class,
    'Mail' => Illuminate\Support\Facades\Mail::class,
    'Notification' => Illuminate\Support\Facades\Notification::class,
    'Password' => Illuminate\Support\Facades\Password::class,
    'Queue' => Illuminate\Support\Facades\Queue::class,
    'Redirect' => Illuminate\Support\Facades\Redirect::class,
    'Redis' => Illuminate\Support\Facades\Redis::class,
    'Request' => Illuminate\Support\Facades\Request::class,
    'Response' => Illuminate\Support\Facades\Response::class,
    'Route' => Illuminate\Support\Facades\Route::class,
    'Schema' => Illuminate\Support\Facades\Schema::class,
    'Session' => Illuminate\Support\Facades\Session::class,
    'Storage' => Illuminate\Support\Facades\Storage::class,
    'URL' => Illuminate\Support\Facades\URL::class,
    'Validator' => Illuminate\Support\Facades\Validator::class,
    'View' => Illuminate\Support\Facades\View::class,

],

上記はconfig/app.phpのaliasesの内容です。

この配列の各キーがファサードのエイリアスになっています。

Illuminate\Log\Logger\Logファサードの実装を読む

例としてLogのファサードを読んでみることにします。


namespace Illuminate\Support\Facades;

use Psr\Log\LoggerInterface;

/**
 * @method static void emergency(string $message, array $context = [])
 * @method static void alert(string $message, array $context = [])
 * @method static void critical(string $message, array $context = [])
 * @method static void error(string $message, array $context = [])
 * @method static void warning(string $message, array $context = [])
 * @method static void notice(string $message, array $context = [])
 * @method static void info(string $message, array $context = [])
 * @method static void debug(string $message, array $context = [])
 * @method static void log($level, string $message, array $context = [])
 * @method static mixed channel(string $channel = null)
 * @method static \Psr\Log\LoggerInterface stack(array $channels, string $channel = null)
 *
 * @see \Illuminate\Log\Logger
 */
class Log extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return LoggerInterface::class;
    }
}

そしてLogファサードに紐づくIlluminate\Support\Facades\Log::classは、上記のように実装されています。

内容は、次の2点です。

  • `Illuminate\Support\Facades\Facade`を継承している。
  • `LoggerInterface::class`を返却する、getFacadeAccessor()が定義されている。

他に実装が無いため、少し面食らうところなのですが、焦らずにIlluminate\Support\Facades\Facade.phpを見ていきます。

そうすると、__callStatic()が実装されていることがわかります。

Illuminate\Support\Facades\Facadeの実装を読む


/**
 * Handle dynamic, static calls to the object.
 *
 * @param  string  $method
 * @param  array   $args
 * @return mixed
 *
 * @throws \RuntimeException
 */
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()は\Illuminate\Log\Logger\Logのアクセス不能なメソッドが静的に呼び出された時、つまりLog::debug('foo', ['bar'])が呼び出された際に呼び出されます。

__callStatic()の詳細については、こちらの記事を参照下さい。

【PHP】マジックメソッド - __callStatic()等の使い方を解説します
今回はPHPのマジックメソッドの使い方について説明します。PHPのクラスにおいて__get()や__set()等のように、__で始まる関数はマジックメソッドと呼ばれる特殊な関数です。それぞれ特定の条件下において実行されるようになって...
※Laravelではファサードに限らず、様々な場所で__callStatic()が使用されています。

上記の例であれば、__callStatic()の$methodにはdebugが、$argsには配列で['foo', ['bar']]が渡されます。


/**
 * Get the root object behind the facade.
 *
 * @return mixed
 */
public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}

更にgetFacadesRoot()は次のように、getFacadeAccessor()を引数として、resolveFacadeInstance()を実行しています。

Logファサードを呼び出す場合、getFacadeAccessor()は前述の通りLoggerInterface::classを返却します。


/**
 * Resolve the facade root instance from the container.
 *
 * @param  string|object  $name
 * @return mixed
 */
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[$name]をreturnしていますが、この$appはIlluminate\Container\Containerクラスを継承したIlluminate\Foundation\Applicationクラスのコンテナです。

つまり、resolveFacadeInstance()がクラス名を使ってコンテナからインスタンスを取得して返却しているということになります。

※デフォルトの設定の場合、Illuminate\Log\LogManagerのインスタンスが返却されます。


/**
 * Handle dynamic, static calls to the object.
 *
 * @param  string  $method
 * @param  array   $args
 * @return mixed
 *
 * @throws \RuntimeException
 */
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);
}

再びFacadeの__callStatic()に戻ります。

$instanceにはgetFacadeRoot()からの返り値である、コンテナから取得したインスタンスがセットされています。

$instance->$method(...$args)は、$methodにはdebug、$argsには配列で['foo', ['bar']]が渡された状態で実行されます。

以上がファサードが呼び出された際に実行されている処理の流れです。

次のステップ

ファサードは既に用意されているものを使用するだけでなく、独自ファサードを追加することもできます。

次回は独自ファサードを追加する方法について紹介したいと思います。

ファサードの自作方法の解説記事を書きました。