Laravel8 ログイン機能実装4 値オブジェクトを用いたバリデーション
前回はlaravelを使って会員サイトの基本機能を構築しました。
しかし、これから機能を拡張していくことを考えると
内部構造を少し改良して、拡張性を高める必要があります。
そこで今回は「ドメイン駆動設計」の考え方を考慮しながら、機能拡張をしていきます。
ドメイン駆動設計(DDD)とは
「ドメインモデリング」です。本稿では詳細は割愛しますが、
値オブジェクト(value object)とは、氏名や価格などの情報をドメインとしてとらえ
オブジェクトとして定義しようというものです。
ドメイン駆動設計を理解する上でもきっかけ重要な考え方です。
詳細に下記ご参考まで。
手順
ドメイン駆動設計を意識しながら、バリデーションロジックを拡張し
システムが大きくなってもメンテンナンス性が落ちないようなコードに修正していきます。
手順は下記の通りです。
- 値オブジェクトの作成
- フォームリクエストを作成
- フォームリクエストからメッセージを受け取る
値オブジェクトの作成
「電話番号」や「メールアドレス」「住所」など、同じ『文字列』ですが
それぞれ数字であったり@マークが必須だったり、変数としてのルールがあります。
この『ルールに従った入力のみ許す』ように制御するのが、バリデーションです。
ただ、前回構築したバリデーションロジックには課題があります。
下記が会員登録画面のコントローラーです。
protected function validator(array $data) { Log::debug('validator'); return Validator::make($data, [ 'login_id' => ['required', new alpha_num_check(), 'max:20','unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required','confirmed'], 'zip-code' => ['required', 'string', 'max:7'], 'prefecture' => ['required', 'string', 'max:5'], 'city' => ['required', 'string', 'max:20'], 'address1' => ['required', 'string', 'max:20'], 'address2' => ['required', 'string', 'max:20'], 'tel' => ['required', 'string', 'max:9'], ]); }
コントローラーにバリデーションロジックを記載しています。
では、今後会員情報の変更ページを作成した場合にバリデーション機能はどうなるでしょう。
protected function validator(array $data) { Log::debug('validator'); return Validator::make($data, [ 'login_id' => ['required', new alpha_num_check(), 'max:20','unique:users'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required','confirmed'], 'zip-code' => ['required', 'string', 'max:7'], 'prefecture' => ['required', 'string', 'max:5'], 'city' => ['required', 'string', 'max:20'], 'address1' => ['required', 'string', 'max:20'], 'address2' => ['required', 'string', 'max:20'], 'tel' => ['required', 'string', 'max:9'], ]); }
どこかで見たコードを書くことになりますね。
このように
同じ「ログインID」に対する登録機能があるたびに、バリデーションロジックを記載する必要が出てきてしまいます。
このようなコードを書いていると、画面によってバリデーションロジックが異なったりしてバグの温床になりかねません。
そこで、値をオブジェクトとして捉え、それぞれのオブジェクトに対してのルールはまとめて「コード自体がルールを表現する」ようにします。
で、どうやるの
サンプルコードです。氏名についての値オブジェクトとして記述しています。
namespace App\ValueObjects; class Name implements ValueObject { protected $value; /** * name constructor. * @param $value * @throws \Exception */ public function __construct($value) { if(empty($value)){ throw new \Exception(NAME.'は必須入力です'); } if(!preg_match('%\D+%',$value)) { throw new \Exception(NAME.'を正しく入力してください'); } if(mb_strlen($value) >; 21){ throw new \Exception(NAME.'は20文字以内で入力してください'); } $this->value = $value; } public static function rules(){ return 'required|max:20|regex:%\D+%'; } public function value() { return $this->value; } public function equals($string):bool { return $this->value === $string->value; } /** * @param string $value * @return static * @throws \Exception */ public static function of(string $value) { return new static($value); } /** * @return string */ public function __toString() { return (string)$this->value; } /** * @return mixed|string */ public function jsonSerialize() { return $this->value; }
クラスの説明
コンストラクタ
登録機能以外にも氏名を扱う場面はありますので、その際にコンストラクタとして氏名を格納します。
セキュリティ観点でも、コンストラクタとして値を受け取った際のチェックロジックは必要です。
今回は「必須入力」「日本語」「20文字以内」というルールを表現しています。
rules
値オブジェクトのバリデーションルールを記述します。
登録画面や編集画面からこのクラスを呼び出すことで、システム全体でバリデーションロジックが統一できます。
複数のルールを記述する際には「|」で挟んでいきます。
記載可能なバリデーションルールはLaravelのマニュアルをご覧ください
https://readouble.com/laravel/8.x/ja/validation.html
値オブジェクトで表現できないルールについて
例えば、「パスワードは再入力項目と一致している事」などは会員登録画面に限定したロジックになります。
この場合は、登録画面のコントローラーにてバリデーションルールを追加します
protected function validator(array $data) { Log::debug('validator'); return Validator::make($data, [ 'password' => ['confirmed'], ]); }
value
コンストラクタから値を取り出すときに使います。
equals
コンストラクタの値との比較を行います。
その他クラスの説明は割愛します。
これを値オブジェクト分作成していきます。
フォームリクエストの作成
laravelのコマンドを使います。
php artisan make:request CustomerRegister
コマンド実行後、自動でファイルが作成されます。
app/Http/Requests/CustomerRegister.php
class CustomerRegister extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { //① trueに変更する return false; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { //② バリデーションルールを記載する return [ ]; } public function messages() { //③ バリデーションエラーメッセージを記載する return [ ]; } }
認可の設定です。デフォルトではfalseになっていますが、trueに変更する必要があります。
falseのままだと下記の画面になります。
認可されていないというエラーです。画面は修正したいところですが、今後の対応とします。
rules
フォームに対してのバリデーションルールを記述します。
作成済みの値オブジェクトを利用すると下記のように記述できます。
public function rules() { return [ 'address1' => Address1::rules(), 'address2' => Address2::rules(), 'city' => City::rules(), 'email' => Email::rules(), 'login_id' => Login_id::rules(), 'password' => Password::rules(), 'prefecture' => Prefecture::rules(), 'tel' => Tel::rules(), 'zip-code' => Zipcode::rules() ]; }
すっきりしましたね。
messages
最後に、エラーメッセージをカスタマイズします。
public function messages() { return [ 'required' => ':attribute の入力は必須です。' ]; }
フォームリクエストからメッセージを受け取る
リクエストデータを登録機能に引き渡す処理にリクエストフォームのデータを引数として追加します。
Laravelの会員機能はトレイトを使っているため、下記が修正箇所になります。
src/Illuminate/Foundation/Auth/RegistersUsers.php
public function register(CustomerRegister $request) { Log::debug('register'); $this->validator($request->all())->validate(); event(new Registered($user = $this->create($request->all()))); $this->guard()->login($user); return $this->registered($request, $user) ?: redirect($this->redirectPath()); }
引数を今回作成したフォームリクエストに修正しています。
public function register(request $request)
↓
public function register(CustomerRegister $request)
これでドメイン駆動設計を意識したバリデーションロジックの完成です。
Laravelログイン機能連載一覧
- Laravel 8でログイン機能実装1 基本機能
- Laravel 8でログイン機能実装2 ログインIDの導入
- Laravel 8でログイン機能実装3 会員登録時の入力項目追加
- Laravel8でログイン機能実装4 ValueObjectとバリデーション ← いまここ
- Laravel 8でログイン機能実装5 会員情報変更
分かりやすく助かっております。
もしよろしければ以下を教えていただけないでしょうか?
「で、どうやるの」の章の「class Name implements ValueObject
{~~~」コードは、どのファイルに記述すべきでしょうか?
新しくファイルを作成するべきでしょうか?
こちらは新しくclassファイルを作成しています。
ValueObjectごとにファイルを作成していっております。