ログイン認証の例
たとえばログイン認証の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()
メソッドは、同じトレイトで定義されている。
1 2 3 4 5 6 |
protected function attemptLogin(Request $request) { return $this->guard()->attempt( $this->credentials($request), $request->filled('remember') ); } |
ここでguard()
というメソッドが出てくる。これは同じAuthenticatesUsers
トレイトの最後の方で以下のように定義されているが、そもそもどういうものかよくわからなかったので掘り下げてみたら、どんどん深みにはまってしまった。
1 2 3 4 5 6 7 8 9 |
/** * 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
定義されている。
1 2 3 4 5 6 7 8 9 10 11 12 |
<?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()
メソッドが見当たらない。
1 2 3 4 5 6 7 8 |
/** * @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()
メソッドは定義されていない。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?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
のインスタンスが返されるが、その道筋を追ってみた。最後の部分だけがまだ理解できておらず、参考サイトの受け売りだが。
1 |
$instance = static::getFacadeRoot() |
このメソッドは同じFacade
クラスで定義されていて、getFacadeAccessor()
を引数に与えたresolveFacadeInstance()
の戻り値を返している。
1 2 3 4 5 6 7 8 9 |
/** * 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()
の定義は、単に「メソッドが定義されていない」という例外を返す。
1 2 3 4 5 6 7 8 9 10 11 |
/** * 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'
を返す。
1 2 3 4 5 6 7 8 9 |
/** * 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
サービスのインスタンスが起動させる下記のコードと同じことを表す
1app()->make('auth')
app()->make(‘auth’)
で作成されるサービスはIlluminate\Auth\AuthServiceProvider
のregister
メソッドによって登録されており、Immulminate\Auth\AuthMangaer
がインスタンス化されている
1234567891011121314151617181920212223242526272829class 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()
は以下のように戻り値を返す。
1 |
return $instance->$method(...$args); |
__callStatic()
が呼ばれたときの$method
がguard()
、引数は空だったので、戻り値のメソッドはAuthManager::guard()
となる。
guard()はSessionGuard()になる
AuthManager::guard()
結局、attemptLogin()->guard()
はAuthManager::guard()
となり、その内容は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 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()
となる。その内容は以下の通り。
1 2 3 4 |
public function getDefaultDriver() { return $this->app['config']['auth.defaults.guard']; } |
このメソッドは、config
ディレクトリーにあるauth.phpのdefaults
のキー'guard'
に対する値を返す。デフォルトでその内容は'web'
となっている。
1 2 3 4 |
'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
としてセットされる。
1 2 3 4 |
protected function getConfig($name) { return $this->app['config']["auth.guards.{$name}"]; } |
auth.php
の該当する部分は以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 |
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ], |
そして、$driverMethod
に文字列連結で生成された文字列'createSessionDriver'
が格納される。
1 |
$driverMethod = 'create'.ucfirst($config['driver']).'Driver'; |
createSessionDriver()
メソッドはAuthManager
に存在するので、if
ブロック内の$this->createSessionDriver($name, $config)
が実行される。
1 2 3 |
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()
メソッドは以下を実行していることになる。
1 2 3 4 5 6 |
protected function attemptLogin(Request $request) { return $SessionGuardのインスタンス->attempt( $this->credentials($request), $request->filled('remember') ); } |
参考サイト
- 【Laravel】ファサードの仕組み – 実装を読んで理解する
- 【PHP】マジックメソッド – __callStatic()等の使い方を解説します
- Laravel ファサード(Facade)を理解する