phalcon5にはデータが一意か(重複しないか)バリデーションするクラスが用意されています。
そのクラスがUniquenessです。
マニュアルはこちら。
https://docs.phalcon.io/5.0/ja-jp/filter-validation#uniqueness
このUniquenessバリデーションを使うパターンとしては、ログイン情報を登録するテーブルのメールアドレスなどがあります。
この時、ハマったポイントがあったのでメモ。
Uniquenessバリデーションでハマった点
例えば以下のようなフォーム作成したいとします。
- ログインのアカウント情報を登録するフォームを作成
- アカウント情報テーブルに登録するメールアドレスは一意とする
この時にメールアドレスが重複していないかUniquenessバリデーションを使って書くと以下のようになります。
今回はバリデーションクラスを作成せず、form生成時にバリデーションを記載とします。
$mail = new Text('mail'); $mail->setLabel('メールアドレス'); $mail->setFilters('email'); $mail->addValidators([ new PresenceOf([ 'message' => 'メールアドレスを入力してください。', 'cancelOnFail' => true, ]), new Email([ 'message' => 'メールアドレスの形式で入力してください。' ]), new StringLength([ 'max' => 300, 'messageMaximum' => 'メールアドレスは300文字以内で入力してください。' ]), // ここが重複チェック(一意か)のバリデーション new Uniqueness([ 'model' => new Account(), 'convert' => function (array $values) { // メールアドレスを暗号化してテーブルに保存している場合は、メアドを暗号化して検索 // $valuesにはformで送信されてきたメールアドレスが格納されています $values['mail'] = crypt($values['mail']); return $values; }, 'message' => 'すでに登録済みのメールアドレスです。' ]) ]); $this->add($mail);
これで、formから送られてきた値をisValid()でバリデーションした際にUniquenessのバリデーションを実行できます。
今回は'model'で設定したAccountクラス(Accountテーブル)のmailカラムに、すでに同じメールアドレスが登録されていないかチェックしています。
で、新規登録時も更新時も同じFormを使用することが多いと思いますが、この時に新規登録の場合はいいのですが、更新時に問題が発生します。
更新時にメールアドレスを変更せずに更新しようとすると、Uniquenessのバリデーションにひっかかって更新できないのです。
例えば、以下のようなデータがあった場合に、
id | |
---|---|
1 | aaaa@bbb.com |
2 | hoge@hoge.com |
id=1のデータを更新する場合に、メールアドレスを変更せずに更新とすると、id=1の「aaaa@bbb.com」と重複しているのでバリデーションエラーになります。
この場合は更新できてほしいです。
ハマりポイントの解決方法
実現したいバリデーションは以下です。
- 新規登録時はすべてのデータと比較し重複していないかチェック
- 更新時は自身のデータ以外と比較して重複していないかチェック
上記の方法では、新規登録時は条件を満たしていますが、更新時の条件は満たしていません。
この問題を解決するために'except'オプションを使用します。
'except'オプションは、指定した値を除外して重複チェックしてくれます。
マニュアルはこちらです。
https://docs.phalcon.io/5.0/ja-jp/filter-validation#using-except-for-fields-sql-operation-value-not-in-except
$mail = new Text('mail'); $mail->setLabel('メールアドレス'); $mail->setFilters('email'); $mail->addValidators([ new PresenceOf([ 'message' => 'メールアドレスを入力してください。', 'cancelOnFail' => true, ]), new Email([ 'message' => 'メールアドレスの形式で入力してください。' ]), new StringLength([ 'max' => 300, 'messageMaximum' => 'メールアドレスは300文字以内で入力してください。' ]), // ここが重複チェック(一意か)のバリデーション new Uniqueness([ 'model' => new Account(), 'convert' => function (array $values) { // メールアドレスを暗号化してテーブルに保存している場合は、メアドを暗号化して検索 // $valuesにはformで送信されてきたメールアドレスが格納されています $values['mail'] = crypt($values['mail']); return $values; }, 'except' => $entity->mail, // ここを追加 'message' => 'すでに登録済みのメールアドレスです。' ]) ]); $this->add($mail);
exceptには更新しようとするデータのメールアドレスを指定します。
$entityはformクラスのconstructで受け渡ししています。
if ($id) { // 更新時はテーブルからデータを取得 $account = Account::findFirst($id); } else { // 新規登録時はデータなし $account = new Account(); } $form = new AccountForm($account);
こうすることで、新規登録時は$entity->mailはnullなので、mailがnull以外のデータと比較してくれ、
更新時は、$entity->mailが更新しようとするデータのmailなので、自身のmail以外のデータと比較してくれます。
id | |
---|---|
1 | aaaa@bbb.com |
2 | hoge@hoge.com |
こちらのデータの例でいうと
- 新規登録時はすべてのmailと比較
- id=1のデータ更新時はid=1のmail以外のmailと比較
となり、実現したかったバリデーションとなります。