【Laravel7でユーザー認証_8】メールアドレス変更時にメール認証を行う

#はじめに
Laravelのユーザー認証機能を利用する際、会員がメールアドレスを変更したら、メール認証を実行する手順をまとめます。

# 環境

XAMPP環境でLaravelが使えるように設定してあります。

- Windows10 Pro 64bit
- PHP 7.3.18
- Laravel 7.12.0
- MariaDB 10.1.32

また、Laravelプロジェクトは以下の手順で作業を進めており、会員登録時にメール認証を行う段階までできている状態です。

- [【Laravel7でユーザー認証_1】基本のき](https://qiita.com/crosawassant/items/bf3982666b6333bcb8c8)
- [【Laravel7でユーザー認証_2】ユーザー認証を日本語化](https://qiita.com/crosawassant/items/d1f4c5c93f30afa9eb65)
- [【Laravel7でユーザー認証_3】ユーザー認証をメールアドレスからユーザー名に変更する](https://qiita.com/crosawassant/items/2464db8a0fe1c603401a)
- [【Laravel7でユーザー認証_4】パスワード変更フォームを作成する](https://qiita.com/crosawassant/items/04242c8d7a12346dcc20)
- [【Laravel7でユーザー認証_5】ユーザーを倫理削除（SoftDelete）する](https://qiita.com/crosawassant/items/a6e3e9b1aedf566b2d27)
- [【Laravel7でユーザー認証_6】ユーザーの情報を表示・変更する設定画面を作成する](https://qiita.com/crosawassant/items/d8b434f0bc98455165b4)
- [【Laravel7でユーザー認証_7】会員登録時にメール認証を行う](https://qiita.com/crosawassant/items/eed947b1e516d864fc6c)

# 実装手順

##メールアドレス変更時、メール認証をリセットする

メールアドレスを変更する処理の中に、メール認証をリセットする処理（`email_verified_at` をNULLにする）を追加します。

```diff:app/Http/Controllers/SettingController.php
      public function changeEmail(ChangeEmailRequest $request)
      {
        //ValidationはChangeUsernameRequestで処理
        //メールアドレス変更処理
        $user = Auth::user();
        $user->email = $request->get('email');
+       $user->email_verified_at = null;
        $user->save();

        //homeにリダイレクト
        return redirect()->route('setting')->with('status', __('Your email address has been changed.'));
      }
  }
```

##認証メールを送信する

メール認証のリセット処理の後、引き続き認証用のメールを送信します。
ユーザー登録時の処理をなぞってみると、認証用のメールは`Users` クラスの `sendEmailVerificationNotification` メソッドで送れるようです。

`notify` を使って `$user->notify(new VerifyEmail());` という書き方もできそうです。
その場合は `use Illuminate\Auth\Notifications\VerifyEmail;` を忘れずに。

処理完了のメッセージも変更しておきます。

```diff:app/Http/Controllers/SettingController.php
      public function changeEmail(ChangeEmailRequest $request)
      {
        //ValidationはChangeUsernameRequestで処理
        //メールアドレス変更処理
        $user = Auth::user();
        $user->email = $request->get('email');
        $user->email_verified_at = null;
        $user->save();
+       $user->sendEmailVerificationNotification();

        //homeにリダイレクト
-       return redirect()->route('setting')->with('status', __('Your email address has been changed.'));
+       return redirect()->route('setting')->with('status', __('A confirmation email for changing the email address has been sent.'));
      }
  }
```

##同じメールアドレスの場合、何も処理せずに完了表示とする

メールアドレスの変更処理を行った際、同じメールアドレスであれば特に処理せずに「完了しました」と表示させたいと思います。
（エラー表示にしたい場合は、`FormRequest` で `withValidator` を定義するとよさそうです。）

```diff:app/Http/Controllers/SettingController.php
      public function changeEmail(ChangeEmailRequest $request)
      {
        //ValidationはChangeUsernameRequestで処理
        //メールアドレス変更処理
        $user = Auth::user();
+ 
+       if ($user->email == $request->get('email')){
+           return redirect()->route('setting')->with('status', __('Your email address has been changed.'));
+       }
+
        $user->email = $request->get('email');
        $user->email_verified_at = null;
        $user->save();
        $user->sendEmailVerificationNotification();

        //homeにリダイレクト
        return redirect()->route('setting')->with('status', __('A confirmation email for changing the email address has been sent.'));
      }
  }
```

##日本語対応

```diff:resources/lang/ja.json
+     "A confirmation email for changing the email address has been sent.": "メールアドレス変更の確認メールを送信しました。"
  }
```

##未認証の場合もメールアドレス変更フォームを使えるよう変更

認証が通っていないユーザーも、メールアドレス変更のフォーム表示 `showChangeEmailForm` と処理`changeEmail` ができるように、`__construct` の定義を変更します。
（ここで表示部分だけ許可したがために、ハマりました……。）

`->except` で複数指定したいので、配列にして渡します。

```diff:app/Http/Controllers/SettingController.php
      public function __construct()
      {
          $this->middleware('auth');
-         $this->middleware('verified')->except('index');
+         $this->middleware('verified')->except(['index', 'showChangeEmailForm', 'changeEmail']);
      }
```

viewについても、リンクが切れないように修正します。

```diff:resources/views/setting/index.blade.php
                        <a href="{{ route('email.form') }}"
-                       @if ($auth->email_verified_at)
                          class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
-                       @else
-                          class="list-group-item list-group-item-action d-flex justify-content-between align-items-center disabled" tabindex="-1" aria-disabled="true"
-                       @endif
                        >
                          <dl class="mb-0">
                            <dt>{{ __('E-Mail Address') }}</dt>
                            <dd class="mb-0">{{ $auth->email }}</dd>
                          </dl>
-                         @if ($auth->email_verified_at)
                          <div><i class="fas fa-chevron-right"></i></div>
-                       @endif
                        </a>
```

##動作確認

▼メールアドレスを変更した直後の表示。認証ができていなくても、アカウント削除とメールアドレス変更は利用可能。
![Laravel-MustVerifyEmail-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262236/68556edd-0b61-6c07-867d-fc2446adac1f.png)

▼認証済ユーザーが、同じメールアドレスで変更処理した場合、特に処理せず完了メッセージ表示。
![Laravel-MustVerifyEmail-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262236/24ce7875-d56c-e77f-f7ed-64d402ad1ee9.png)

#参考サイト

- [Laravel5.7のEmail Verificationを読む - Laravelコードリーディング - Qiita](https://qiita.com/yamaji_daisuke/items/731868a4de6037794976)
- [通知 7.x Laravel](https://readouble.com/laravel/7.x/ja/notifications.html)