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

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

Laravel開発、ユーザー情報を編集する【2】ユーザ情報更新を個別に扱う

前回でユーザー名とパスワードの変更処理はできるようになりました。
しかし、実装を行ってみて問題点に気づいたので、こちらを解決していきます。
前回の記事はこちら 

fippiy.hatenablog.jp

今回の目的

  • ユーザー情報編集フォームから送信した内容をバリデーションによって正しいデータのみ処理できるようにしたい
  • パスワード変更時に現状のパスワードによる認証をしたい

なぜやるか

  • フォーム入力全データバリデーションを使用すると、意図しないバリデーションエラーとなっているので、対処するため
  • 現状パスワード認証処理をすることで、ログインユーザーであることを確認して新パスワードへ移行する処理とするため

やりたいこと

ユーザー情報毎に編集できるようにして、個別にバリデーションを設定したい
現状のパスワードを認証する仕組みを実装したい

やったこと

indexページから各カラム情報編集の導線を作成する
カラム別にユーザー情報を編集できるようにする
editビューひとつで各カラム編集に対応する
現在のパスワードを認証する
カスタムバリデーションを作成する 

実施内容

パスワード処理の課題を整理する 

前回の課題を確認する
前回記事の最後に上げた課題はこちらです。
ユーザーのパスワード変更作業を実装していました。
  1. バリデーションに文字数制限を入れていない為1文字から設定できてしまう
  2. 8文字以上設定にすると0文字で更新対象外とならなくなる(入力必須)
  3. 現在のパスワードを確認せずに新パスワードでいきなり更新される
まず1と2です。
文字数制限をいれるのは簡単ですが、制限することによって空欄時の対象外とする処理ができなくなります。
両方の条件に一致するバリデーションを試行錯誤しましたが、うまくいきませんでした。正規表現等で表現できればできそう…ですが、そこまでしてバリデーションに2つの条件をまとめる必要があるのか?という疑問もあります。
そして3です。
こちらは、結果として私が考慮してなかっただけですが…。
パスワード更新時に現在のパスワードを確認する工程をいれてなかったので、追加実装したいと考えています。
 
 
ユーザー情報更新の手順を変更する
問題1と2を解決します。
現在のフォーム構造はユーザー情報(名前・メール・パスワード)を一つのフォームに表示し、変更があった情報を全て更新する…といった想定をしていました。
しかし、バリデーションが複雑になってきたので、全データ編集をやめて、各カラム毎に編集して更新するといった形式に変更することにしました。
こうすることで、個別に編集フォームからデータを送信し、そのデータに対してのみバリデーションするので結果として記述が簡単な内容になります。
 
編集フォームについては、カラム毎にビューを用意する手順を考えましたが、ビューファイル、アクション共に3つ作成となります…が、ここでは編集フォームページを1つにして、条件によって対象の編集フォームだけを表示するという方法にすることにしました。
編集ページへのリンクについても、メニューに表示している編集ボタンを廃止し、indexページのユーザー情報に対して編集ボタンを用意し、そこから個別に編集画面を表示させる…といった手段をとることにしました。
以上で実装を行っていきます。 

マイページの表示を変更する

マイページトップ画面を編集する
まずは、トップページの表示を変更します。
# ~/resources/views/user/index.blade.php
<div class="book-table">
<div class="book-table__profile-list">
<div class="profile-group">
<div class="profile-group__title">ユーザーID</div>
<div class="profile-group__element">{{$auth->id}}</div>
</div>
<div class="profile-group">
<div class="profile-group__title">ユーザー名</div>
<div class="profile-group__element">{{$auth->name}}</div>
<div class="profile-group__edit">
 <a href="{{ route('edit.user', 'name') }}">編集</a>
</div>
</div>
ユーザー情報一覧表示ページにカラム毎に編集ボタンを設置して、ここから個別に編集することとし、リンクを設置しました。
ここでroute情報にnameという第二引数を設定しています。これを利用して編集画面で変更するカラムのフォームのみ表示するという形にします。
 
# ビュー表示

f:id:Fippiy:20190420153142p:plain

ビュー表示
編集リンクを個別に設置したことにより、変更したいデータのみ編集する導線を確立しました。
マイページメニューから編集へ移動する必要がなくなったので編集メニューは削除してます。
また、パスワード編集のボタンが必要となるため、パスワード項目を追加してますが、表示はしないことにしました。
 
編集ビュー設置のためのコントロールを再定義する
次にユーザーeditページを編集していきます。
コントローラ
# ~/app/Http/Controllers/UserController.php
public function edit($id)
{
//
}

 
public function useredit($page)
{
$auth = Auth::user();
return view('user.edit',[ 'auth' => $auth, 'page' => $page ]);
}
編集画面を表示させるためのeditアクションについては、標準仕様から外れる為、新たにusereditアクションに再定義しました。
ビューで設定した引数をpageとして取得して、編集を行うカラムを選択できるようにしています。
 
ルート設定
# ~/routes/web.php
// ログイン必須ページ
〜省略〜
Route::get('/user/{page}', 'UserController@useredit')->name('edit.user');
Route::post('/user/{page}', 'UserController@update')->name('update.user');
Route::resource('user', 'UserController',['except' => ['edit']]);
});
コントローラーの修正に伴ってルートも変更しています。
 

edit画面を再編集する

ユーザー名編集画面を再定義する
editビューを修正していきます。
各編集ボタンに対応したフォームのみ表示する…といった方法にしていきます。
まずは、ユーザー名のみでフォームを再作成します。
 

f:id:Fippiy:20190420154036p:plain

編集画面
# ~/resources/views/user/edit.blade.php
 
<div class="index-content">
<div class="books-list">
<div class="books-list__title mypage-color">
ユーザー情報編集
</div>
@if (isset($msg))
<div class="books-list__msg">
<span>{{$msg}}</span>
</div>
@endif
<div class="book-new">
@if ($page == 'name')
<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}}">
<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="name"
 value="{{$auth->name}}"></div>
</div>
</div>
</div>
<div class="form-foot">
<input class="send" type="submit" value="編集">
</div>
</form>
@endif
indexページの編集ボタンで$pageを定義していたので、$pageの内容によって表示するフォームを変更しよう…という考えです。
route情報にnameという第二引数を渡していたので、$pageの内容によってフォームが表示できます。
nameのみ実装しましたが、ここに他のページも追加することで全カラムに対応しようというものです。
 
コントローラー
# ~/app/Http/Controllers/UserController.php
public function update(Request $request, $id)
{
// 選択ページ情報取得
$page = $request->page;
// 選択ページでバリデーションを選ぶ
if ($page == 'name'){
$rule = User::$editNameRules;
} elseif ($page == 'email'){
$rule = User::$editEmailRules;
} elseif ($page == 'password'){
$rule = User::$editPasswordRules;
}
// バリデーションチェック
$this->validate($request, $rule);
// 対象レコード取得
$auth = User::find($id);
// リクエストデータ受取
$form = $request->all();
// フォームトークン削除
unset($form['_token']);
// ページ情報削除
unset($form['page']);
// パスワードハッシュ化
if (isset($form['password'])) {
$form['password'] = Hash::make($form['password']);
}
// レコードアップデート
$auth->fill($form)->save();
return redirect('/user');
}
保存処理です。ここでページによってバリデーション選択を変更することで、編集に応じた対応ができるようにしました。
 # ~/app/User.php
public static $editNameRules = array(
'name' => 'required|max:255'
);
public static $editEmailRules = array(
'email' => 'required|email'
);
public static $editPasswordRules = array(
'password' => 'confirmed|min:8'
);
 
こちらがそのバリデーションです。
選択したフォームに対するバリデーションのみを設定しているので、他のカラムの条件を考える必要がなくなった為、シンプルに設定できるようになりました。
特にパスワードについては、確認フォームと一致すること、8文字以上という条件をしっかり指定ができています。フォームを個別にしたことにより未入力=変更処理なしという点については考える必要がなくなりました。
各カラムに対応したフォームを表示する
編集フォームのif分岐をform全体にいれていましたが、ユーザー名など、各カラムの部分だけが異なっている状態でしたので、ifの範囲を変更しました。
アクション指定はおなじ、page名は変数でとれるので、form-groupクラス内だけ書き換えることで対応しました。
# ~/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}}">
<div class="form-contents">
<div class="form-one-size">
@if ($page == 'name')
<div class="form-input">
<div class="form-label">ユーザー名</div>
<div><input class="form-input__input" type="text" name="name"
 value="{{$auth->name}}"></div>
</div>
@endif
</div>
</div>
<div class="form-foot">
<input class="send" type="submit" value="編集">
</div>
</form>
これでも問題なく動作しました。
 
動作確認もできたので、残りのフォームも追加します。 
# ~/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}}">
<div class="form-contents">
<div class="form-one-size">
@if ($page == 'name')
<div class="form-input">
<div class="form-label">ユーザー名</div>
<div><input class="form-input__input" type="text" name="name"
 value="{{$auth->name}}"></div>
</div>
@endif
@if ($page == 'email')
<div class="form-input">
<div class="form-label">メールアドレス</div>
<div><input class="form-input__input" type="text" name="email"
 value="{{$auth->email}}"></div>
</div>
@endif
@if ($page == 'password')
<div class="form-input">
<div class="form-label">パスワード</div>
<div><input class="form-input__input" type="password" name="password"
 value=""></div>
</div>
<div class="form-input">
<div class="form-label">パスワード(確認)</div>
<div><input class="form-input__input" type="password"
 name="password_confirmation" value=""></div>
</div>
@endif
</div>
</div>
<div class="form-foot">
<input class="send" type="submit" value="編集">
</div>
</form>
これでフォームひとつでそれぞれのカラムに対しての編集ページがでるようになりました。
 
以上で、個別カラム毎のデータ編集ページを作成することができました。
パスワードに対してのバリデーション指定もシンプルになっています。
 

現在のパスワードを認証する

残っている問題点を解決する
パスワードとパスワード確認に対してのバリデーション設定はできました。
しかし、パスワード変更時には現在のパスワードを認証した上で新パスワードへの変更といった処理がありませんので、追加していきます。
 
フォームを追加する
新しいパスワードと確認のフォームしかない状態でしたので、現在のパスワードを入力する欄を追加します。 
 # ~/resources/views/user/edit.blade.php
<div class="form-input">
<div class="form-label">現在のパスワード</div>
<div><input class="form-input__input" type="password"
 name="old_password" value=""></div>
</div>
<div class="form-input">
<div class="form-label">新パスワード</div>
<div><input class="form-input__input" type="password"
 name="password" value=""></div>
</div>
<div class="form-input">
<div class="form-label">新パスワード(確認)</div>
<div><input class="form-input__input" type="password"
 name="password_confirmation" value=""></div>
</div>
 
old_passwordとして追加しました。
 
パスワード認証を実装する 
認証方法について調べてみたところ、こちらを参考にさせて頂きました、ありがとうございます。

readouble.com

テキスト認証をする場合、フォーム入力テキストとDBの内容をチェックすればいいのですが、今回の場合ここに一手間必要となります。

DBに登録されているパスワードは暗号化されているため、そのまま比較してもテキスト内容が異なる為、比較できません。

 

そこで、ハッシュチェックを利用して、チェックすることにしました。

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

// 旧パスワードチェック
$passcheck = Hash::check($form['old_password'], $auth->password);

フォームデータとDBのパスワードを照合して、true or falseで結果が返ってきます。

あとはfalse時に変更処理をしなければ良いということになります。

パスワード認証結果をバリデーションエラーに表示する 
バリデーションエラー時は$errorsにエラー内容が反映されて元のページにリダイレクトされます。
現在のパスワード認証についても、バリデーションエラーとして返すことによって、リダイレクト処理とエラーメッセージ表示を同時に実装できると思い、実装することにしました。
 
今までのバリデーションは入力したテキストに対してのチェックをしていました。
しかし、今回はテキストではなく、ハッシュチェックを行った結果に対してバリデーションチェックさせます。 
 
バリデーションチェックの手順について改めて確認しました、こちらを参考にさせて頂きました、ありがとうございます。
 
"accepted"によるチェックとして、false時にはエラーとする処理として作成しました。
# ~/app/Http/Controllers/UserController.php
 
// old_passwordにチェック結果をいれて、バリデーションチェックする
$validator = Validator::make(['old_password' => $passcheck],
 ['old_password' => 'accepted']);
バリデータを使用して、先程の結果を入れ、trueの場合は問題なし、falseの場合はバリデーションに引っかかりNGとなります。
# ~/app/Http/Controllers/UserController.php
// NG時にエラーとして処理をかえす
if ($validator->fails()) {
return redirect('user/password')
->withErrors($validator)
->withInput();
}
バリデーションエラー時は、エラーメッセージを返して、リダイレクトさせます。
 
バリデーションエラーを表示させる
パスワード認証は今回カスタム作成しました。
カスタムバリデーションのエラーメッセージの表示をどうするのか確認したところ、バリデーターの第3引数にセットすればいいようです。
// old_passwordにチェック結果をいれて、バリデーションチェックする
$validator = Validator::make(['old_password' => $passcheck],
 ['old_password' => 'accepted'], ['現在のパスワードが一致しません']);
第3引数にメッセージを追加しました。
 
# ビュー表示
 

f:id:Fippiy:20190421003319p:plain

エラー表示

バリデーションが適用され、エラーメッセージがでました。

後は、エラーメッセージの表示を調整します。

# ビュー表示

f:id:Fippiy:20190421211008p:plain

エラー表示修正

 

以上で現在のパスワードに対する認証ができるようになりました。

 

以上で、編集ページ内のパスワードまわりの設定が完了しました。

次は、メールアドレスの編集処理を行っていきます。