Laravelの標準の認証機能のパスワードリセットに独自の処理を差し込むやり方

Laravelには標準で認証機能がついています。
標準の認証機能を利用した場合でのパスワードリセットのときに、独自の処理を差し込む必要があり、
ハマったので共有します。
独自の処理というのは具体的にはpassword_resetsに管理用の列を追加してパスワードリセット申請時にその列に値を入れるという処理です。

最初はconfig/auth.phpの設定ファイルをちょろっと変えるだけでいけるかと思っていましたが、
Laravelが提供する認証機能のパスワードリセット関連のクラスを継承して、必要なメソッドだけを子クラスでオーバーライドしてそこに独自の処理を差し込む、という形になります。

## 環境

|名前|バージョン|
|---| -------- |
|PHP | 7.1.8    |
|Laravel| 5.5.5 |

## やり方

config/app.phpのPasswordServiceProviderを独自のものに置き換えます。

```config/app.php
    'providers' => [
        //Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,  // ★コメントにします
        App\Auth\Passwords\MyPasswordResetServiceProvider::class,         // ★追加
    ],
```

config/app.phpに追加した独自のサービスプロバイダを定義します。

```App\Auth\Passwords\MyPasswordResetServiceProvider.php
<?php

namespace App\Auth\Passwords;

use Illuminate\Auth\Passwords\PasswordResetServiceProvider;
use App\Auth\Passwords\MyPasswordBrokerManager;

/**
 * パスワードリセットのサービスプロバイダ
 * @see \Illuminate\Auth\Passwords\PasswordResetServiceProvider
 */
class MyPasswordResetServiceProvider extends PasswordResetServiceProvider    // ★標準のPasswordResetServiceProviderを継承する
{
    /**
     * PasswordBrokerをサービスコンテナに登録
     * Laravelのものではなく独自のクラスを利用する
     *
     * @return void
     */
    protected function registerPasswordBroker()
    {
        $this->app->singleton('auth.password', function ($app) {
            return new MyPasswordBrokerManager($app);    // ★独自のパスワードブローカーマネージャを指定する
        });

        $this->app->bind('auth.password.broker', function ($app) {
            return $app->make('auth.password')->broker();
        });
    }
}
```

Auth標準のPasswordResetServiceProviderを継承して、`registerPasswordBroker` をオーバーライドし
`auth.password`で独自のパスワードブローカーマネージャを指定します。

```App\Auth\Passwords\MyPasswordBrokerManager.php
<?php

namespace App\Auth\Passwords;

use Illuminate\Auth\Passwords\PasswordBrokerManager;
use Illuminate\Support\Str;
use App\Auth\Passwords\MyDatabaseTokenRepository;

/**
 * PasswordBrokerマネージャ
 * @see \Illuminate\Auth\Passwords\PasswordBrokerManager
 */
class MyPasswordBrokerManager extends PasswordBrokerManager    // ★標準のPasswordBrokerManagerを継承する
{
    /**
     * Create a token repository instance based on the given configuration.
     *
     * @param  array  $config
     * @return \Illuminate\Auth\Passwords\TokenRepositoryInterface
     */
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];

        if (Str::startsWith($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }

        $connection = $config['connection'] ?? null;

        return new MyDatabaseTokenRepository(            // ★独自のDatabaseTokenRepositoryを指定します。
            $this->app['db']->connection($connection),
            $this->app['hash'],
            $config['table'],
            $key,
            $config['expire']
        );
    }
}
```

Auth標準のPasswordBrokerManagerを継承して、`createTokenRepository` をオーバーライドし
独自のDatabaseTokenRepositoryを指定します。

```App\Auth\Passwords\MyDatabaseTokenRepository.php
<?php

namespace App\Auth\Passwords;

use Illuminate\Auth\Passwords\DatabaseTokenRepository;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Support\Carbon;

/**
 * 独自DatabaseTokenRepository
 * @see \Illuminate\Auth\Passwords\DatabaseTokenRepository
 */
class MyDatabaseTokenRepository extends DatabaseTokenRepository      // ★標準のDatabaseTokenRepositoryを継承する
{
    /**
     * Create a new token record.
     *
     * @param  \Illuminate\Contracts\Auth\CanResetPassword  $user
     * @return string
     */
    public function create(CanResetPasswordContract $user)
    {
        $email = $user->getEmailForPasswordReset();

        $this->deleteExisting($user);

        // We will create a new, random token for the user so that we can e-mail them
        // a safe link to the password reset form. Then we will insert a record in
        // the database so that we can verify the token within the actual reset.
        $token = $this->createNewToken();

        // ★独自の管理用の列の値を取得（※実際はもっと複雑な取得をしていますが例のため簡単にしています）
        $dummy = 'dummy.value';
        // ★独自の管理用の列dummyを更新するためにgetPayloadではなく独自のメソッドgetMyPayloadを呼び出す
        $this->getTable()->insert($this->getMyPayload($email, $token, $dummy));

        return $token;
    }

    /**
     * Build the record payload for the table.
     * ★管理用の列更新のためgetPayloadをコピーして作成
     * 引数に$dummyが追加され、配列に$dummyをセットするだけ
     *
     * @param  string  $email
     * @param  string  $token
     * @param  string  $dummy
     * @return array
     */
    protected function getMyPayload($email, $token, $dummy)
    {
        // ★dummyを追加
        return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon, 'dummy' => $dummy];
    }
}
```

これで、パスワードリセットの申請をしたときに、
password_resetsテーブルに追加した管理用の列も更新されるようになりました。

このように標準のクラスを継承して必要なメソッドをオーバーライドしてカスタムすることで、
独自の処理を入れることができます。
今回は管理用の列に値を入れるケースでしたが、他のケースでも同様に必要なメソッドをオーバーライドすれば
カスタムできるかと思います。

## 参考

https://github.com/laravel/framework/issues/15071