またどこかでCTOっぽいことやってる人のブログ

フリーランスを経て、またどこかでCTOっぽいことをやってる人が書いてます。何か色々やってます。

CakePHP3でbuildRuleに条件分岐を追加してみた。

2017/05/14 追記 ------------------------------------------

昔できていたはずなのに、過去の自分の記事を見ながら作ってみたらエラーに…。
下の方、修正しておきました。
2017/05/14 追記終わり -----------------------------------


2016年の夏はどうも天気がシャキっとしてません(7月現在)
きっと、まだ地球さんが本気出してないんでしょう。

さて、今回はこんなことをやりたくなりました。

「ユーザ登録時にメールアドレスは必須じゃないけど、入力された時は重複不可にしたい」

重複不可だけならbuildRule(saveメソッドコール時に実行されるバリデーション)に書けば終わりです。

<?php
 public function buildRules(RulesChecker $rules) {
   // カラム名はemailと仮定します
   $rules->add($rules->isUnique(['email'], 'メールアドレス重複エラー'));

   // 処理結果を返す
   return $rules;
}

なんですが。
メールアドレスが必須ではないので、入力された時はいいとしても未入力の場合は最初の1件しかデータが入らず、メールアドレス未入力データ2件目以降の場合はしっかりエラーになってしまいます。
要するに「メールアドレスが空のデータはもうありますので!」というわけです。

困りました。
困りましたが、要は「メールアドレスが入力された時だけ、重複チェックすればいいじゃん」というのはわかります。
何か上手い方法はないものかと色々調べたのですが、どうもそれっぽい情報が見当たりません。
で、公式を見ながら実現できたコードが以下です。

<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\Rule\isUnique; // これを宣言しておく必要があります

class UsersTable extends Table {

/**
 * POST時のバリデーション
 */
public function validationDefault(Validator $validator) {
  // 今回は省略しますが、何かバリデーションを色々
  // メールアドレスを必須にせずに形式だけチェック
  $validator
    ->allowEmpty('email')
    ->email('email', true, 'メールアドレス形式ではありません。');

  // 処理結果を返す
  return $validator;
}


/**
 * save実行時のバリデーション
 */
public function buildRules(RulesChecker $rules) {
  // カラム名はやっぱりemailと仮定します
  $rules->add(
    function ($entity, $options) {
      if (!empty($entity->email)) {
        // 2017/05/14 ここから修正
        $rule = new isUnique(['email'], $options);
        // 2017/05/14 ここまで修正
        return $rule($entity, $options);
      } else {
        // 未入力の場合はtrueで返す
        return true;
      }
    },
    ['errorField' => 'email', 'message' => 'メールアドレス重複エラー']
  );
  return $rules;
}

$entityからチェックしたいカラムを抜いてきて、その項目に対してemptyチェック。
入力されていたらisUniqueで重複チェックしてます。

isUniqueのコンストラクタ引数が配列指定なので上記のような形式で渡しています


未入力の場合はtrueを返してます。
どうもちゃんと明示的に書かないとダメみたいです。
エラーメッセージ設定は見たままです。

きっともう少しいい方法あるんじゃないかなとは思います。
が、今回emptyチェックしている部分を色々変えれば融通が利くbuildRuleが作れるかなと。

そもそもemailを必須にしない仕様自体がアレとかいう話もありますが。