(10)
(0)
(0)
(0)
Total: 10 CakePHP Behaviorでバリデーション周りの効率化を図る
CakePHPでバリデーションを使う際、標準(CakePHP1.2)のままでも十分開発はできますが、大きめのプロジェクトや複数のプロジェクトでも汎用的かつ効率的に使えるようにできないかと検討しました。
バリデーション改善の目的
- エラーメッセージを日本語化し共通化・デフォルト化したい
- 独自バリデーションメソッドを共通化したい
- ルール配列を簡単に記述したい(項目が多いとソースが長くなり過ぎて見通しがよくない)
- バリデーションの前に自動で整形処理をしたい(半角英数変換やカタカナ変換など)
実装にあたって
バリデーションメソッドの共通化については、
cakephperさんの日記:よく使う独自バリデーションルールをプラグインのbehaviorにまとめる
に記載してありますように、プラグインのbehaviorで管理するほうが良いと思いますが、今回は弊社で過去に実装した通常のbehavior(BasicValidationBehaviorとします)を使った方法を紹介します。
BasicValidationBehaviorの主な機能
1.デフォルトのエラーメッセージを定義し、指定が無い場合はこれをメッセージとして出力する
2.独自バリデーションメソッドを追加することで、複数のモデルから利用可能にする
3.標準のバリデーションを疑似オーバーライドできるようにする
4.モデル側からのメソッド定義はシンプルな形式で記述可能にし、通常形式でも記述可能にする
5.バリデーション実行前に自動整形処理を実行する
実装準備:Behaviorの登録
ソースコードは最後に記述しています。
BasicValidationBehavior(basic_validation.php)を
/app/models/behaviors/basic_validation.php
に配置。
機能を実装したいモデルの宣言部分にBasicValidationBehaviorを追加。
-
<?php
-
class SampleModel extends Model{
-
:
実装方法:バリデーションルールの指定
仮にユーザー情報テーブルに以下のようなカラムとバリデーションを指定したいとします。
name => 必須 | 最大50文字
email => 必須 | メール形式 | 重複チェック | 最大200字
password => 必須 | 半角英数記号 | 最大20字 | 最小6字
re_password => 必須 | passwordと一致
sex => 必須 | 整数
birthday => 必須 | 日付形式(YYYY-MM-DD)
tel => 必須 | 電話番号形式
zip => 必須 | 郵便番号形式(3桁-4桁)
addr1 => 必須 | 最大200字
addr2 => 必須ではない | 最大200字
CakePHP1.2の従来の方法だと、
-
'message' => '50バイト以内で入力してください',
-
'required' => true,
-
'allowEmpty' => false,
-
),
-
),
-
'message' => 'メールアドレスが不正です',
-
'required' => true,
-
'allowEmpty' => false,
-
),
-
'message' => 'この値は既に登録されています',
-
'required' => true,
-
'allowEmpty' => false,
-
),
-
'message' => '200バイト以内で入力してください',
-
'required' => true,
-
'allowEmpty' => false,
-
),
-
// 以下省略
-
);
といった定義を延々と記述する必要があります。
これをBasicValidationBehaviorを利用することで、以下のように記述できます。
-
function loadValidate() {
-
'name' => 'required | maxLen[50]',
-
'email' => 'required | email | isUnique | maxLen[200]',
-
'password' => 'required | single | maxLen[20] | minLen[6]',
-
're_password' => 'required | confirm[password]',
-
'sex' => 'required | numeric | maxLen[1]',
-
'birthday' => 'required | ymd',
-
'tel' => 'required | phone | maxLen[13]',
-
'zip' => 'required | zip | maxLen[8]',
-
'pref' => 'required | maxLen[12]',
-
'addr1' => 'required | maxLen[200]',
-
'addr2' => 'maxLen[200]',
-
);
-
$this->setValidate($valid);
-
}
動作や展開したバリデーションルールはこちらのサンプルから確認できます。
サンプルのソースコードはこちらからダウンロードできます。
ポイント
・モデルに記述する loadValidate() メソッドはBasicValidationBehaviorからモデルのbeforeValidate()にフックされます
・setValidate() メソッドでmodel->validate に定義情報を展開します
・各バリデーションメソッドは valid_メソッド名 という形でbasic_validation.phpに登録しておきます
・デフォルトのエラーメッセージもbasic_validation.phpに記述しておきます
エラーメッセージのカスタマイズや通常形式でバリデーション設定を行う
setValidate() メソッドは内部でmodel->validate配列にバリデーション情報を展開します。
この後であれば通常形式と変わらずにmodel->validate配列を操作することができます。
-
function loadValidate() {
-
'name' => 'required | maxLen[50]',
-
'email' => 'required | email | isUnique | maxLen[200]',
-
// 省略
-
);
-
$this->setValidate($valid);
-
-
// エラーメッセージをデフォルト以外に変更する
-
$this->validate['email']['valid_email']['message'] = 'カスタムエラーメッセージ';
-
}
モデルに固有のバリデートメソッドを指定する
BasicValidationBehavior内のバリデートメソッドはメソッド名に 'valid_' というプレフィックスをつけるようにしています。
setValidate()での展開時にクラス内にプレフィックス付きのメソッドが無い場合は、そのままのメソッド名で展開しモデルのメソッドがコールされます。
-
// モデル内でカスタムメソッドを定義
-
function _custom_method ($data) {
-
if($v !== 'xxxx'){
-
return true;
-
}else{
-
// エラーメッセージを返す
-
return '禁止されている文字列です';
-
}
-
}
-
function loadValidate() {
-
// バリデーションにカスタムメソッドを追加
-
'name' => 'required | _custom_method | maxLen[50]',
-
// 省略
-
);
-
$this->setValidate($valid);
-
}
自動整形処理
BasicValidationBehaviorの_convert() メソッド内に整形処理を記述しています。
このメソッドはモデルのbeforeValidate() 時に実行され、メソッド名毎のswitch文となっています。
バリデーションメソッドを追加する際に整形処理が必要な場合はこの処理をカスタマイズします。
(ソースコードを参照ください)
注意
自動整形されるデータはmodelにセットされたデータ、model->dataが対象になります。
通常、ビューなどにセットされるデータはコントローラーのデータ、controller->dataになります。
通常の記述だと、バリデーション実行時やデータの保存時には自動整形後のデータで実行されるのに、表示は整形前の状態になります。
表示も同様に整形後のデータを利用するには、コントローラー側で以下のようにモデルのデータをマージする必要があります。
コントローラー側の記述
-
function add(){
-
$M =& $this->TestUser;
-
-
} else {
-
$M->set($this->data);
-
if($M->validates()) {
-
$M->save();
-
$this->Session->setFlash('情報を保存しました。');
-
return;
-
} else {
-
$this->Session->setFlash('入力に誤りがあります。内容を確認してください。');
-
}
-
// モデルのデータとコントローラーのデータをマージする
-
$this->data = am($this->data, $M->data);
-
}
-
$this->set('data', $this->data);
-
$this->render('add');
-
}
今後の課題
- エラーメッセージの多言語対応
- behaviorのプラグイン化
補足記事(09/06/23):CakePHP Behaviorでバリデーション周りの効率化を図る 応用編
ソースコード
/app/models/behaviors/basic_validation.php
ダウンロード
※ソース内のバリデーションメソッドはサンプルですので、適宜カスタマイズしてください
-
<?php
-
class BasicValidationBehavior extends ModelBehavior {
-
var $loaded = false;
-
var $autoConvert = true;
-
-
#########################################################################
-
/**
-
* エラーメッセージ
-
*/
-
#########################################################################
-
// 標準バリデーション
-
'alphaNumeric' => '半角英数字で入力してください',
-
'between' => '%s文字以上%2s文字以内の半角文字を入力してください',
-
'blank' => '空でなければなりません',
-
'cc' => 'クレジットカード番号として正しくありません',
-
'custom' => '入力値が正しくありません',
-
'date' => '日付形式で入力してください',
-
'decimal' => '小数点第%s位までの半角数字を入力してください。',
-
'email' => 'メールアドレスが不正です',
-
'equalTo' => '入力値が%sと一致しません',
-
'extension' => '拡張子が正しくありません',
-
'ip' => 'IPアドレス形式で入力してください',
-
'minLength' => '%sバイト文字以上で入力してください',
-
'maxLength' => '%sバイト文字以内で入力してください',
-
'money' => '入力値が正しくありません',
-
'numeric' => '半角数字で入力してください',
-
'phone' => '電話番号形式で入力してください',
-
'postal' => '郵便番号形式で入力してください',
-
'range' => '%sより大きく%sより小さい半角数字を入力してください',
-
'url' => 'URL形式で入力してください',
-
'isUnique' => 'この値は既に登録済です',
-
'inList' => '入力値が正しくありません',
-
'time' => '時:分 形式で入力してください',
-
-
// 拡張バリデーション
-
'valid_required' => '入力してください',
-
'valid_maxLen' => '入力された文字数が制限を越えています(最大%s文字)',
-
'valid_minLen' => '入力された文字数が制限未満です(最小%s文字)',
-
'valid_equalLen' => '入力された文字数が正しくありません(%s文字で入力してください)',
-
'valid_phone' => '電話番号形式で入力してください',
-
'valid_zip' => '郵便番号形式(3桁-4桁)で入力してください',
-
'valid_zen' => '全角以外の文字が含まれています',
-
'valid_kana' => 'カタカナ以外の文字が含まれています',
-
'valid_hirakana' => 'ひらかな以外の文字が含まれています',
-
'valid_single' => '半角以外の文字が含まれています',
-
'valid_confirm' => '入力内容が一致しません',
-
'valid_email' => 'メールアドレスが不正です',
-
'valid_emailMulti' => 'メールアドレスが不正です',
-
'valid_ymd' => '正しい日付形式で入力してください',
-
'valid_jis' => '環境依存文字・旧漢字はご利用頂けません',
-
);
-
-
-
#########################################################################
-
/**
-
* データ整形用にカラムとルールの対応を保存
-
*/
-
#########################################################################
-
function SetConvert(&$model, $col, $rule) {
-
$this->convert[][$col] = $rule;
-
}
-
-
#########################################################################
-
/**
-
* バリデーション定義毎のデータ整形
-
*/
-
#########################################################################
-
function convertData(&$model, $col, $rule) {
-
$before = '';
-
$after = '';
-
$before = $model->data[$model->name][$col];
-
$after = $model->data[$model->name][$col] = $this->_convert($before, $rule);
-
}
-
$before = $model->data[$col];
-
$after = $model->data[$col] = $this->_convert($model->data[$col], $rule);
-
}
-
}
-
function _convert($v, $rule){
-
if($v == '') {
-
return $v;
-
}
-
switch($rule){
-
case 'alphaNumeric':
-
case 'email':
-
case 'date':
-
case 'email':
-
case 'ip':
-
case 'numeric':
-
case 'url':
-
case 'time':
-
case 'valid_single':
-
case 'valid_email':
-
case 'valid_emailMulti':
-
// 1バイト文字
-
break;
-
-
case 'valid_zen':
-
// 全角文字
-
break;
-
-
case 'valid_kana':
-
// 全角カタカナ文字
-
break;
-
-
case 'valid_hirakana':
-
// 全角ひらかな文字
-
break;
-
case 'valid_phone':
-
break;
-
case 'valid_zip':
-
}
-
break;
-
case 'valid_ymd':
-
break;
-
}
-
return $v;
-
}
-
-
#########################################################################
-
/**
-
* 必須項目の出力文字列設定
-
*/
-
#########################################################################
-
var $require_string = '';
-
function setRequireString(&$model, $str) {
-
$this->require_string = $str;
-
}
-
-
#########################################################################
-
/**
-
* 必須項目の場合は設定文字列を返す
-
*/
-
#########################################################################
-
function getRequireString(&$model, $col) {
-
// バリデーション定義の読み込み
-
$model->loadValidate();
-
$this->loaded = TRUE;
-
}
-
if($this->_getArrayValueRecursive('required', $model->validate[$col])){
-
return $this->require_string;
-
}
-
return '';
-
}
-
-
#########################################################################
-
/**
-
* 配列にキーが存在していればその値を返す
-
*/
-
#########################################################################
-
function _getArrayValueRecursive($strKey, $arrArray) {
-
$ret = false;
-
$ret = $key === $strKey ? $value : false;
-
$ret = $this->_getArrayValueRecursive($strKey, $value);
-
}
-
if ($ret) break;
-
}
-
return $ret;
-
}
-
-
#########################################################################
-
/**
-
* バリデーションの実行前に初期化を行う
-
*/
-
#########################################################################
-
function beforeValidate(&$model, $options = NULL) {
-
// バリデーション定義の読み込み
-
$model->loadValidate();
-
$this->loaded = TRUE;
-
}
-
-
// 整形処理実行
-
if($this->autoConvert){
-
foreach($this->convert as $i => $arr){
-
$this->convertData($model, $col, $rule);
-
}
-
}
-
return TRUE;
-
}
-
-
#########################################################################
-
/**
-
* バリデーション配列を引数の共通項のみとする
-
*/
-
#########################################################################
-
function intersectValidate(&$model, $arg) {
-
$model->loadValidate();
-
$this->loaded = TRUE;
-
}
-
// for 'colA,colB'
-
}else{
-
// for normal $data[model][colA]="xxx"
-
$okVali = $arg[$model->name];
-
}else{
-
$cnt = Set::countDim($arg);
-
// for saveAll $data[23][colA]="xxx"
-
if($cnt == 2){
-
}else{
-
// for columnArray array('colA', 'colB')
-
}else{
-
// for columnKeyArray array('colA'=>"xxx", 'colB'=>"yyy")
-
$okVali = $arg;
-
}
-
}
-
}
-
}
-
$model->validate = array_intersect_key($model->validate, $okVali);
-
}
-
-
#########################################################################
-
/**
-
* バリデーションの展開
-
*/
-
#########################################################################
-
function setValidate(&$model, $arr) {
-
foreach($arr as $col => $validate){
-
// 必須項目判定
-
$allowEmpty = FALSE;
-
$required = TRUE;
-
// 必須メッセージは優先表示(最後に再設定)
-
$vali_arr[] = 'required';
-
}else{
-
$allowEmpty = TRUE;
-
$required = FALSE;
-
}
-
-
foreach($vali_arr as $rule){
-
$param = "";
-
$rule = $match[1];
-
$param = $match[2];
-
}
-
$rule = 'valid_' . $rule;
-
}
-
$msg = '';
-
$msg = $this->validateMessage[$rule];
-
}else{
-
$msg = $rule;
-
}
-
}
-
}
-
-
-
$my_rule = $rule;
-
if($param != ''){
-
}
-
}
-
'rule' => $my_rule,
-
'message' => $msg,
-
'required' => $required,
-
'allowEmpty' => $allowEmpty,
-
);
-
// 整形セット
-
$this->SetConvert($model, $col, $rule);
-
}
-
}
-
$this->loaded = TRUE;
-
}
-
-
#########################################################################
-
/**
-
* メッセージのカスタマイズ
-
*/
-
#########################################################################
-
function setMessage(&$model, $col, $rule, $message) {
-
$rule = 'valid_' . $rule;
-
}
-
$model->validate[$col][$rule]['message'] = $message;
-
}
-
}
-
-
#########################################################################
-
/**
-
* バリデーションのクリア
-
*/
-
#########################################################################
-
function clearValidate(&$model) {
-
$this->loaded = TRUE;
-
}
-
-
#########################################################################
-
/**
-
* 必須項目チェック
-
*/
-
#########################################################################
-
function valid_required(&$model, &$data) {
-
-
// 配列の場合(チェックボックス用)
-
foreach($v as $arr_v){
-
if($arr_v){
-
return TRUE;
-
}
-
}
-
return FALSE;
-
}
-
-
if($v === ''){
-
return FALSE;
-
}else{
-
return TRUE;
-
}
-
}
-
-
#########################################################################
-
/**
-
* 最大文字数チェック
-
*/
-
#########################################################################
-
function valid_maxLen(&$model, &$data, $len) {
-
return FALSE;
-
}else{
-
return TRUE;
-
}
-
}
-
-
#########################################################################
-
/**
-
* 最少文字数チェック
-
*/
-
#########################################################################
-
function valid_minLen(&$model, &$data, $len) {
-
return FALSE;
-
}else{
-
return TRUE;
-
}
-
}
-
#########################################################################
-
/**
-
* 文字数一致チェック
-
*/
-
#########################################################################
-
function valid_equalLen(&$model, &$data, $len) {
-
return FALSE;
-
}else{
-
return TRUE;
-
}
-
}
-
-
#########################################################################
-
/**
-
* 電話番号チェック
-
*/
-
#########################################################################
-
function valid_phone(&$model, &$data) {
-
if($v === '') return TRUE;
-
-
return TRUE;
-
}else{
-
return FALSE;
-
}
-
}
-
-
#########################################################################
-
/**
-
* 郵便番号チェック
-
*/
-
#########################################################################
-
function valid_zip(&$model, &$data) {
-
if($v === '') return TRUE;
-
-
return TRUE;
-
}else{
-
return FALSE;
-
}
-
}
-
-
#########################################################################
-
/**
-
* 全角チェック
-
*/
-
#########################################################################
-
function valid_zen(&$model, &$data) {
-
if($v === '') return TRUE;
-
return TRUE;
-
}
-
return FALSE;
-
}
-
-
#########################################################################
-
/**
-
* カタカナチェック
-
*/
-
#########################################################################
-
function valid_kana(&$model, &$data) {
-
if($v === '') return TRUE;
-
-
return TRUE;
-
}
-
return FALSE;
-
}
-
-
#########################################################################
-
/**
-
* ひらかなチェック
-
*/
-
#########################################################################
-
function valid_hirakana(&$model, &$data) {
-
if($v === '') return TRUE;
-
return TRUE;
-
}
-
return FALSE;
-
}
-
-
#########################################################################
-
/**
-
* 環境依存文字・旧漢字などJISに変換できない文字チェック
-
*/
-
#########################################################################
-
function valid_jis(&$model, &$data) {
-
if($v === '') return TRUE;
-
$myEnc = Configure::read('App.encoding');
-
// 対象外
-
if ($v == $v2) {
-
return TRUE;
-
}
-
return FALSE;
-
}
-
-
#########################################################################
-
/**
-
* 1バイト文字列チェック
-
*/
-
#########################################################################
-
function valid_single(&$model, &$data) {
-
if($v === '') return TRUE;
-
-
return FALSE;
-
}
-
return TRUE;
-
}
-
-
#########################################################################
-
/**
-
* 確認入力用
-
*/
-
#########################################################################
-
function valid_confirm(&$model, &$data, $col ) {
-
return FALSE;
-
}
-
if($v === $model->data[$model->name][$col]){
-
return TRUE;
-
}
-
return FALSE;
-
}
-
-
#########################################################################
-
/**
-
* メールアドレス妥当性チェック
-
*/
-
#########################################################################
-
function valid_email(&$model, &$data ) {
-
if($v === '') return TRUE;
-
-
$__pattern = '(?:[a-z0-9][-a-z0-9]*\.)*(?:[a-z0-9][-a-z0-9]{0,62})\.(?:(?:[a-z]{2}\.)?[a-z]{2,4}|museum|travel)';
-
$__regex = '/^[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@' . $__pattern . '$/i';
-
-
return true;
-
} else {
-
return false;
-
}
-
}
-
-
#########################################################################
-
/**
-
* メールアドレス妥当性チェック(複数カンマ区切り)
-
*/
-
#########################################################################
-
function valid_emailMulti(&$model, &$data ) {
-
if($v === '') return TRUE;
-
-
foreach($mails as $m){
-
if(!$this->valid_email($model, $myData)){
-
return FALSE;
-
}
-
}
-
return TRUE;
-
}
-
-
#########################################################################
-
/**
-
* YYYY-MM-DD形式かどうか
-
*/
-
#########################################################################
-
function valid_ymd(&$model, &$data, $col ) {
-
if($v === '') return TRUE;
-
-
$yyyy = $tmp[0];
-
$mm = $tmp[1];
-
$dd = $tmp[2];
-
}
-
}
関連するその他の記事
Comments
Leave a Reply