6月 05

14.Validate

go @ 4:30 AM

「好きな物」の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’ が使えた訳です。

>>次のページ「Validate続き」へ

Comments are closed.

here comes