今回は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 --devphp artisan ui vue --authnpm 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にてメールアドレスを変更する方法について解説しました。
他にも解説して欲しい内容があれば、是非ともコメント欄やお問い合わせからメッセージをください!
それでは!





コメント