6月 05
14.Validate
「好きな物」のcheckboxまわりの動きがおかしいので直しますが、それにさきだってモデルに記述する validate を設定しようと思います。
まあ、 validate ですけど。
本家サイトのドキュメントを見たら、うわーっつと思うよね。
こんなにたくさんあって、一体これはどれを使ったらいいんだ?
と言う訳でちょっと長いですが、いろいろ考察した結果を書きます。
1. allowEmpty 編
例えば code というフィールドに対して以下のような validate を設定したとします。
var $validate = array( "code" => array( "rule" => 'alphaNumeric', "message" => "コードを入力してください", ), );
これは英数字以外だとエラーになり、さらに値が空でもエラーになります。
そこで以下のように ‘allowEmpty’ => true, を追加すると、
var $validate = array( "code" => array( "rule" => 'alphaNumeric', "message" => "コードを入力してください", 'allowEmpty' => true, ), );
値が空の場合は ” でinsertされますが、もし値が入っていてそれが英数字以外だとエラーになります。
ちなみに、フィールドがint型の時に以下のようにして、何も入力しないと、
var $validate = array( "number" => array( 'rule' => 'numeric', 'message'=>'数字を入力してください', 'allowEmpty' => true, ), );
フィールド number には NULL が入ります。
つまりCore Validation Rules(組み込みのルール)だけを書くと、何も入力しない時にも適用されてしまうということですね。
2. notEmpty 編
では、 ‘notEmpty’ というルールを使う時はあるのだろうか?
例えば、以下のようにすると、何も入力しなかった時は’コードを入力してください’とメッセージが出る。
var $validate = array( "code" => array( "rule" => 'alphaNumeric', "message" => "コードを入力してください", ), );
ただこれは、「ああああ」などと入力しても同じ、’コードを入力してください’とメッセージが出てしまう。
これはやはり「ああああ」と入れた時は’コードは半角英数字で入力してください’と出したい。
という時に、 ‘notEmpty’ を使います。
var $validate = array( "code" => array( 'rule1' => array( 'rule' => 'alphaNumeric', 'message'=>'コードは半角英数字で入力してください', ), 'rule2' => array( 'rule' => 'notEmpty', 'message'=>'コードを入力してください', ), ), );
エラーメッセージは下から順に出ます。
3. required 編
次に以下のような、 ‘required’ を入れたvalidateを設定したとします。
var $validate = array( "number" => array( 'rule' => 'numeric', 'message'=>'数字を入力してください', 'required' => true, ), );
これでview側にフィールド number のフォーム
echo $form->input('number');
を書くのを忘れたとします。
すると、DBには保存されないです。保存されないがエラーメッセージは出ません。
(numberのフォームがないんだから当たり前)
もちろん、以下のように ‘required’ => true, を取ってしまえば、フォームに number が存在しなくてもDBに保存できます。( ” か NULL が入る。)
var $validate = array( "number" => array( 'rule' => 'numeric', 'message'=>'数字を入力してください', ), );
ということでこれを忘れたら本当にやばい、という大事なフィールドには ‘required’ => true, を入れておけばいいだろう。
まあ、そんな大事なものは普通忘れるはずがないので、使うかどうか・・・という感じですね。
4. maxLength 編
次に、普通のテキストフィールドに ‘maxLength’ は要るのか?という話。
まず、DBでvarchar(50)とかに設定してあるとする。それを元にbakeすると、生成されたviewのフォームには、自動的にmaxlengthが指定されます。
<input name="data[Member][title]" type="text" maxlength="50" value="" id="MemberTitle" />
このフォームでは今のIEではどうやっても50文字以上入らないです。(コピペしても)
そこで、フォームHTMLをねつ造して、postしてみます。
すると、
SQL Error: 1406: Data too long for column ‘title’ at row 1
とエラー終了します。
このエラーメッセージはデバッグモード0にすれば表示されません。
それならいいんじゃないの? わざわざ model の方で、 ‘maxLength’ で validate しなくても。と思います。
また、きっちり長さの決まっているパスワードみたいなものは、’custom’ でやったほうがいいです。
"rule" => array("custom", '/^[a-zA-Z0-9\_\-]{6,10}$/i'),
では日本語文字の ‘maxLength’ はどうでしょうか?
MySQLの場合で確認しましたが、正しくテーブルが utf8 で作られていれば、 varchar(50) は全角文字でも50文字まで入ります。
同じくIEの maxlength=”50″ も全角文字でも50文字まで入ります。
つまり全角も半角も関係ないということですね。
ただ、あえて model 側の validate で ‘maxLength’ 指定してエラーメッセージを出したい、となると問題が発生します。
例えばテキストエリアに1000文字までという入力制限をつけたい、なんてことはよくあることだろう。
"rule" => array('maxLength', '1000'),
こう書いた場合、半角と全角で同じ動きになるのだろうか?
これはならないです。
これは恐らく判定に strlen を使ってるんだろうから、それを mb_strlen にしてやらなきゃいけない。
5. 参照id 編
次に、 user_id とか type_id とか、他のテーブルのidを参照するセレクトの値などに validate は必要でしょうか?
あるいは birthday などの日付データにも。。。
これもねつ造フォームを使って実験してみると、何も validate を指定しないと、int型で指定してある user_id のようなフィールドに対しても、入力された値をそのままinsertしようとすることが分かります。
当然、値は入らないのでint型なら0、date型なら0000-00-00になります。
(何故MySQLはエラーにならないのか?)
変なデータがDBに入るのもいやだし、一応入れておいた方がよさそうです。
ただし、ルールの ‘numeric’ を使うと小数もOKになってしまう。int型に小数を insert すると、DBに入った時点で整数になる。これもMySQLの挙動のおかしな話なので ‘custom’ でルールを作ってやった方がよさそうです。
5. HABTM multiple 編
最後に、hasAndBelongsToMany で関連づけてチェックボックスなどで複数選択させるデータの validate はどうやればよいのでしょうか?
複数選択なんだけど一つ以上は選択させたい、なんてことは実によくある話です。
こういう場合に組み込みルールの ‘multiple’ を使うのかな? と思って固まりました。
「一体そのルールはどこに書けばいいんだ??」
今回の例で言うと、会員 members がいて、その「好きな物」 favorites があって、それを結びつける中間テーブル members_favorites があって、となっている訳ですが、当然選択した値はその中間テーブルに入る訳ですが、その中間テーブルのモデルにルールを書くとはとても思えないのです。
じゃあ Favorite モデルだろうか?
だとしてもそんなところにルールを書いたってそれが実行されるとはとても思えない。
どうすりゃいいんだ、ということで丸一日悩んだ(笑)。
一つには解決策としてコントローラ側にvalidates()を入れる、という手がある。
$this->Member->Favorite->set($this->data);
if($this->Member->Favorite->validates()) {
$this->Member->save($this->data);
}
で、validate のルールは Favorite のモデルの方に書いておく。
これは動く。
確かに動くんだが、先に Favorite の方が実行されて次に Member の方が実行されるので、チェックボックス部分のエラーメッセージだけが先に出て、それを埋めると他のエラーメッセージが出る、という動きになり実にみっともない。
同時に複数のモデルの validate を実行させる方法はないのか?!
とさんざん悩んだがそれはあきらめました(^^;
発想の転換です。
あくまでもこれは Member を登録してるんだから、チェックボックスの選択値も今は Member として処理すればいいんじゃないか。
つまりviewでこうだ。
今こうなってるやつを、
echo $form->input('Favorite', array('multiple'=>'checkbox'));
こうする。
echo $form->input('favorites', array('multiple'=>'checkbox'));
するとなんと!
<input type="checkbox" name="data[Member][favorites][]" value="2" id="MemberFavorites2" />
こういうソースが生成されるのである。
これはモデル Member だ。
ということで Member の方にルールを書く。
'favorites'=>array(
'rule'=>array('multiple', array('min' => 1, 'max' => 3)),
'message' => '好きな物は一つ以上選択してください'
),
これは動く。実によく動く。
やっとここで念願の ‘multiple’ が使えた訳です。
