filter_var($value, FILTER_VALIDATE_EMAIL)は、値が正しいメールアドレスならその値を、そうでなければfalseを返します。フィルターを替えればほかの形式も確認できます:FILTER_VALIDATE_INT、FILTER_VALIDATE_FLOAT、FILTER_VALIDATE_URL、FILTER_VALIDATE_BOOL、FILTER_VALIDATE_IP。結果は常に=== falseで比較しましょう。
2つの結果が意外かもしれません。ada@exampleはドメインにドットがないので拒否され、' ada@example.com'は先頭の空白のせいで拒否されます。検証する前に入力をtrim()しましょう。
フォームからのメールアドレスを検証する
フォームの裏で同じ確認をします。実行してアドレスを入力し、Checkを押すと、入力が$_POSTに入った状態でブロックがもう一度実行されます。
FILTER_VALIDATE_EMAILが確認するのは形式だけです。nobody@example.comは通りますし、ドメインにタイプミスのあるアドレスも通ります。ASCII以外の文字は拒否されます。adä@example.comは@の前にそれらを許すFILTER_FLAG_EMAIL_UNICODEを付けたときだけ通り、exämple.comのようなドメインは、先にxn--の形に変換しない限りフラグを付けても失敗します。アドレスが使えるかを知る唯一の方法は、確認用のリンクを送ることです。
min_rangeとmax_range付きで整数を検証する
FILTER_VALIDATE_INTは正しい文字列をintに変え、それ以外はすべて拒否します。オプションの配列で範囲と既定値を加えられます。
前後の空白と先頭の+は受け付けられますが、先頭のゼロ('042')は受け付けられず、'42'の結果は文字列ではなく整数の42です。範囲外ならfalseで、defaultオプションがあればfalseの代わりに既定値が得られ、?page=のようなものに便利です(_POSTを参照)。0x1Aや0755を受け付けるには、FILTER_FLAG_ALLOW_HEXやFILTER_FLAG_ALLOW_OCTALを加えます。
0とfalseの罠
正しい'0'は整数の0を返し、0はfalseと評価されます。素のifはそれを失敗として扱ってしまいます。
同じことが、FILTER_VALIDATE_FLOAT('0.0')と、falseが正しい答えであるFILTER_VALIDATE_BOOLにも当てはまります。FILTER_NULL_ON_FAILUREはそのためにあります。
真偽値、浮動小数点数、URL、IPアドレス
FILTER_VALIDATE_BOOLはPHP 8での名前で、FILTER_VALIDATE_BOOLEANは同じフィルターです。FILTER_NULL_ON_FAILUREがなければ、'maybe'はfalseを返し、'off'と区別できなくなります。
正しいURLは安全なリンクではない
FILTER_VALIDATE_URLが確認するのは、文字列がURLの形をしているかどうかで、スキームは何でもかまいません。そこにはhrefに決して入れたくないスキームも含まれます。
javascript://comment%0Aalert(1)はFILTER_VALIDATE_URLを通り、%0Aが//commentを終わらせるので、リンクをクリックするとブラウザはそれをJavaScriptとして実行します。ユーザーのURLをページに出力する前に、parse_url()でスキームを確認しましょう。FILTER_FLAG_PATH_REQUIREDやFILTER_FLAG_QUERY_REQUIREDで部品を必須にすることもできます。
サニタイズフィルター
サニタイズフィルターは、はいかいいえを答える代わりに文字を取り除きます。入力の正規化には役立ちますが、出力しても安全にするためのものではありません。
1行目を見てください。Order #12-345 (x2)は、入力のどこにもなかった数値12-3452になり、3行目は誰も入力していないアドレスです。サニタイズするなら、そのあとで結果を検証しましょう。HTMLに出力するものには、出力時のhtmlspecialchars()が正しい道具です(FILTER_SANITIZE_FULL_SPECIAL_CHARSも同じエスケープをします)。FILTER_SANITIZE_STRINGはPHP 8.1以降非推奨です。代わりに何を使うかはhtmlspecialcharsを参照してください。
filter_var_arrayでフォーム全体を検証する
filter_var_array()はキーごとに1つのフィルターを適用し、求めたすべてのキーを持つ配列を返します。値はフィルター済みの値、検証に失敗したらfalse、キーがなければnullです。
roleはなくなっています。並べなかったキーは取り除かれるので、訪問者がフォームにrole=adminを加えてそれが保存されるのを防げます。countryは送られなかったのでnullです。Webサーバーでは、filter_input_array(INPUT_POST, $rules)がリクエストから直接読んで同じことをします。
よくある質問
PHPでメールアドレスを検証するには?
filter_var($email, FILTER_VALIDATE_EMAIL)を使います。正しければメールアドレスの文字列を、そうでなければfalseを返します:if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { /* invalid */ }。確認するのは形式だけなので、アドレスが存在するかを知るには確認メールを送ります。
PHPで文字列が整数かどうかを確認するには?
filter_var($s, FILTER_VALIDATE_INT)は、"42"、"-7"、" 42 "に対して整数を返し、"4.2"、"42abc"、"042"に対してfalseを返します。正しい"0"は0を返しfalseと評価されるので、!== falseで比較しましょう。
filter_varが0に対してfalseを返すのはなぜですか?
返していません。filter_var('0', FILTER_VALIDATE_INT)は整数の0を返します。バグは判定のif (filter_var(...))にあり、0をfalseと同じように扱ってしまいます。if (filter_var($v, FILTER_VALIDATE_INT) !== false)と書きましょう。
PHP 8.1でFILTER_SANITIZE_STRINGの代わりになるものは何ですか?
直接の代わりはありません。名前が実際には与えない安全を約束していたので非推奨になりました。期待する入力を検証し、そのまま保存し、出力するときにhtmlspecialchars()でエスケープします。タグを取り除く必要があるならstrip_tags()を使います。
filter_varとfilter_inputの違いは何ですか?
filter_var()は渡した任意の値をフィルターします。filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT)は元のリクエストから直接読むので、パラメータがなければnullを返し、コードが$_GETに加えた変更は無視します。どちらも同じフィルターとオプションを受け取ります。