今回はLaravelでメールアドレスを変更する方法について、わかりやすく説明していきます。
また、なるべくコピペで実装できるようにコードをまるごと掲載していきます!
今回実装するメールアドレス変更機能の流れは以下の通りです。
①すでにログインしているユーザーが新しいメールアドレスを入力
②入力したメールアドレスあてに確認メールを送信する
③メールのリンクをクリックするとメールアドレスの変更が完了する
※新しいメールアドレスを認証するまでは、以前のメールアドレスでログインさせる
LaravelにはEmail Verificationというメールアドレスの確認機能が実装されていますが、Email Verificationを使用すると新しいメールアドレスを入力した際に古いメールアドレスは上書きされてしまいます。
今回実装したい機能のように、新しいメールアドレスを認証するまでは以前のメールアドレスでログインさせる場合はEmail Verificationは使えないので自前で実装していきます。
それではいきましょう!
開発環境
- MacBook Pro (macOS Catalina バージョン10.15.2)
- PHP 7.3.9
- Laravel Framework 6.13.1
Composerはすでにインストールしているとします。
メールアドレス変更の前準備
すでにプロジェクトを作成している場合は省略してください。
新規プロジェクト作成
まずは新規プロジェクトを作成します。
ターミナル
composer create-project --prefer-dist laravel/laravel EmailChangeSample
プロジェクトのカレントディレクトリに移動して、ローカル開発サーバを立ち上げます。
ターミナル
php artisan serve
http://127.0.0.1:8000
にアクセスします。
ページが正常に表示されていればOKです。
認証機能作成
コマンドで簡単に認証機能を作成します。
Laravel 6.0以降は認証機能実装手順が変更されているので注意してください。
- Laravel 5.8以前
ターミナル
php artisan make:auth
- Laravel 6.0以降
ターミナル
composer require laravel/ui --dev
php artisan ui vue --auth
npm install && npm run dev
認証機能が作成されると右上にLOGIN と REGISTERが追加されます。
マイグレーションしてテーブル作成&テストデータ挿入
シーダーファイルを作成し、以下のように修正します。
ターミナル
php artisan make:seeder UsersTableSeeder
UsersTableSeeder.php
<?php use Illuminate\Database\Seeder; class UsersTableSeeder extends Seeder { public function run() { DB::table('users')->insert([ [ 'name' => '田中太郎', 'email' => 'tanaka@example.com', 'password' => Hash::make('password'), ], ]); } }
DatabaseSeeder.phpを以下のように修正します。
<?php use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { $this->call(UsersTableSeeder::class); } }
テーブルを作成して、データを挿入します。
ターミナル
php artisan migrate:fresh --seed
DBの状態はこんな感じです。
LOGINからメールアドレスとパスワードを入力すると無事ログインできました。
これで準備は完了です。
メールの受信テストの準備
メールを送信するためメールサーバーが必要ですが、今回はテスト送信で済ませるので、MailHogを使用します。
MailHogはメールのテストツールです。
MailHogをインストール&起動します。
ターミナルで
brew install mailhog
brew services start mailhog
Laravel側は.envの設定を以下のように修正します。
MAIL_DRIVER=smtp # MAIL_HOST=smtp.mailtrap.io MAIL_HOST=localhost # MAIL_PORT=2525 MAIL_PORT=1025 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=sender@example.com MAIL_FROM_NAME="${APP_NAME}"
.envの設定を変更したのでキャッシュをクリアしておきます。
ターミナル
php artisan config:cache
以下のURLにアクセスしてMailHogの画面が表示されれば成功です。
http://localhost:8025/
メールアドレス変更機能を実装
メールアドレス変更機能は以下のフローで実装します。
①新しいメールアドレスと発行したトークンを保存するテーブル(email_resets_table)を作成
②ユーザーがフォームから新規メールアドレスを送信したらトークンを発行。email_resets_tableに新規メールアドレスと発行したトークンを格納する
③ユーザーにメールアドレス確認メールを送信する
④ユーザーがメールアドレスのリンクをクリックしたら、トークンが一致するか確認
⑤トークンが一致していた場合、ユーザーのメールアドレスを更新
それではひとつひとつ実装していきます。
メールアドレスリセット用テーブルを作成
まず新しいメールアドレスと新規で発行したトークンを保存するためのテーブルを作成します。
ターミナル
php artisan make:migration create_email_resets_table
また、作成したcreate_email_resets_table.phpを以下のように修正します。
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateEmailResetsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('email_resets', function (Blueprint $table) { $table->bigIncrements('id'); $table->integer('user_id')->comment('メールアドレスを更新したユーザーID'); $table->string('new_email')->comment('ユーザーが新規に設定したメールアドレス'); $table->string('token'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('email_resets'); } }
マイグレーションを実行します。
ターミナル
php artisan migrate:fresh --seed
DB内にemail_resetsテーブルが作成されています。
同様にモデルも作成します。
ターミナル
php artisan make:model EmailReset
作成したEmailReset.phpを以下のように修正します。
<?php namespace App; use Illuminate\Database\Eloquent\Model; class EmailReset extends Model { protected $fillable = [ 'user_id', 'new_email', 'token', ]; }
メールアドレス変更用のフォームも作成しておきます。
home.blade.phpにフォームを追加します。
@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <!-- フラッシュメッセージ --> @if (session('flash_message')) <div class="flash_message alert-success text-center py-3 my-2"> {{ session('flash_message') }} </div> @endif <div class="card"> <div class="card-header">メールアドレス変更</div> <div class="card-body"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif You are logged in! </div> <div class="card-body"> 新規メールアドレスを入力してください <form action="/email" method="POST"> {{ csrf_field() }} <input type="email" name="new_email"> <input type="submit"> </form> </div> </div> </div> </div> </div> @endsection
メールアドレスリセット用テーブルにメールアドレスとトークンを保存する処理を記載
web.phpにルーティングを追加します。
<?php Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); // メールアドレス確認メールを送信 Route::post('/email', 'ChangeEmailController@sendChangeEmailLink');
ChangeEmailControllerを作成します。
ターミナル
php artisan make:controller ChangeEmailController
作成したコントローラー内にsendChangeEmailLinkメソッドを作成します。
このメソッドは新規メールアドレスと発行したトークンをDBに保存して、メールアドレス確認リンクを送信します。
public function sendChangeEmailLink(Request $request) { $new_email = $request->new_email; // トークン生成 $token = hash_hmac( 'sha256', Str::random(40) . $new_email, config('app.key') ); // トークンをDBに保存 DB::beginTransaction(); try { $param = []; $param['user_id'] = Auth::id(); $param['new_email'] = $new_email; $param['token'] = $token; $email_reset = EmailReset::create($param); DB::commit(); $email_reset->sendEmailResetNotification($token); return redirect('/home')->with('flash_message', '確認メールを送信しました。'); } catch (\Exception $e) { DB::rollback(); return redirect('/home')->with('flash_message', 'メール更新に失敗しました。'); } }
ここではhash_hmac関数を使用してハッシュ値を生成して、これをトークンとしています。
sendEmailResetNotificationメソッドは後ほど作成します。
ユーザーに確認メールを送信する
EmailReset.phpにsendEmailResetNotificationとrouteNotificationForMailメソッドを定義します。
<?php namespace App; use App\Notifications\ChangeEmail; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; class EmailReset extends Model { use Notifiable; protected $fillable = [ 'user_id', 'new_email', 'token', ]; /** * メールアドレス確定メールを送信 * * @param [type] $token * */ public function sendEmailResetNotification($token) { $this->notify(new ChangeEmail($token)); } /** * 新しいメールアドレスあてにメールを送信する * * @param \Illuminate\Notifications\Notification $notification * @return string */ public function routeNotificationForMail($notification) { return $this->new_email; } }
通知(notification)を作成します。
ターミナル
php artisan make:notification ChangeEmail
上記のコマンドを実行すると、appディレクトリ直下にNotificationsディレクトリが作成されます。
Notificationsディレクトリ直下のChangeEmail.phpを以下のように編集します。
<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; class ChangeEmail extends Notification { use Queueable; public $token; public function __construct($token) { $this->token = $token; } public function via($notifiable) { return ['mail']; } public function toMail($notifiable) { return (new MailMessage) ->subject('メールアドレス変更') // 件名 ->view('emails.changeEmail') // メールテンプレートの指定 ->action( 'メールアドレス変更', url('reset', $this->token) //アクセスするURL ); } public function toArray($notifiable) { return [ // ]; } }
次にメールの文言を記載するbladeを作成します。
resources/viewsディレクトリ直下にemailsディレクトリを作成して、changeEmail.blade.phpを作成します。
changeEmail.blade.phpを以下のように修正します。
<a href="{{ config('app.url') }}">{{ config('app.name') }}</a> <p> {{ __('下記のURLをクリックして新しいメールアドレスを確定してください。') }}<br> </p> <p> {{ $actionText }}: <a href="{{ $actionUrl }}">{{ $actionUrl }}</a> </p> <p> {{ __('※URLの有効期限は一時間以内です。有効期限が切れた場合は、お手数ですがもう一度最初からお手続きを行ってください。') }}<br> </p>
これでメール送信の準備が整ったので、メールを送信してみます。
フォームに test@gmail.comと入力して送信します。
http://localhost:8025/
にアクセスして更新ボタンを押すと、test@gmail.comあてのメールを受信できていることがわかります。
email_resetsテーブルを確認すると、新しいメールアドレスと発行したトークンが格納されていることがわかります。
これでメール送信は完了しました!
受信したメール内のリンクをクリックしてメールアドレスを更新する
最後にユーザーが受信したメール内のリンクをクリックしたら、メールアドレスを更新する処理を記載します。
web.phpにルーティングを追記します。
// 新規メールアドレスに更新 Route::get("reset/{token}", "ChangeEmailController@reset");
ChangeEmailControllerにresetメソッドと、tokenExpiredメソッドを作成します。
/** * メールアドレスの再設定処理 * * @param Request $request * @param [type] $token */ public function reset(Request $request, $token) { $email_resets = DB::table('email_resets') ->where('token', $token) ->first(); // トークンが存在している、かつ、有効期限が切れていないかチェック if ($email_resets && !$this->tokenExpired($email_resets->created_at)) { // ユーザーのメールアドレスを更新 $user = User::find($email_resets->user_id); $user->email = $email_resets->new_email; $user->save(); // レコードを削除 DB::table('email_resets') ->where('token', $token) ->delete(); return redirect('/home')->with('flash_message', 'メールアドレスを更新しました!'); } else { // レコードが存在していた場合削除 if ($email_resets) { DB::table('email_resets') ->where('token', $token) ->delete(); } return redirect('/home')->with('flash_message', 'メールアドレスの更新に失敗しました。'); } } /** * トークンが有効期限切れかどうかチェック * * @param string $createdAt * @return bool */ protected function tokenExpired($createdAt) { // トークンの有効期限は60分に設定 $expires = 60 * 60; return Carbon::parse($createdAt)->addSeconds($expires)->isPast(); }
まず最初にemail_resetsテーブルの中で、トークンが一致しているレコードを取り出します。
その後、レコードが取り出せているか、トークンの有効期限が切れていないかをチェックしています(今回は有効期限を60分に設定)。
問題がなければユーザーのメールアドレスを更新して、email_resetsテーブルのレコードを削除します。
これでメールアドレス更新機能の完成です!
実際にメールアドレスを更新してみる
実装したメールアドレス更新機能を使って、実際にメールアドレスを更新してみます。
最初のメールアドレスはtanaka@example.comです。
これをtest@gmail.comに変更したいと思います。
フォームにtest@gmail.comと入力して、送信ボタンをクリックします。
この時点ではまだメールアドレスは更新されていません。
http://localhost:8025/
にアクセスして、受信メールのリンクをクリックします。
リンクをクリックすると、メールアドレスが更新されます。
usersテーブルを確認すると、メールアドレスがtest@gmail.comに更新されているのが確認できます。
おつかれさまでした!
今回作成したサンプルはGitHubにあげているので、興味のある方は参考にしてみてください。
今回はLaravelにてメールアドレスを変更する方法について解説しました。
他にも解説して欲しい内容があれば、是非ともコメント欄やお問い合わせからメッセージをください!
それでは!
コメント