laravelのlogin処理のソースコードリーディング


login処理のソースコードリーディングをした過程を書いたのですが、ログイン周りをカスタムする必要性が出てきたら勉強すればよいと思います。

自分の場合は軽い気持ちで読んでみただけなので、カスタムする必要性が出てきてからでよかったなと思ってます。

本当にカスタムしようとすると、GuardだったりUserProviderも勉強しないといけないのでアレなんですけどね。

## 前提知識

認証を理解するにあたって知っておきたい知識を簡単に説明します。

### Guard

認証情報の操作をするものです。このクラスは`Illuminate\Contracts\Auth\Guard`を実装する必要があります。

デフォルトではセッションを利用するものが使われます。

### UserProvider

実際に認証処理を行うクラスです。idなどを利用してユーザーのデータを取得して返却します。

`Illuminate\Contracts\Aurh\UserProvider`を実装する必要があります。

デフォルトではDBからユーザー情報の取得を行い、DB接続に利用するものとしてeloquentかdatabaseの２つが用意されており、デフォルトではeloquentが利用されます。

### Authenticatable

UserProviderでは引数や返り値として`Illuminate\Contracts\Auth\Authenticatable`を実装したクラスを扱います。

デフォルトで使用される`User.php`もextends先でこれを実装しています。


## login処理

**まずはじめに注意点として、掲載するコードは今回の説明に関係ないところを省略して書いていたりします。完全なコードが知りたい場合はlaravelのコードを見てください。**


`LoginController`には`login`メソッドはなく、`AuthenticatesUsers`トレイトに実装されています。


```php
public function login(Request $request)
{
    $this->validateLogin($request);

    // If the class is using the ThrottlesLogins trait, we can automatically throttle
    // the login attempts for this application. We'll key this by the username and
    // the IP address of the client making these requests into this application.
    if (method_exists($this, 'hasTooManyLoginAttempts') &&
        $this->hasTooManyLoginAttempts($request)) {
        $this->fireLockoutEvent($request);

        return $this->sendLockoutResponse($request);
    }

    if ($this->attemptLogin($request)) {
        return $this->sendLoginResponse($request);
    }

    // If the login attempt was unsuccessful we will increment the number of attempts
    // to login and redirect the user back to the login form. Of course, when this
    // user surpasses their maximum number of attempts they will get locked out.
    $this->incrementLoginAttempts($request);

    return $this->sendFailedLoginResponse($request);
}
```

`validateLogin`は入力された認証情報(標準ではメールアドレスとパスワード)をバリデーションしています。

`attemptLogin`の箇所以外はログイン試行回数が規定回数を超えたらロックするといった機能を実現するものなので、今回は割愛します。

二個目のif文の中の`$this->attemptLogin`を見ていきます。

```php
protected function attemptLogin(Request $request)
{
    return $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );
}
```

`guard()`メソッドを見ていきます。

```php
protected function guard()
{
    return Auth::guard();
}
```

Authファサードで`guard`メソッドを呼び出しています。

Authファサードは`auth`という名前でサービスコンテナから解決をします。
`config/app.php`から`AuthServiceProvider`が登録されているのがわかるので、その中身を見ればどの具象クラスが返ってくるかがわかります。

Authファサードでは`Illuminate\Auth\AuthManager`のインスタンスを返却するので、その中の`guard`メソッドを呼び出していることになります。

なので上記のコードは以下のコードとほぼ同義になります。

```php
protected function guard()
{
    return (new AuthManager())->guard();
}
```

では`guard`メソッドの中身を見ていきましょう。

```php
public function guard($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
```

`guard`には引数を渡していないので、`$this->getDefaultDriver`が呼ばれます。

```php
public function getDefaultDriver()
{
    return $this->app['config']['auth.defaults.guard'];
}
```

ここのコードの意味は`config/auth.php`の中身の`defaults => guard`の値を取り出すといった意味です。

```php:auth.php
defaults' => [
    'guard' => 'web',
    'passwords' => 'users',
],
```

標準ではこのように記載されているので、`web`といった文字列を返却することになります。
なので`$name = "web"`となります。

次は`$this->resolve($name)`を見ていきます。

```php
protected function resolve($name)
{
    $config = $this->getConfig($name);

    $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

    if (method_exists($this, $driverMethod)) {
        return $this->{$driverMethod}($name, $config);
    }
}
```

`getConfig`は先程の`getDefaultDriver`と同じ用に`auth.php`から値を取ってきます。

`['driver' => 'session', 'provider' => 'users']`といった配列が返却されます。

`$driverMethod = createSessionDriver`となります。if文の中でこの名前のメソッドを呼び出しています。
(`$this->createSessionDriver($name, $config)`)

```php
public function createSessionDriver($name, $config)
{
    $provider = $this->createUserProvider($config['provider'] ?? null);

    $guard = new SessionGuard($name, $provider, $this->app['session.store']);

    return $guard;
}
```

`$this->createUserProvider`でUserProviderを取得し、それらを利用してSessionGuardを生成しています。

`attemptLogin`メソッド内の`$this->guard()`は`SessionGuard`クラスのインスタンスを返すことがわかりました。

いろんなところにとびすぎたので`attemptLogin`メソッドを再掲します。

```php
protected function attemptLogin(Request $request)
{
    return $this->guard()->attempt(
        $this->credentials($request), $request->filled('remember')
    );
    
    // 以下と同値
    return (new SessionGuard()->attempt(
       $this->credentials($request), $request->filled('remember')
   );
}
```

`Illuminate\Auth\SessionGuard`の`attempt`メソッドを見ていきます。

```php
public function attempt(array $credentials = [], $remember = false)
{
    $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

    if ($this->hasValidCredentials($user, $credentials)) {
        $this->login($user, $remember);

        return true;
    }
}
```

`$this->provider->retrieveByCredentials($credentials)`はメールアドレスを元にユーザー情報をDBから取得しています。

`$this->hasValidCredentials`は主にパスワードの照合をしています。パスワードが一致すれば`login`メソッドが呼ばれます。

```php
public function login(AuthenticatableContract $user, $remember = false)
{
    $this->updateSession($user->getAuthIdentifier());

    // If the user should be permanently "remembered" by the application we will
    // queue a permanent cookie that contains the encrypted copy of the user
    // identifier. We will then decrypt this later to retrieve the users.
    if ($remember) {
        $this->ensureRememberTokenIsSet($user);

        $this->queueRecallerCookie($user);
    }

    // If we have an event dispatcher instance set we will fire an event so that
    // any listeners will hook into the authentication events and run actions
    // based on the login and logout events fired from the guard instances.
    $this->fireLoginEvent($user, $remember);

    $this->setUser($user);
}
```

`login`メソッドでは、セッションにユーザーIDを格納したり、remember_tokenをDBにセットしたりしています。

そうすることでログインという状態を維持しているわけです。

`login`メソッドのあとに`true`を返却することで、一番最初の`login`メソッドに戻り、ログインイベントが発火されるわけです。

```php
if ($this->attemptLogin($request)) {
    return $this->sendLoginResponse($request);
}
```