Laravel 8で会員サイト構築5 会員情報更新機能
Laravelのパッケージとして利用できるログイン機能のカスタマイズシリーズも第5弾となりまして、今回は会員情報更新機能を準備します。
会員サイトとしては必須の機能となりますが、更新辺りから少し複雑になってきますのでできるだけ細かく記載していきます。
対応概要
パッケージとして利用できる会員登録機能の流れに準拠し、会員情報更新機能を構築します。
今回の対応の中では作り込まない機能もありますが、ルールを統一のために呼び出すだけ呼び出されている機能などもありますので、ご了承ください。
手順
今回準備が必要なのは下記です。ちょっと多いですが、個々の対応はそこまで複雑ではありません。
- コントローラ
- Trait
- リクエストフォーム
- Event
- View
- ルートの設定
それではまずはコントローラを作成しましょう。
コントローラ新規作成
ベースは会員登録のコントローラ(RegisterController)をコピペしてきます。
完成形は下記になります。
namespace App\Http\Controllers\Customer; use App\User; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Validator; use App\Traits\Customer; use Illuminate\Validation\Rule; class EditController extends Controller { use Customer\EditsTrait; protected $redirectTo = '/home'; public function __construct() { $this->middleware('auth'); } public function index () { return $this->showEditForm(); } protected function validator(array $data) { return Validator::make($data, [ Rule::unique('users')->ignore(Auth::id()) ]); } protected function update(array $data) { $id = Auth::id(); $customer = User::find($id); $customer->email = $data['email']; $customer->zipcode = $data['zipcode']; $customer->prefecture = $data['prefecture']; $customer->city = $data['city']; $customer->address1 = $data['address1']; $customer->address2 = $data['address2']; $customer->tel = $data['tel']; $customer->save(); return $customer; } }
use Customer\EditsTrait
あとから作成するTraitを呼び出しています。
会員情報変更に関わるビジネスロジックはこちらに集約します。
index
会員情報更新画面を呼び出します。showEditFormはTraitに定義しておりまして、Viewに渡す会員情報の取得などもTraitから渡しています。
validator
こちらが今回のミソです。
会員情報を更新する際に、メールアドレスのバリデーションロジックをそのまま使うと
「メールアドレスがすでに登録されているよ」というエラーに見舞われることになります。
自分の会員情報を更新しようとして、すでに自分の情報として登録済みのメールアドレスが重複チェックとして引っ掛かるわけです。
そこで、重複チェックの対象から「ログインユーザのみ外す」というルールを追加します。
Rule::unique('users')->ignore(Auth::id())
update
Viewからarrayでパラメータを受け取り、updateしています。
更新対象の会員はログイン情報から受け取るようにします。
$id = Auth::id();
View側にhiddenで埋め込んだIDを使うなどしてしまうと、不正にIDが書き換えられた場合に他人の会員情報が更新できてしまいます。必ずフロント側から遠いところで会員特定する必要があります。
traitの準備
こちらもベースはRegisterUsersを利用しますが、このファイルはコアファイルの中に格納されているので、探すのが少し手間取ります。
パスはこちらになります。
src/Illuminate/Foundation/Auth/RegistersUsers.php
完成したソースがこちら
namespace App\Traits\Customer; use App\Events\Customer\CustomerEditedEvent; use App\Http\Requests\Customer\CustomerEditRequest; use App\User; use Illuminate\Foundation\Auth\RedirectsUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; trait EditsTrait { use RedirectsUsers; public function showEditForm() { $id = Auth::id(); $customer = User::find($id); return view('customer.edit',[ 'customer' => $customer ]); } public function edit(CustomerEditRequest $request) { $this->validator($request->all())->validate(); event(new CustomerEditedEvent($user = $this->update($request->all()))); $this->guard()->login($user); return $this->edited($request, $user) ?: redirect($this->redirectPath()); } protected function guard() { return Auth::guard(); } protected function edited(request $request, $user) { } }
こちらもメソッドの説明をしていきます。
showEditForm
会員情報更新画面を表示させるためのメソッドです。
$id = Auth::id() でログイン中の会員情報を取得しており、Viewにパラメータとして渡しています。
return view('customer.edit',[
'customer' => $customer
]);
edit
会員情報更新のビジネスロジックを記述しています。
public function edit(CustomerEditRequest $request)
CustomerEditRequestリクエストフォーム(このあと構築)からパラメータを受けとって処理を行います。
$this->validator($request->all())->validate();
コントローラ独自のバリデーションロジックを読み出します。
event(new CustomerEditedEvent($user = $this->update($request->all())));
会員情報変更をイベントとして登録します。(イベントの中身はこのあと構築)
リクエストフォーム新規作成
登録画面の作成のたびに、リクエストフォームも準備します。
こちらも会員登録画面作成時に作成したリクエストフォームをベースにします。
namespace App\Http\Requests\Customer; use App\ValueObjects\Address1; use App\ValueObjects\Address2; use App\ValueObjects\City; use App\ValueObjects\Email; use App\ValueObjects\Prefecture; use App\ValueObjects\Tel; use App\ValueObjects\Zipcode; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; class CustomerEditRequest extends FormRequest { public function authorize() { return true; } public function rules() { $id = Auth::id(); return [ 'address1' => Address1::rules(), 'address2' => Address2::rules(), 'city' => City::rules(), 'email' => Email::rules($id), 'prefecture' => Prefecture::rules(), 'tel' => Tel::rules(), 'zipcode' => Zipcode::rules() ]; } public function messages() { return [ 'required' => ':attribute を入力してください。' ]; }
今回、メールアドレスの値オブジェクトはバリデーションルールを修正しています。
public static function rules($customer_id = 0){ if($customer_id===0){ return ['required','string','email','max:255']; }else{ $customer = User::find($customer_id); return ['required','string','email','max:255',Rule::unique('users')->ignore($customer->email,'email')]; } }
更新対象として、重複チェックから外す対象がある場合の分岐を追加しています。
VIEWの新規作成
@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Customer_Edit') }}</div> <div class="card-body"> <form method="POST" action="{{ route('customer.update') }}"> @csrf <div class="form-group row"> <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label> <div class="col-md-6"> <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $customer->email }}" required autocomplete="email"> @error('email') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <!-------ここから会員登録追加----------> <div class="form-group row"> <label for="zip-code" class="col-md-4 col-form-label text-md-right">{{ __('zip-code') }}</label> <div class="col-md-6"> <input id="zip-code" type="number" class="form-control @error('zip-code') is-invalid @enderror" name="zipcode" value="{{ $customer->zipcode }}" required autocomplete="zip-code"> @error('zip-code') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="prefecture" class="col-md-4 col-form-label text-md-right">{{ __('prefecture') }}</label> <div class="col-md-6"> <input id="prefecture" type="text" class="form-control @error('prefecture') is-invalid @enderror" name="prefecture" value="{{ $customer->prefecture }}" required autocomplete="prefecture"> @error('prefecture') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="city" class="col-md-4 col-form-label text-md-right">{{ __('city') }}</label> <div class="col-md-6"> <input id="city" type="text" class="form-control @error('city') is-invalid @enderror" name="city" value="{{ $customer->city }}" required autocomplete="city"> @error('city') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="address1" class="col-md-4 col-form-label text-md-right">{{ __('address1') }}</label> <div class="col-md-6"> <input id="address1" type="text" class="form-control @error('address1') is-invalid @enderror" name="address1" value="{{ $customer->address1 }}" required autocomplete="address1"> @error('address1') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="address2" class="col-md-4 col-form-label text-md-right">{{ __('address2') }}</label> <div class="col-md-6"> <input id="address2" type="text" class="form-control @error('address2') is-invalid @enderror" name="address2" value="{{ $customer->address2 }}" autocomplete="address2"> @error('address2') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="tel" class="col-md-4 col-form-label text-md-right">{{ __('tel') }}</label> <div class="col-md-6"> <input id="tel" type="tel" class="form-control @error('tel') is-invalid @enderror" name="tel" value="{{ $customer->tel }}" required autocomplete="tel"> @error('tel') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <!-------ここまで会員登録追加----------> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Register') }} </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection
ルート設定
追加するのはこの2行です。
Route::get('/customer/edit', 'Customer\EditController@index')->name('customer.edit'); Route::post('/customer/edit', 'Customer\EditController@edit')->name('customer.update');