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 |
mail |
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 |
mail |
1 |
aaaa@bbb.com |
2 |
hoge@hoge.com |
こちらのデータの例でいうと
- 新規登録時はすべてのmailと比較
- id=1のデータ更新時はid=1のmail以外のmailと比較
となり、実現したかったバリデーションとなります。