OctoberのModelクラスにはオーバーライドすることができるイベントメソッドが複数あり、バリデーション前後や、レコードの作成前後、レコードの更新前後など、それぞれの処理の前後に開発者が任意の処理を加えることができます。
今回は「octoberのバリデーションチェックではできない選択内容の組み合わせエラーをチェックする」という実装をする際に、イベントメソッドのafterValidateを利用したのですが、使い方に苦戦したのでご紹介します。
環境
- Laravel:5.5
- October CMS:1.0
仕様
まずafterValidateを含む登録フローをざっくりですが整理してみました。
※今回はレコード作成時を想定しています。
①beforeValidate() → ②october側でバリデーションチェック → ③afterValidate()
→ ④beforeCreate() → ⑤october側でレコード作成処理 → ⑥afterCreate()
簡単に書くとこんな感じでした。
私は③のafterValidateで、組み合わせエラーをチェックしようと考えました。
ValidationExceptionというクラスがあったので、これを利用して③でエラーがあればエクセプションを投げ、画面にエラー内容を表示するように実装しました。
Test1.php
<?php namespace casareal\Test\Models; use Model; use Db; use BackendAuth; use October\Rain\Exception\ValidationException; use October\Rain\Database\ModelException; class Test1 extends Model { use \October\Rain\Database\Traits\Validation; // Modelの基本プロパティ public $table = 'casareal_table'; // バリデーション public $rules = [ 'title' => 'required' ]; // バリデーション前後のイベント public function beforeValidate() { } public function afterValidate() { if ($this->status == 1 && empty($this->published_at)) { throw new ValidationException(['error' => 'ステータスが「公開」の時、公開日は必須です。']); } } }
ですが何度やっても②でエラーがあったとしても、③のエクセプションが優先して出てきてしまいました。
内部コードを調べてみると、以下のようなフローであることが分かりました。
beforeValidate() → october側でバリデーションチェック → afterValidate()
→バリデーションエラーの出力
このことから、afterValidate内でエクセプションを投げてしまうと、バリデーションエラーが出力される前にエクセプションが投げられることになってしまうことが分かりました。
これを踏まえて実装を行いました。
実装
まず内部コードを見て、$this->validationErrorsには、october側のバリデーションエラーが入っていることが分かったので、そこにafterValidate内のエラー内容を追加するようにコードを変更してみました。
public function afterValidate() { // エラー内容の追加 if ($this->status == 1 && empty($this->published_at)) { $this->validationErrors->add("error", "ステータスが「公開」の時、公開日は必須です。"); } }
ただこれだけでは、afterValidateの内容が出力されませんでした。
原因は、october側のバリデーションチェックでエラーがあったかどうかのフラグを持っているらしく、afterValidateでエラーを追加しても、october側のバリデーションチェックでエラーが無ければ、afterValidateの内容は無かったことになってしまうようでした。
これを踏まえて、次のように変更しました。
public function afterValidate() { if ($this->status == 1 && empty($this->published_at)) { $this->validationErrors->add("error", "ステータスが「公開」の時、公開日は必須です。"); } // エラー出力 if (!empty($this->validationErrors->toArray())) { throw new ModelException($this); } }
これで、octoberのバリデーションエラーが先、afterValidateのエラーが後に、
$this->validationErrorsに格納されるため、表示の優先順位が問題なくなり、「バリデーションチェック後、status=”1″でpublished_atに値が無かった場合にエラーを投げる」という実装ができました。
まとめ
私は内部ソースを読んでこの方法を発見したので、正規の方法とは違っている可能性がありますが、私の探し方では公式の使い方が見つからなかったため、独自の方法をご紹介しました。
以上になります。