そこまで難しくないだろーとか言ってやってみたら意外とあれ?ってなってたので備忘録として書いておきますw
passwordの方が簡単なのでpasswordからいきます〜。
大まかな流れ
ルーティング設定
コントローラー設定
ビュー設定
emailの編集に必要なマイグレーション ファイルを作成
ユーザーの新規email入力部分
確認メールの送信
確認メールをクリックした時の処理
上記が正しければ、emailを更新する
というような流れで実装していきます。
目次
- ルーティング設定
- ビュー設定
- コントローラー設定
- emailの変更
- マイグレーション ファイルを作成
- 新規email入力部分
- 確認メールの送信
- app/notifications/ChangeEmail
- 確認メールをクリックした時の処理(emailの更新)
- 学んだ事
すべて表示
ルーティング設定
Route::get('/user/{user}/passwordchange_show', 'UserController@passwordchange_show')->name('passwordchange_before');
Route::post('/user/{user}/passwordchange', 'UserController@passwordchange')->name('passwordchange_after');
ビュー設定
現在のパスワード、新しいパスワード、新しいパスワードの確認を入力する部分を作ります。
<form method="POST" action="{{ route('passwordchange_after', ['user' => $user->id]) }}">
@csrf
<input id="password" type="password" class="form-control @error('currentpassword') is-invalid @enderror" name="currentpassword" autocomplete="new-password">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" autocomplete="new-password">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" autocomplete="new-password">
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('登録する') }}
</button>
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
</div>
</div>
</form>
コントローラー設定
use App\Http\Requests\ChangePassword;
public function passwordchange_show(User $user)
{
$check_user = Auth::user();
#1 if($check_user->id == $user->id) {
return view('users.passwordchange')->with('user', $user);
} else {
return back();
}
}
public function passwordchange(ChangePassword $request, User $user)
{
$new_password = $request->password;
$old_password = $request->currentpassword;
#2 if(!(Hash::check($old_password, $user->password))) {
return redirect()->back()->with('say', '現在のパスワードが間違っています。');
} else {
#3 if(Hash::check($new_password, $user->password)) {
return redirect()->back()->with('say', '新しいパスワードが、現在のパスワードと同じです。違うパスワードを設定してください。');
} else {
$user->password = Hash::make($request['password']);
$user->save();
return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'パスワードが正しく変更されました']);
}
}
}
#1, if文がtrueであればビューページに飛んで行けるようにします。
#2, 現在のパスワードが入力されたものと一致しているかをチェック。
#3, 新しいパスワードと確認パスワードが一致するかチェック。
以上がうまくいけばhashを使って新しいパスワードを作成し、更新していきます。
フォームリクエストも適宜用意した方が良いかなと思います。
次はemailです。
emailの変更
emailもpasswordと同じように変更できれば良いんですけど、正しいemail(確認メール)として判定してから変更しないと不正なemailが登録されてしまう可能性があります。
そこに対応するためにテーブルやtokenを使っていきます。
Route::get('/user/{user}/emailchange_show', 'UserController@emailchange_show')->name('emailchange_before');
Route::post('/user/{user}/emailchange', 'UserController@emailchange')->name('emailchange_after');
Route::get('/reset/{token}', 'UserController@emailchange_reset')->name('emailchange_reset');
マイグレーション ファイルを作成
php artisan make:migration email_resets_table
public function up()
{
Schema::create('email_resets', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('new_email')->unique();
$table->string('token');
$table->timestamps();
});
}
保存する情報はuser_id,新しいemail, そしてtokenです。このtokenは確認メールを実装する時に使用します。
モデルファイルも作ります。2つメソッドを用意します。確認メール関連です。
php artisan make:model EmailReset
use Notifiable;
protected $fillabel = ['user_id', 'new_email', 'token'];
public function sendEmailResetNotification($token)
{
$this->notify(new ChangeEmail($token));
}
public function routeNotificationForMail($notification)
{
return $this->new_email;
}
routeNotificationForMailメソッドはメールの送信先を指定しています。
新規email入力部分
現在のメールアドレスを表示し、その下に入力画面があります。
<form method="POST" action="{{ route('emailchange_after', ['user' => $user->id]) }}">
@csrf
<h5>現在のメールアドレス</h5>
<p style="margin-top: .4rem;">{{$user->email}}</p>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('新しいメールアドレス') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('new_email') is-invalid @enderror" name="new_email" value="{{ old('new_email') }}" autocomplete="email">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('登録する') }}
</button>
</div>
</div>
</form>
確認メールの送信
use App\Http\Requests\ChangeEmail;
public function emailchange_show(User $user)
{
$check_user = Auth::user();
if($check_user->id == $user->id) {
return view('users.emailchange')->with('user', $user);
} else {
return back();
}
}
public function emailchange(ChangeEmail $request, User $user)
{
$check_user = Auth::user();
if($check_user->id == $user->id) {
// トークン生成
$new_email = $request->new_email;
#1 $token = hash_hmac(
'sha256',
Str::random(40) . $new_email,
config('app.key')
);
#2 $email_reset = new EmailReset();
$email_reset->user_id = $user->id;
$email_reset->new_email = $new_email;
$email_reset->token = $token;
$email_reset->save();
#3 $email_reset->sendEmailResetNotification($token);
return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => '確認メールが送信されました']);
} else {
return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'メールの送信に失敗しました']);
}
}
#1, new->emailを元にtokenを作成しています。
#2, テーブルに保存するための値を代入していきます。
#3, 確認メールを送信します。モデルにあるメソッドを呼び出しています。最終的にはapp/notifications/ChangeEmailというところを動かすようになっています。
app/notifications/ChangeEmail
ここはemailが変更される時に送る確認メールを送信している場所になります。
php artisan make:notification ChangeEmail
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Lang;
class ChangeEmail extends Notification
{
use Queueable;
#1 public $token;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($token)
{
#2 $this->token = $token;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage)
->subject(Lang::get('変更認証メールです'))
->line(Lang::get('下記リンクをクリックしてメールアドレスの変更を完了してください'))
#3 ->action(Lang::get('変更認証メール'), url('reset', $this->token))
->line(Lang::get('この変更に身に覚えがない場合は、無視してください'));
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
#1, この処理で$tokenをいうものを取得できるようにしている。
#2, $this->tokenとする事でtokenを使用できるように設定している。
#3, urlにtoken情報を入れて送信している。
確認メールをクリックした時の処理(emailの更新)
public function emailchange_reset(Request $request, $token)
{
// token情報を取得
$email_reset = EmailReset::where('token', $token)->first();
// レコードが存在しているかつ、有効期限内でアクセスしているかを判断
#1 if($email_reset && !$this->tokenStatus($email_reset->created_at)) {
// ユーザーのemailを更新して保存
$user = User::find($email_reset->user_id);
$user->email = $email_reset->new_email;
$user->save();
// token履歴は使わないので削除
$email_reset->delete();
return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'メールアドレスが正常に変更されました']);
} else {
if($email_reset) {
$user = User::find($email_reset->user_id);
// 新規作成が出来なくなるので削除
$email_reset->delete();
return redirect()->route('my_page', ['user' => $user->id])->with(['user' => $user, 'say' => 'メールアドレスの変更に失敗しました']);
}
}
}
protected function tokenStatus($created_at)
{
// $expiresはurlは1時間有効なので3600秒で追加
// Carbon::parse($created_at)はtokenが作成された日時を取得
// addSeconds($expires)で上記で取得した日時に$expiresつまり、1時間分の時間を追加
// isPast()は上記で1時間追加した時間を現在過ぎているかどうかを判断。
$expires = 3600;
return Carbon::parse($created_at)->addSeconds($expires)->isPast();
}
少し量が多かったのでコメントアウトでも書いてます。
確認メールをクリックした後にこのアクションに飛んできます。
#1, tokenStatusは確認メールをクリックしたのが発行されてから1時間以内かをチェックしています。trueであれば変更するようにしています。
trueであれば変更した後、falseであれば最初の処理でemail_resetのレコードを削除しています。不要だし、再度変更リクエストが送れなくなってしまったりするので。
処理が終わった後はredirectでviewに飛ばします。
ここまでで処理は終了です!お疲れ様でした!
データベースなどで正しく変更されているか確認してみましょう!
学んだ事
routeNotificationForMailメソッドがメールの送信先を指定してくれている。
emailの方が手順が多い。->正しいか確認するため。
まとめ
emailはパスワードに比べれば確かに処理は多いですが、1つ1つ見ていくと流れが見えてきます。
やる前はemailもpasswordみたいに出来るのかと思ってましたw
今回はそんな感じで
以上!