Laravel の Auth Facade の仕組みを理解する
先日、次の記事を投稿した。
この記事中に
Auth::login()
を呼び出すと\Illuminate\Auth\SessionGuard::login()
が呼び出される。
とあるが、公式ドキュメントの Facade Class Reference を読むと Auth
ファサードは Illuminate\Auth\AuthManager
に基づいているとあり、話が違うじゃないと思われるかもしれない。結論を言うと AuthManager
を通して SessionGuard
のメソッドを呼び出すのだが、本記事ではこれがどのように実現されているのか、 Laravel のソースコードを読んで理解する。
環境
- PHP 8.2
- Laravel 10.20.0
前提
公式ドキュメントの Facade のページの内容を前提知識とする。
Laravel のソースコードを読む
今回は公式ドキュメントに従って Laravel Sail の環境を構築し、 Laravel Breeze を使って認証機能の土台を追加した。
Auth
ファサードと AuthManager
の関係
まずは Auth
ファサードと AuthManager
の関係について理解するため、サービスコンテナへのバインディングの処理について調べていく。このセクションの内容は他のファサードクラスの仕組みを理解する上でも利用できる。
Auth
ファサードは次のようになっている。
<?php
namespace Illuminate\Support\Facades;
// ...(中略)...
class Auth extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'auth';
}
// ...(中略)...
}
ファサードは基本的に getFacadeAccessor()
で返却された文字列にバインドされたオブジェクトをサービスコンテナに解決させる。 Auth
ファサードの場合、サービスコンテナは auth
という名前でバインドされたオブジェクトがあれば、それを返す。このバインディングはサービスプロバイダを通して登録されており、それは config/app.php
を辿って見つけることができる。
<?php
// ...(中略)...
return [
// ...(中略)...
'providers' => ServiceProvider::defaultProviders()->merge([
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
])->toArray(),
// ...(中略)...
];
ここで ServiceProvider::defaultProviders()
は DefaultProviders
のインスタンスを単に生成して返却する。
<?php
namespace Illuminate\Support;
class DefaultProviders
{
// ...(中略)...
/**
* Create a new default provider collection.
*
* @return void
*/
public function __construct(?array $providers = null)
{
$this->providers = $providers ?: [
\Illuminate\Auth\AuthServiceProvider::class,
\Illuminate\Broadcasting\BroadcastServiceProvider::class,
\Illuminate\Bus\BusServiceProvider::class,
\Illuminate\Cache\CacheServiceProvider::class,
\Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
\Illuminate\Cookie\CookieServiceProvider::class,
\Illuminate\Database\DatabaseServiceProvider::class,
\Illuminate\Encryption\EncryptionServiceProvider::class,
\Illuminate\Filesystem\FilesystemServiceProvider::class,
\Illuminate\Foundation\Providers\FoundationServiceProvider::class,
\Illuminate\Hashing\HashServiceProvider::class,
\Illuminate\Mail\MailServiceProvider::class,
\Illuminate\Notifications\NotificationServiceProvider::class,
\Illuminate\Pagination\PaginationServiceProvider::class,
\Illuminate\Pipeline\PipelineServiceProvider::class,
\Illuminate\Queue\QueueServiceProvider::class,
\Illuminate\Redis\RedisServiceProvider::class,
\Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
\Illuminate\Session\SessionServiceProvider::class,
\Illuminate\Translation\TranslationServiceProvider::class,
\Illuminate\Validation\ValidationServiceProvider::class,
\Illuminate\View\ViewServiceProvider::class,
];
}
// ...(中略)...
}
さらに \Illuminate\Auth\AuthServiceProvider
を見ると、 auth
という名前でバインディングする処理を見つけることができる。
<?php
namespace Illuminate\Auth;
// ...(中略)...
class AuthServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerAuthenticator();
$this->registerUserResolver();
$this->registerAccessGate();
$this->registerRequirePassword();
$this->registerRequestRebindHandler();
$this->registerEventRebindHandler();
}
/**
* Register the authenticator services.
*
* @return void
*/
protected function registerAuthenticator()
{
$this->app->singleton('auth', fn ($app) => new AuthManager($app));
$this->app->singleton('auth.driver', fn ($app) => $app['auth']->guard());
}
// ...(中略)...
}
これを見ると、 auth
という名前でバインディングされているのは Illuminate\Auth\AuthManager
のインスタンスであることが分かる。
Auth
ファサードと SessionGuard
の関係
次に、 Auth::login()
を呼び出すと \Illuminate\Auth\SessionGuard::login()
が呼び出される点について調べる。 AuthManager
のコードを読むと分かるが、このクラスには login()
メソッドが定義されていない。そのため、 PHP のマジックメソッドである AuthManager::__call()
メソッドが呼び出される。
<?php
namespace Illuminate\Auth;
// ...(中略)...
class AuthManager implements FactoryContract
{
// ...(中略)...
/**
* Dynamically call the default driver instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
}
このメソッドではまず guard()
が呼び出され、さらにその返り値のインスタンスが持つ $method
という名前のメソッドが呼び出されることが分かる。
/**
* Attempt to get the guard from the local cache.
*
* @param string|null $name
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*/
public function guard($name = null)
{
$name = $name ?: $this->getDefaultDriver();
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
guard()
メソッド内では getDefaultDriver()
によってガード名が取得されるが、これはデフォルトの config/auth.php
の設定であれば web
となり、 resolve()
メソッドでこのガードを解決しようとする。
/**
* Resolve the given guard.
*
* @param string $name
* @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
*
* @throws \InvalidArgumentException
*/
protected function resolve($name)
{
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException(
"Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
);
}
resolve()
メソッド内では 'create'.ucfirst($config['driver']).'Driver'
によって作成された名前のメソッドを呼び出して返り値を返している。 web
ガードのドライバは session
であるため、 createSessionDriver
という文字列が組み立てられ、このメソッドが呼び出される。
/**
* Create a session based authentication guard.
*
* @param string $name
* @param array $config
* @return \Illuminate\Auth\SessionGuard
*/
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider'] ?? null);
$guard = new SessionGuard(
$name,
$provider,
$this->app['session.store'],
);
// ...(中略)...
return $guard;
}
ここで SessionGuard
のインスタンスを生成して返却する処理を見つけることができる。返却されたインスタンスは前述した __call()
メソッド内の guard()
の返り値になるため、 SessionGuard::login()
メソッドが呼び出されることになる。
まとめ
サマリーすると次のようになる。
-
Auth
ファサードはIlluminate\Auth\AuthManager
に基づいている -
login()
メソッドのようにIlluminate\Auth\AuthManager
に定義が存在しない場合、__call()
メソッドが呼び出される -
__call()
メソッド内で認証ガードが解決されてSessionGuard
インスタンスが生成され、SessionGuard::login()
メソッドが呼び出される。
Discussion