ログイン認証の例
たとえばログイン認証のLoginControllerに使われるIllumination\Foundation\Auth\AuthenticatesUsersトレイトのlogin()メソッドは次のような内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public function login(Request $request) { // 入力バリデーション $this->validateLogin($request); // 連続ログインエラーへの対応 if (method_exists($this, 'hasTooManyLoginAttempts') && $this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } // ログイン認証処理 if ($this->attemptLogin($request)) { return $this->sendLoginResponse($request); } // ログイン失敗の場合の失敗回数インクリメント $this->incrementLoginAttempts($request); // ログイン失敗時の処理 return $this->sendFailedLoginResponse($request); } |
この中で呼ばれているログイン認証処理のattemptLogin()メソッドは、同じトレイトで定義されている。
|
|
protected function attemptLogin(Request $request) { return $this->guard()->attempt( $this->credentials($request), $request->filled('remember') ); } |
ここでguard()というメソッドが出てくる。これは同じAuthenticatesUsersトレイトの最後の方で以下のように定義されているが、そもそもどういうものかよくわからなかったので掘り下げてみたら、どんどん深みにはまってしまった。
|
|
/** * Get the guard to be used during authentication. * * @return \Illuminate\Contracts\Auth\StatefulGuard */ protected function guard() { return Auth::guard(); } |
guard()が見当たらない
AuthenticatesUsersトレイトでAuth::guard()が呼ばれているので、まずAuthクラスについて調べてみる。
AuthクラスはAuthenticatesUsersトレイトの最初で以下のようにuse定義されている。
|
|
<?php namespace Illuminate\Foundation\Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Validation\ValidationException; trait AuthenticatesUsers { ..... } |
そこでIlluminate\Foundation\Authクラスを見てみると、コメントにstaticメソッドらしきものが書かれているが、クラス定義の本体にはguard()メソッドが見当たらない。
|
|
/** * @method static \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard guard(string|null $name = null) ..... class Auth extends Facade { ..... } |
Authクラスの継承元のIlluminate\Support\Facades\Facadeクラスも見てみたが、useしているトレイトも含めてguard()メソッドは定義されていない。
|
|
<?php namespace Illuminate\Support\Facades; use Closure; use Mockery; use Mockery\MockInterface; use RuntimeException; abstract class Facade { ..... } |
マジックメソッド__callStatic()を使っている
__callStatic()の内容
いくつかのサイトを参考にしたところ、__callStatic()というマジックメソッドを使っていることがわかってきた。このマジックメソッドは、クラスに定義されていないstaticメソッドが呼ばれた際に実行される。
Facadeクラスを見てみると、以下のように__callStatic()が定義されていた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
/** * 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); } |
最初にあったAuth::guard()スタティックメソッドを呼ぶと、このマジックメソッドが呼ばれ、引数の$methodは'guard'、$argsは空の配列となる。
以下で、この__callStatic()の内容を読んでいく。
getFacadeRoot()
メソッドの最初でgetFacadeRoot()メソッドが呼ばれている。最終的に$instanceにはAuthManagerのインスタンスが返されるが、その道筋を追ってみた。最後の部分だけがまだ理解できておらず、参考サイトの受け売りだが。
|
|
$instance = static::getFacadeRoot() |
このメソッドは同じFacadeクラスで定義されていて、getFacadeAccessor()を引数に与えたresolveFacadeInstance()の戻り値を返している。
|
|
/** * Get the root object behind the facade. * * @return mixed */ public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); } |
getFacadeAccessor()
ここでstaticメソッドgetFacaceAccessor()は、Facadeクラスとこれを継承したAuthクラスの両方で定義されている。
FacadeクラスでのgetFacadeAccessor()の定義は、単に「メソッドが定義されていない」という例外を返す。
|
|
/** * Get the registered name of the component. * * @return string * * @throws \RuntimeException */ protected static function getFacadeAccessor() { throw new RuntimeException('Facade does not implement getFacadeAccessor method.'); } |
一方、Facadeを継承したAuthクラスで定義されたgetFacadeAccessor()は文字列'auth'を返す。
|
|
/** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'auth'; } |
getFacadeRoot()がFacadeを継承したAuthクラスから呼ばれると、AuthクラスのstaticメソッドがFacadeクラスのstaticメソッドをオーバーライドして実行される。
この結果、resolveFacadeInstance()の引数には文字列'auth'が与えられる。
resolveFacadeInstance()
引数に'auth'を与えられたresolveFacadeInstance('auth')の挙動を見てみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
/** * Resolve the facade root instance from the container. * * @param object|string $name * @return mixed */ protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; } if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } if (static::$app) { return static::$resolvedInstance[$name] = static::$app[$name]; } } |
流れは以下の通り。
- 引数
$nameの内容は文字列なので、1つ目のifブロックはスルー。
static::$resolvedInstanceはFacadeで定義されていて、$nameに対応したインスタンスがあればそのインスタンスが得られる。
- もしインスタンスが登録されていなければ、最後の
ifブロックでstatic::$app['auth']の内容が登録されて、そのインスタンスが返される。
この$app['auth']がわからなかった。こちらのサイトの内容をひとまず丸呑みする。
static::$appはヘルパーメソッドapp()と同じ動作をする
static::$app[‘auth]はapp()[‘auth’]と同じ意味
- これはサービスコンテナから
authサービスのインスタンスが起動させる下記のコードと同じことを表す
app()->make(‘auth’)で作成されるサービスはIlluminate\Auth\AuthServiceProviderのregisterメソッドによって登録されており、Immulminate\Auth\AuthMangaerがインスタンス化されている
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
class AuthServiceProvider extends ServiceProvider { public function register() { $this->registerAuthenticator(); $this->registerUserResolver(); $this->registerAccessGate(); $this->registerRequirePassword(); $this->registerRequestRebindHandler(); $this->registerEventRebindHandler(); } protected function registerAuthenticator() { $this->app->singleton('auth', function ($app) { // Once the authentication service has actually been requested by the developer // we will set a variable in the application indicating such. This helps us // know that we need to set any queued cookies in the after event later. $app['auth.loaded'] = true; return new AuthManager($app); }); $this->app->singleton('auth.driver', function ($app) { return $app['auth']->guard(); }); } ..... } |
__callStatic()の戻り値
$instanceが適正にセットされれば、_callStatic()は以下のように戻り値を返す。
|
|
return $instance->$method(...$args); |
__callStatic()が呼ばれたときの$methodがguard()、引数は空だったので、戻り値のメソッドはAuthManager::guard()となる。
guard()はSessionGuard()になる
AuthManager::guard()
結局、attemptLogin()->guard()はAuthManager::guard()となり、その内容は以下の通り。
|
|
/** * 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); } |
引数なしで呼ばれるので、$nameはAuthManager::getDefaultDriver()となる。その内容は以下の通り。
|
|
public function getDefaultDriver() { return $this->app['config']['auth.defaults.guard']; } |
このメソッドは、configディレクトリーにあるauth.phpのdefaultsのキー'guard'に対する値を返す。デフォルトでその内容は'web'となっている。
|
|
'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], |
そして、最後のreturn文にある$this->guards[$name]は$this->guards['web']となるが、これが存在しない場合は??演算子の右側が評価されて、AuthManager::resolve('web')が内容としてセットされる。
AuthManager::resolve()
AuthManager::resolve()の内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
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." ); } |
最初の実行文で呼ばれるgetConfig('web')により、config/auth.phpのguardsセクションの'web'キーに対応する値が$configとしてセットされる。
|
|
protected function getConfig($name) { return $this->app['config']["auth.guards.{$name}"]; } |
auth.phpの該当する部分は以下の通り。
|
|
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ], |
そして、$driverMethodに文字列連結で生成された文字列'createSessionDriver'が格納される。
|
|
$driverMethod = 'create'.ucfirst($config['driver']).'Driver'; |
createSessionDriver()メソッドはAuthManagerに存在するので、ifブロック内の$this->createSessionDriver($name, $config)が実行される。
|
|
if (method_exists($this, $driverMethod)) { return $this->{$driverMethod}($name, $config); } |
createSessionDriver()を見ると、$guardとして\Illuminate\Auth\SessionGuardクラスのインスタンスが格納される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public function createSessionDriver($name, $config) { $provider = $this->createUserProvider($config['provider'] ?? null); $guard = new SessionGuard($name, $provider, $this->app['session.store']); // When using the remember me functionality of the authentication services we // will need to be set the encryption instance of the guard, which allows // secure, encrypted cookie values to get generated for those cookies. if (method_exists($guard, 'setCookieJar')) { $guard->setCookieJar($this->app['cookie']); } if (method_exists($guard, 'setDispatcher')) { $guard->setDispatcher($this->app['events']); } if (method_exists($guard, 'setRequest')) { $guard->setRequest($this->app->refresh('request', $guard, 'setRequest')); } return $guard; } |
SessionGuardクラスはattempt()メソッドを持つので、最初のattemptLogin()メソッドは以下を実行していることになる。
|
|
protected function attemptLogin(Request $request) { return $SessionGuardのインスタンス->attempt( $this->credentials($request), $request->filled('remember') ); } |
参考サイト