【Laravel】メールアドレスを変更する方法を徹底解説【コピペでOK】

技術メモ

今回は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にあげているので、興味のある方は参考にしてみてください。

 

nosuke0926/EmailChangeSample
Laravelでのメールアドレス変更機能のサンプル. Contribute to nosuke0926/EmailChangeSample development by creating an account on GitHub.

 

今回はLaravelにてメールアドレスを変更する方法について解説しました。

他にも解説して欲しい内容があれば、是非ともコメント欄やお問い合わせからメッセージをください!

それでは!

 

技術メモ
スポンサーリンク
スポンサーリンク
のすけをフォローする
やばブロ!

コメント