Fippiyのプログラム学習内容アウトプットBlog

日々の学習内容をアウトプットして振り返りを実施する。

Laravel開発、ユーザー情報を編集する【3】メールアドレスを変更する

ユーザー情報編集について、残るはメールアドレスの編集です。

以前の記事でも触れましたが、単純に変更するだけであれば既に出来ています。

しかし、新規登録やパスワードリセット時にメール送付による認証をしているので、ユーザー情報編集のメールアドレス変更についても対応させていきたいです。

前回の記事はこちら。

fippiy.hatenablog.jp

今回の目的

メールアドレス変更時に新メールアドレス宛にメールを送付し、使用できるメールアドレスであることを確認した上で変更させる

なぜやるか

ユーザー新規登録・パスワードリセット時にメール送付による確認を実装しており、メールアドレス変更にも適用させるため。

やりたいこと

メールアドレス変更時にメールを送付したい

やったこと

メール送付の手順の詳細を考える

ユーザー編集コードを見直す

メールを送付する

新メールアドレスを一時保存する

トークンを発行する

メールフォーマットを作成する

同一メールは処理しない

実施内容

動作させるための手順を確認する

実装の手順についてこちらを参考にさせて頂きました、ありがとうございます。

qiita.com

まずどういう動作をさせて実装するか。具体的な手順をあげてみました。

  1. フォームにアクセスする
  2. 新メールアドレスを入力
  3. フォームボタン押下
  4. メール照合用トークン生成
  5. 新メールアドレス情報を一端保存
  6. メール送付
  7. メールURLからアクセス
  8. メールURLのトークンを照合
  9. 保存したメールアドレスを実際のDBに上書
  10. 一時保存データ削除
  11. 変更完了通知

メールアドレスが入力されると、一端仮のDBへデータを保存しておき、照合用のトークンを生成してメールを送付。

メールから照合用のURLにアクセスされると仮DBに保存しているメールアドレスをユーザーのメールアドレスとして登録させる…といった具合です。

この手順で実装してみます。

 

ユーザー編集を見直す

前回までに作成したユーザー編集コードについて見直しをする必要が出てきたため、変更します。

 

前回のフォームでは、編集アクションを1箇所で動作させていました。

同じフォーマットで同じ更新手順を実施していたので、この時は問題ありませんでした。

しかし、メールアドレスに関しては仮DBへの保存やメール送付などの処理が必要となり、そもそも動作が全く異なるので別の物として作成したほうが作りやすく、結果としてコードの可読性も良くなると考えました。

となると、全体的な見直しが必要です。 

 

ビューを見直す 

同じアクション・同じ送信メソッド・ページによる分類という方法でeditページを使い分ける方法で実装しましたが、このままメールアドレス送信を実装するとなると、下記の場所に変更が生じている状況となっています。

# ~/resources/views/user/edit.blade.php

<form action="{{ route('update.user', $auth->id)}}"
 method="post" enctype="multipart/form-data">
{{ csrf_field() }}
<input type="hidden" name="_method" value="PUT">
<input type="hidden" name="page" value="{{$page}}">
〜略〜
 
</form>

フォームの冒頭部分です、処理先が異なるのでアクションが変更になります。

他にもデータの更新をするわけではないので、PUT処理も不要です。

※この記述があるまま実装しようとしてはまってました。PUTがあることでupdate処理となっており、メールアドレス処理を開始してくれない状態となりました。

 

これだけ内容が変わると1フォームでif,if,if…は見栄えがよくないです。メール専用の新フォームを作成します。

# ~/resources/views/user/email.blade.php

<form action="{{ route('email.change')}}" method="post"
enctype="multipart/form-data">
{{ csrf_field() }}
<div class="form-contents">
<div class="form-one-size">
<div class="form-input">
<div class="form-label">メールアドレス</div>
<div><input class="form-input__input" type="text" name="email"
value="{{$auth->email}}"></div>
</div>
</div>
</div>
<div class="form-foot">
<input class="send" type="submit" value="編集">
</div>
</form>

editビューを参考にメールアドレス部分のみを新たに作成しました。

 

ビュー作成に伴うルーティング・アクション設定 

ビューファイルで設定していたアクションに対応するアクションとルーティングが必要になるので、追加します。

まずルーティングを設定します。

# ~/routes/web.php

Route::get('/user/email', 'UserController@userEmailEdit')->name('email.edit');
Route::post('/user/email', 'UserController@userEmailChange')->name('email.change');
Route::get('/user/userEmailUpdate/', 'UserController@userEmailUpdate');

メールアドレス変更用としてルートを作成しました。ビュー表示・メール送付用・認証用の3つのルートを作成しています。

 

続いて、コントローラーのアクションを設定します。

# ~/app/Http/Controllers/UserController.php

public function userEmailChange(Request $request)
{
// バリデーションチェック
$this->validate($request, User::$editEmailRules);
// 対象レコード取得
$auth = Auth::user();
// リクエストデータ受取
$form = $request->input('email');
return [$auth, $form];
}
public function userEmailUpdate(Request $request)
{
//
}

まず、動作を確認する為の処理をいれています。

バリデーションは前回作成していたので流用し、まずは入力された情報とユーザー情報を出力できるようにしました。

これで、メールアドレス変更専用のフォームとコントローラー処理ができる状態となりました。

 

メール送付による認証を実装する

ここから先程のコントローラーに設定を追記して、メール送付による認証を行い、メールアドレスを変更するという内容を実装していきます。

 

メール送付を実装する 

規登録やパスワードリセットでメール送付する機能はすでに実装していますが、元となるメール送信設定以外はAuth機能内で動作しているようです。

そこで、個別にメール送信する方法を調べました。 

こちらを参考にさせて頂きました、ありがとうございます。

readouble.com

この中からメール送付の記述を使用します。まずは変数だけ調整してそのまま実装。

# ~/app/Http/Controllers/UserController.php

$user = Auth::user();
Mail::send('index', ['user' => $user], function ($m) use ($user) {
$m->from('hello@app.com', 'Your Application');
$m->to($user->email, $user->name)->subject('Your Reminder!');
});

Mail::send()で値を引き渡すことでメールが送付できます。

メール送付の設定は以前設定したもので動作しました。

第一引数でメールフォーマットを指定しますが、新規登録等で使用していたメールフォーマットではうまく動作しませんでした。

 

フォーマット上に設定されている変数がこの状態だと動作していなかったようです。

まず送信することを目指すため、メールフォーマットは後回しとし、仮フォーマットとしてindexページを指定しています。

この状態でメールが送付され、受け取ることはできました。

 

メールアドレス変更情報を保存する

この状態で使用できる変数としては$userです。現状のユーザーの情報を引き渡し、そこからメールアドレスを参照してメールを送付しています。

ですが、このままではAuth::user()のメールアドレス…つまり現在登録中のメールアドレス宛に送付しています。

今回作成するのは、メールアドレス変更時の認証となるので、新しいメールアドレス宛に送付する必要があります。

 

検討した作成手順では、新メールアドレスを一端保存してメール送付…という流れです。

一時保存DBを新規作成して、新メールアドレスを保存した情報を引き渡してやれば、必要な情報をメールに追加して使えそうです。

 

というわけで、データを保存する新たなテーブルを作成します。

コマンドでchangeEmailテーブルを作成します。

php artisan make:migration create_changeEmail_table

 

# ~/database/migrations/xxxx_create_change_email_table.php

class CreateChangeEmailTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('change_email', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->string('new_email');
$table->text('update_token');
$table->timestamps();
});
}

新しいメールアドレスと照合用のトークンを保存するカラムを設定しました。

マイグレーションファイルを実装してテーブルを作成しておきます。

 

コントローラー上でデータを保存する設定を追加します。

# ~/app/Http/Controllers/UserController.php

public function userEmailChange(Request $request)
{
// バリデーションチェック
$this->validate($request, User::$editEmailRules);
// 対象レコード取得
$auth = Auth::user();
// リクエストデータ受取
$new_email = $request->input('email');
// メール照合用トークン生成
$update_token = hash_hmac(
'sha256',
str_random(40).$new_email,
env('APP_KEY')
);
// 変更データ一時保存DBへレコード保存
$change_email = new ChangeEmail;
$change_email->user_id = $auth->id;
$change_email->new_email = $new_email;
$change_email->update_token = $update_token;
$change_email->save();
// メール送付
Mail::send('index', ['change_email' => $change_email],
 function ($message) use ($change_email) {
$message->from('hello@app.com', 'Your Application');
$message->to($change_email->new_email)->subject('Your Reminder!');
});
return redirect('user');
}

細々した設定がまだ入っていませんが、ベースとなるものを作成しました。

新しいDBにデータ保存し、メールを送付しています。

$update_tokenで照合用の文字列を作成しています。この情報を送付し、DB登録トークンと一致していることを確認することで、認証する形とします。

これで、トークン発行して、新メールアドレスと共にDBに保存するところまで完了です。

 

メールフォーマットが適当なので、送付メール内には照合先のURLは表示されない状態となっています。

 

トークン認証を実装する

次に認証機能を実装します。

認証の流れとしては

  • メールURLからアクセス
  • メールURLのトークンを照合
  • 保存したメールアドレスを実際のDBに上書
  • 一時保存データ削除
  • 変更完了通知

こういった流れで考えていました。

メールにURLはまだ記載されていない状態ですが、照合する手順を先に実装します。

# ~/app/Http/Controllers/UserController.php

public function userEmailUpdate(Request $request)
{
// メールからのアクセス
// トークン受け取り
$token = $request->input('token');
// トークン照合
$email_change = DB::table('change_email')
->where('update_token', '=', $token)
->first();
// 照合一致で一時保存DBのメールアドレスをDBメールアドレスに上書
$user = User::find($email_change->user_id);
$user->email = $email_change->new_email;
$user->save();
// 一時保存DBレコード削除
DB::table('change_email')
->where('update_token', '=', $token)
->delete();
// 変更完了通知
// (----あとで作成----)
// リダイレクト
return redirect('user');
}

URLはコメントアウトされている形式で作成する予定です。

今回は直接URLを指定することで、動作確認しました。

 

アクセス後は、URLからトークンを取り出して一時保存DBと照合しています。

照合OKであれば、メールアドレスをユーザーDBへ上書し、一時保存DBの内容を消去するという形にしました。

以上で、メールアドレス変更の骨組みは完成です。

 

メールフォーマットを作る 

あとは、メールのフォーマットを用意して、変更用URLが引き渡せると、一連の動作を完結させることができそうです。

# ~/resources/views/email/change_email.blade.php

book-property-management<br><br>
新メールアドレス確認<br><br>
新しいメールアドレスに変更します。<br>
以下のURLをクリックして認証してください。<br>
{{$url}}

URLを引き渡した簡単なメールフォームを作成しました。

 

# ~/app/Http/Controllers/UserController.php

// メール送付
// !!!!一時保存DBのデータを引き渡してメールをおくる
$domain =env('APP_URL');
Mail::send('email/change_email', ['url' =>
 "{$domain}/user/userEmailUpdate/?token={$update_token}"],
 function ($message) use ($change_email) {
$message->to($change_email->new_email)->subject('メールアドレス確認');
});
return redirect('user');
}

フォーマットを適用しました。

 

後は実際にメールアドレス変更をして、送付メールを確認します。

# メール

f:id:Fippiy:20190422181248p:plain

新メールアドレス確認メール

フォーマットを元にして新しいメールアドレス確認用のURLを送付できるようになりました。

あとは、このアドレスをクリックすることで登録完了です。登録処理は先に完成させていたので、これで登録が完了します。

 

これで新メールアドレス宛てにメールを送付してアドレス確認後に登録する処理が完了しました。

 

その他細かな調整を実施する

同じメールアドレスの確認処理

今のままでは、同じメールアドレスに変更した…としても、変更処理されてしまします。

そもそも同じメールアドレスであれば処理する必要はありません。

同一メールアドレスはエラーとします。

# ~/app/Http/Controllers/UserController.php

public function userEmailChange(Request $request)
{
〜略〜
// 変更前後でメールアドレスが同じか確認
$email_check = true;
if ($auth->email == $new_email) {
$email_check = false;
}
// メールアドレスが変更されていない時にエラーとして処理をかえす
$validator = Validator::make(['email' => $email_check],
 ['email' => 'accepted'], ['メールアドレスが変更されていません']);
if ($validator->fails()) {
return redirect('user/email')
->withErrors($validator)
->withInput();
}

userテーブル情報とフォーム送信されたメールアドレスを照合し、同じであればバリデーターを使用してエラーメッセージを返すことにしました。

 

 

以上でユーザー情報編集については完了です。

細々した問題はまだ残っていますが、最低限の編集機能としては問題ないでしょう。

次は、ユーザー登録と編集が完了したので、削除機能を実装します。