#Codeigniter3.0.3のバリデーションルールまとめ
遅れて申し訳ありません。
申し訳ついでに、面白くないテーマですが、バリデーションルールについてまとめました。
基本ルール
「/system/libraries/Form_validation.php」
内にある、
「required」以下のメソッドに内容についてソース読みましょう、読みましょう。
調査対象
- Codeigniter3.0.3
基礎知識
バリデーションルールって、こんな感じで記述すると思います。
// 例1:一番単純な書き方
// $_POST['first_name'] = 'tomcat';
$this->form_validation->set_rules('first_name', 'lang:first_name', 'max_length[100]');
バリデーションルール名がそのままメソッド名となります。
フォームに入力された値が第一引数として渡ります。
[]に囲まれた部分(例1での100)が、バリデーションルールの第二引数として渡ります。
// 例1に対応したバリデーションルール
// ここには $str = 'tomcat'; $val = '100'; が来ます。
public function max_length($str, $val)
{
if ( ! is_numeric($val))
{
return FALSE;
}
return ($val >= mb_strlen($str));
}
本編
required
public function required($str)
{
return is_array($str) ? (bool) count($str) : (trim($str) !== '');
}
- 要素があるかどうかをチェック
- 配列対応
regex_match
public function regex_match($str, $regex)
{
return (bool) preg_match($regex, $str);
}
正規表現を使用した、文字チェック
詳しくはPHPマニュアル参照のこと
http://php.net/manual/ja/function.preg-match.php
matches
public function matches($str, $field)
{
return isset($this->_field_data[$field], $this->_field_data[$field]['postdata'])
? ($str === $this->_field_data[$field]['postdata'])
: FALSE;
}
他のカラムと一致するかの確認
パスワードの確認、メールアドレスの確認なのでよく使う
differs
public function differs($str, $field)
{
return ! (isset($this->_field_data[$field]) && $this->_field_data[$field]['postdata'] === $str);
}
他のカラムと一致しない
パスワードの再設定とか?
matchesの反対
is_unique
public function is_unique($str, $field)
{
sscanf($field, '%[^.].%[^.]', $table, $field);
return isset($this->CI->db)
? ($this->CI->db->limit(1)->get_where($table, array($field => $str))->num_rows() === 0)
: FALSE;
}
特定のテーブルの特定のカラムで、ユニークかどうか調べる
自分自身のレコードを除外できない、削除済レコードの除外等の小賢しい処理を挟めないので、実用性は・・・
min_length
public function min_length($str, $val)
{
if ( ! is_numeric($val))
{
return FALSE;
}
return ($val <= mb_strlen($str));
}
最低文字数
パスワードの設定などに
max_length
public function max_length($str, $val)
{
if ( ! is_numeric($val))
{
return FALSE;
}
return ($val >= mb_strlen($str));
}
最大文字数
input系ではほぼ必須?
exact_length
public function exact_length($str, $val)
{
if ( ! is_numeric($val))
{
return FALSE;
}
return (mb_strlen($str) === (int) $val);
}
指定文字数かどうか
郵便番号とか?長さが決まっているもの
valid_url
public function valid_url($str)
{
if (empty($str))
{
return FALSE;
}
elseif (preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches))
{
if (empty($matches[2]))
{
return FALSE;
}
elseif ( ! in_array($matches[1], array('http', 'https'), TRUE))
{
return FALSE;
}
$str = $matches[2];
}
$str = 'http://'.$str;
// There's a bug affecting PHP 5.2.13, 5.3.2 that considers the
// underscore to be a valid hostname character instead of a dash.
// Reference: https://bugs.php.net/bug.php?id=51192
if (version_compare(PHP_VERSION, '5.2.13', '==') OR version_compare(PHP_VERSION, '5.3.2', '=='))
{
sscanf($str, 'http://%[^/]', $host);
$str = substr_replace($str, strtr($host, array('_' => '-', '-' => '_')), 7, strlen($host));
}
return (filter_var($str, FILTER_VALIDATE_URL) !== FALSE);
}
URLとして正しいかどうかチェックする
ここに来て、ちょっと面白いルールが出てきました。
まず、今回この記事を書くにあたって、注目した部分が以下の一文です。
preg_match('/^(?:([^:]*)\:)?\/\/(.+)$/', $str, $matches)
勉強不足で初めて知ったのですが、ここの正規表現の「(?:〜〜)」の部分はキャプチャしない
という意味らしいです。(キャプチャとはここでいう$matches
に入れないと考えていただければ差し支えありません。)
この場合ですと、外側の丸カッコは必要ないだけではなく、丸カッコがネストしています。
そこでキャプチャしないという指定をしてあげることで、$matches
の中にどういう順番で文字列が格納されるのかとてもわかりやすくなります。
さて、その後の記述ですが、PHPのバージョンについてコメント付きの条件式が出てきますが、3.0ではPHP5.4以上なのでそのうち外されるんでしょうか?
何にせよ、歴史を感じる記述です。(2.0の頃、ここに関する面白いやりとりが公式リポジトリのissueでありましたが、今ログが見つかりませんでした・・・残念)
valid_email
public function valid_email($str)
{
if (function_exists('idn_to_ascii') && $atpos = strpos($str, '@'))
{
$str = substr($str, 0, ++$atpos).idn_to_ascii(substr($str, $atpos));
}
return (bool) filter_var($str, FILTER_VALIDATE_EMAIL);
}
メールアドレスとして正しいかチェックする
もうひとつ面白いルールです。
idn_to_ascii
とは、誤解を恐れず言うとマルチバイト対応です。
つまり、日本語ドメインなどのメールアドレスをちゃんと扱えるということになります。
日本語ドメインメールアドレスはGoogleが対応すると告知した以上、今後ある程度は普及すると考えられます。
https://googleblog.blogspot.jp/2014/08/a-first-step-toward-more-global-email.html
valid_emails
public function valid_emails($str)
{
if (strpos($str, ',') === FALSE)
{
return $this->valid_email(trim($str));
}
foreach (explode(',', $str) as $email)
{
if (trim($email) !== '' && $this->valid_email(trim($email)) === FALSE)
{
return FALSE;
}
}
return TRUE;
}
カンマ区切りの複数のメールアドレスが正しいか、チェックする
カンマでぶった切って、valid_email
に投げてくれます。
valid_ip
public function valid_ip($ip, $which = '')
{
return $this->CI->input->valid_ip($ip, $which);
}
IPアドレスとして正しいかチェックする
処理の本体は/system/libraries/Input.php
にあります。
public function valid_ip($ip, $which = '')
{
$which = strtolower($which);
// First check if filter_var is available
if (is_callable('filter_var'))
{
switch ($which) {
case 'ipv4':
$flag = FILTER_FLAG_IPV4;
break;
case 'ipv6':
$flag = FILTER_FLAG_IPV6;
break;
default:
$flag = '';
break;
}
return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flag);
}
if ($which !== 'ipv6' && $which !== 'ipv4')
{
if (strpos($ip, ':') !== FALSE)
{
$which = 'ipv6';
}
elseif (strpos($ip, '.') !== FALSE)
{
$which = 'ipv4';
}
else
{
return FALSE;
}
}
$func = '_valid_'.$which;
return $this->$func($ip);
}
// --------------------------------------------------------------------
/**
* Validate IPv4 Address
*
* Updated version suggested by Geert De Deckere
*
* @access protected
* @param string
* @return bool
*/
protected function _valid_ipv4($ip)
{
$ip_segments = explode('.', $ip);
// Always 4 segments needed
if (count($ip_segments) !== 4)
{
return FALSE;
}
// IP can not start with 0
if ($ip_segments[0][0] == '0')
{
return FALSE;
}
// Check each segment
foreach ($ip_segments as $segment)
{
// IP segments must be digits and can not be
// longer than 3 digits or greater then 255
if ($segment == '' OR preg_match("/[^0-9]/", $segment) OR $segment > 255 OR strlen($segment) > 3)
{
return FALSE;
}
}
return TRUE;
}
// --------------------------------------------------------------------
/**
* Validate IPv6 Address
*
* @access protected
* @param string
* @return bool
*/
protected function _valid_ipv6($str)
{
// 8 groups, separated by :
// 0-ffff per group
// one set of consecutive 0 groups can be collapsed to ::
$groups = 8;
$collapsed = FALSE;
$chunks = array_filter(
preg_split('/(:{1,2})/', $str, NULL, PREG_SPLIT_DELIM_CAPTURE)
);
// Rule out easy nonsense
if (current($chunks) == ':' OR end($chunks) == ':')
{
return FALSE;
}
// PHP supports IPv4-mapped IPv6 addresses, so we'll expect those as well
if (strpos(end($chunks), '.') !== FALSE)
{
$ipv4 = array_pop($chunks);
if ( ! $this->_valid_ipv4($ipv4))
{
return FALSE;
}
$groups--;
}
while ($seg = array_pop($chunks))
{
if ($seg[0] == ':')
{
if (--$groups == 0)
{
return FALSE; // too many groups
}
if (strlen($seg) > 2)
{
return FALSE; // long separator
}
if ($seg == '::')
{
if ($collapsed)
{
return FALSE; // multiple collapsed
}
$collapsed = TRUE;
}
}
elseif (preg_match("/[^0-9a-f]/i", $seg) OR strlen($seg) > 4)
{
return FALSE; // invalid segment
}
}
return $collapsed OR $groups == 1;
}
filter_var
はここでも大活躍ですね。
alpha
public function alpha($str)
{
return ctype_alpha($str);
}
PHPのデフォルト関数に渡しているだけです。
シンプルでいいですね。
参考文献: http://php.net/manual/ja/function.ctype-alpha.php
alpha_numeric
public function alpha_numeric($str)
{
return ctype_alnum((string) $str);
}
こちらもPHPのデフォルト関数に渡しているだけです。
シンプルでいいですね。
参考文献: http://php.net/manual/ja/function.ctype-alnum.php