こんにちは、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()の詳細については、こちらの記事を参照下さい。

上記の例であれば、__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']]
が渡された状態で実行されます。
以上がファサードが呼び出された際に実行されている処理の流れです。
次のステップ
ファサードは既に用意されているものを使用するだけでなく、独自ファサードを追加することもできます。
次回は独自ファサードを追加する方法について紹介したいと思います。