【現象】
CakePHPのmodelを利用して、tinyint(1)で定義したカラムを読み込むとbool型として読み込まれる。
これまでは特に気にしていなかったが、、このデータをfindByIdで読み込んで、、そのまま、setして保存すると、、、
カラムに対して、validateを設定していない場合は成功するが、validate設定(notEmpty)している場合に中身がfalseだと失敗することが発生。。。
いやいや、boolで判別してるのはCakeさんですよね。。。と、言いつつ、不可解なのでコードを読んでみた。
【見たところ】
1. /lib/Cake/Utility/Validation.phpのnotEmpty。
実際の値チェックがされているnotEmptyをまず確認。_check前の事前確認では問題なし。
public static function notEmpty($check) { if (is_array($check)) { extract(self::_defaults($check)); } if (empty($check) && $check != '0') { return false; } return self::_check($check, '/[^\s]+/m'); }
2. /lib/Cake/Utility/Validation.phpの_check。
正規表現で値をチェックする_check。
protected static function _check($check, $regex) { if (is_string($regex) && preg_match($regex, $check)) { return true; } return false; }
ここでエラー。trueの場合成功するのも謎なんですが、とりあえずfalseになる箇所は発見。
3. テストコードで調べる
簡単なコードを作り、$tmpValueの中を変化させて確認。
$tmpValue = false; if (preg_match('/[^\s]+/m', $tmpValue)) { echo "OK"; } else { echo "NG"; } ...NG
$tmpValue = 0; if (preg_match('/[^\s]+/m', $tmpValue)) { echo "OK"; } else { echo "NG"; } ...OK
$tmpValue = ''; if (preg_match('/[^\s]+/m', $tmpValue)) { echo "OK"; } else { echo "NG"; } ...NG
$tmpValue = null; if (preg_match('/[^\s]+/m', $tmpValue)) { echo "OK"; } else { echo "NG"; } ...NG
$tmpValue = true; if (preg_match('/[^\s]+/m', $tmpValue)) { echo "OK"; } else { echo "NG"; } ...OK
いまいちピンときませんでした。が、preg_matchの定義を改めて見てみると、
int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
チェック前に、文字列変換してやってるのか?、、と、考え、、もうひとつ確認。
$tmpValue = true; var_dump(strval($tmpValue)); ... string(1) "1"
$tmpValue = false; var_dump(strval($tmpValue)); ... string(0) ""
これか。。これなのか。これだと、最初のテストコードで、、”の場合にNGになったのと合います。
【多分結論】
・CakePHPではtinyint項目をboolとして読み込む
・PHPでは、bool値を文字列変換すると、true => 1, false => ”と変換される
・notEmptyの項目は最終的にpreg_matchを使って判定され、その過程で、おそらく判定対象の値がstring変換される。結果、falseは上記とおり”に変換されてしまい、emptyとして判定される。。。
※罠1。tinyintをboolに勝手に変換してくれる。
※罠2。trueは1だから、falseは0とかなんとなく想定していたらfalseは”。(どうせならtrueも”にてくれれば、、わかりやすいのに)
【回避策】
回避方法としては、、DBから読み込んだ値をそのまま使わずに、0/1を設定してあげるか、、validateをはずしてあげるか、、ぐらいしか思いうかばず。
自分の知識の無さもふまえても、なんだかなーって結果でした。
追記(2014.04.15)
指摘されて、、確認したところ、CakePHPにはbooleanというvalidationルールがありました。
notEmptyではなく、booleanで定義したところ、、問題なく動作。
'boolean' => Array( 'rule' => Array('boolean'), 'message' => '指定してね', 'required' => true, )
結果、文字列でない値にnotEmptyを使っていた私が、、、知識不足ということでした。CakePHP様申し訳ございません、。