$_GET
$_POST
などの要素の 未定義 や 想定外の型 の検出
「$_GET, $_POSTなどを受け取る際の処理」 で詳しく解説しています。
a=foo&b[]=bar
$a = filter_input(INPUT_GET, 'a'); // "foo"
$b = filter_input(INPUT_GET, 'b'); // false
$c = filter_input(INPUT_GET, 'c'); // null
$a = (string)filter_input(INPUT_GET, 'a'); // "foo"
$b = (string)filter_input(INPUT_GET, 'b'); // ""
$c = (string)filter_input(INPUT_GET, 'c'); // ""
ここから先はこういったチェックを行った後の状態であると仮定して,値の形式に関するバリデーションのみに着目します。
/* 1. 未定義や想定外の型の検出 */
$email = (string)filter_input(INPUT_POST, 'email');
/* 2. 値の形式に関するバリデーション */
if (false !== filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "正しいEメールアドレスです: $email\n";
} else {
echo "Eメールアドレスの形式が不正です: $email\n";
}
なお,この例において,無効なメールアドレスを変数 $email
に格納しなくてもよい場合,
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if (is_string($email)) {
echo "正しいEメールアドレスです: $email\n";
} else {
echo "Eメールアドレスの形式が不正です\n";
}
としてまとめてしまっても構いません。
※ 送信されていない場合にnull
になるので false !==
の比較は不適当です。
|関数|フィルタリング対象| 成功時の返り値 | 失敗時の返り値| インデックス
未定義時の返り値|
|:----:|:----:|:----:|:----:|:----:|:----:|
|filter_var| 自分で渡す値 | 文字列や整数など
(フィルタによる) | false
| - |
|filter_input| $_GET
$_POST
などの
インデックス| 文字列や整数など
(フィルタによる) | false
| null
|
URL
2通りの実装パターンを示します。多くの場合では B を選択しておけばよいでしょう。
function is_valid_url($url)
{
return false !== filter_var($url, FILTER_VALIDATE_URL);
}
function is_valid_url($url)
{
return false !== filter_var($url, FILTER_VALIDATE_URL) && preg_match('@^https?+://@i', $url);
}
A | B | |
---|---|---|
http:// https:// |
○ | ○ |
mailto: | ○ | × |
その他のスキーム:// | ○ | × |
その他のスキーム: | × | × |
Eメールアドレス
3通りの実装パターンを示します。不変ではないIPアドレスで登録されたら後々いろいろ面倒なことが起きそうなので, C を選択しておくのが無難だと思います。
※ 追記により良い結論を掲載しているので必ずご覧ください
function is_valid_email($email)
{
return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
}
function is_valid_email($email)
{
switch (true) {
case !is_string($email):
case false === $pos = strrpos($email, '@'):
case false === filter_var(substr($email, $pos + 2, -1), FILTER_VALIDATE_IP):
case false === filter_var(substr_replace($email, '0.0.0.0', $pos + 2, -1), FILTER_VALIDATE_EMAIL):
return false;
default:
return true;
}
}
function is_valid_email($email)
{
return false !== filter_var($email, FILTER_VALIDATE_EMAIL) && ']' !== substr($email, -1);
}
A | B | C | |
---|---|---|---|
ローカル部@ドメイン | ○ | ○ | ○ |
ローカル部@[ IPv4 ] | ○ | ○ | × |
ローカル部@[ IPv6 ] | × | ○ | × |
「PHPしか書けないザコがメールアドレス正規表現でガチ勢に挑んでみた」 でさんざん議論しましたが,PHP純正のフィルタリング関数をうまく活用するのが最も賢明な方法かな・・・という思いで執筆させていただきました。ええ,もちろん日本の携帯キャリアのRFCガン無視実装は弾いてますけどね・・・
2015.8.11 追記
- 最近ではメールアドレスの国際化が進み,ローカル部のバリデーションに関してはほぼ無意味になって来ています。しかし現状それを考慮しているWebサービスが少ないということもあって,そういったメールアドレスを使用している人は非常に少ないはずなので,周囲の様子を見ながら対応する…ぐらいでもいいのかもしれません。
- ドメインも同様ですが,DNSレコードをチェックすることによってドメインの存在を確認出来ます。
これらを踏まえて,以下に@xRxさんのコードに少し手を加えたものを示します。Cをベースにしています。第2引数にtrue
を渡すとDNSレコードをチェックします。
function is_valid_email($email, $check_dns = false)
{
switch (true) {
case false === filter_var($email, FILTER_VALIDATE_EMAIL):
case !preg_match('/@([^@\[]++)\z/', $email, $m):
return false;
case !$check_dns:
case checkdnsrr($m[1], 'MX'):
case checkdnsrr($m[1], 'A'):
case checkdnsrr($m[1], 'AAAA'):
return true;
default:
return false;
}
}
もしUTF-8文字列にも対応したい場合は,egulias/EmailValidatorではなくsymfony/Validatorを使うようにしてください。ソースコードを見る限り前者は**MX
レコードのチェックしか行っていません**。後者は内部で前者を利用しているようですが,わざわざこのためだけに修正を行っています。
- egulias/EmailValidator src/Egulias/EmailValidator/EmailValidator.php #L149
- symfony/Validator Constraints/EmailValidator.php #L155
Symfonyのコンポーネントを使ったことがないのでサンプル紹介は割愛します。
2015.8.11 追記
**PHP7.1.0から,ネイティブでUTF-8文字列を含むEメールアドレスのバリデーションに対応しました。**このバージョン以降では最も適切な選択肢と言えそうです。
function is_valid_email($email, $check_dns = false)
{
switch (true) {
case false === filter_var($email, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE):
case !preg_match('/@([^@\[]++)\z/', $email, $m):
return false;
case !$check_dns:
case checkdnsrr($m[1], 'MX'):
case checkdnsrr($m[1], 'A'):
case checkdnsrr($m[1], 'AAAA'):
return true;
default:
return false;
}
}
注意点として,ドメイン部はPunycode表記になっている必要があります。例えばGigazineの「Gmailが日本語など非アルファベット文字を含むメールアドレスとの送受信に対応」では武@メール.グーグル
が例に挙げられていますが,これは**武@xn--4dkua4c.xn--qcka1pmc
**にエンコードされている必要があります。
郵便番号や住所
- 日本郵便のHP から全国一括のCSVデータをダウンロードする。
- MySQLやSQLiteにインポートして使う。
SELECT
一発で判定出来るデータがあるので,下手に正規表現を書こうとするよりもSQLを使うべきであると言えるでしょう。
電話番号
総務省のHP のデータをもとに,目的にあった関数を自分で作成します。正規表現一発でやろうとしても,正規表現が長すぎて失敗することが多いので注意してください。必要があればMySQLやSQLiteにインポートして使います。
function is_valid_phone_number($number)
{
return is_string($number) && preg_match('/\A\d{2,4}+-\d{2,4}+-\d{4}\z/', $number);
}
真面目にやろうとすると かなり面倒 です。
整数
全てを整数型に強制する
整数へのキャスト
$id = (int)$id;
/**
* "1" => 10進数の "1" => 1
* "01" => 10進数の "01" => 1
* "010" => 10進数の "010" => 10
* "0x1A" => 10進数の "0" => 0
*/
$id = min(max((int)$id, 0), 9);
intval 関数
$id = intval($id); // 10がデフォルト
/**
* "1" => 10進数の "1" => 1
* "01" => 10進数の "01" => 1
* "010" => 10進数の "010" => 10
* "0x1A" => 10進数の "0" => 0
*/
$id = intval($id, 2);
/**
* "1" => 2進数の "1" => 1
* "01" => 2進数の "01" => 1
* "010" => 2進数の "010" => 2
* "0x1A" => 2進数の "0" => 0
*/
$id = intval($id, 36);
/**
* "1" => 36進数の "1" => 1
* "01" => 36進数の "01" => 1
* "010" => 36進数の "010" => 36
* "0x1A" => 36進数の "0X1A" => 42814
*/
$id = intval($id, 8);
/**
* "1" => 8進数の "1" => 1
* "01" => 8進数の "1" => 1
* "010" => 8進数の "10" => 8
* "0x1A" => 8進数の "" => 0
*/
$id = intval($id, 16);
/**
* "1" => 16進数の "1" => 1
* "01" => 16進数の "01" => 1
* "010" => 16進数の "010" => 16
* "0x1A" => 16進数の "1A" => 26
*/
$id = intval($id, 0); // 0は自動判定を意味する
/**
* "1" => 10進数の "1" => 1
* "01" => 8進数の "1" => 1
* "010" => 8進数の "10" => 8
* "0x1A" => 16進数の "1A" => 26
*/
正しい場合は整数型に変換し,誤っている場合は false
とする
filter_var 関数
$id = filter_var($id, FILTER_VALIDATE_INT);
/**
* "1" => 10進数の "1" => 1
* "01" => false
* "010" => false
* "0x1A" => false
*/
$options = ['min_range' => 0, 'max_range' => 9];
$id = filter_var($id, FILTER_VALIDATE_INT, compact('options'));
$id = filter_var($id, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_OCTAL | FILTER_FLAG_ALLOW_HEX);
/**
* "1" => 10進数の "1" => 1
* "01" => 8進数の "1" => 1
* "010" => 8進数の "10" => 8
* "0x1A" => 16進数の "1A" => 26
*/