皆さんは変数が「数字だけ」であるかどうか判定したいとき、どう書きますか?
is_****()
系の関数を使ってこうですか?
(※こちら、'+123'
というような文字列が渡された場合が考慮されておりませんでした。訂正コードを追記に記載しておりますので、もしも利用される場合はそちらを参照下さい。)
<?php
if (!is_null($target) && !is_bool($target)
&& is_int($target + 0) && ($target >= 0)) {
}
それとも正規表現を使ってこうですか?
<?php
if (preg_match('/^\d+$/', $target)) {
}
あなたがもしPHP5.1以上を使っているのであれば、 ctype_digit()
という関数を利用出来ます。
(※5.1未満でも利用は可能ですが、空文字も true と返してしまうので利用しないほうが良いでしょう。)
<?php
if (ctype_digit((string) $target)) {
}
この ctype_digit()
という関数ですが、以外に知られていなかったりします。
ググっても、期待ハズレだとか、罠だとか、変な挙動だとか、どちらかと言うとネガティブな記事が目立ちます。
これらの記事に書かれているのはいずれも、数値を渡した際の挙動に関する物でした。
PHPマニュアルには「与えられた文字列 text のすべての文字が 数字であるかどうかを調べます。」と書かれています。
(記事を書かれた時にはなかったかもしれませんが・・・)
また、こちらも当時からあったものかは分かりませんが、注意の項目にも「たとえば integer を渡すと、期待する結果にならない可能性があります。」と記載があります。
つまり、特別な理由がない限り、文字列にキャストして渡してやらなければなりません。
この辺り、変数の型に緩いPHPならよしなにやってくれるだろうと考えて、混乱する方が多いのではないでしょうか。
実はこの関数、ネイティブなCのライブラリを使用している為、そのライブラリの関数と同様の動作をします。
(※この辺りは、マニュアルに書かれている事をそのまま書いておるだけですので、Cのライブラリについて詳しく把握しておるわけではございません。間違っておりましたら、ご指摘いただけますと幸いです。)
ちなみに、上記注釈のページにもあります通り、Cのライブラリを使用する為、「正規表現よりもつねに好ましく、さらに "str_*" および "is_*" のような いくつかの等価な関数よりも好ましい」、更に「処理が著しく 高速である」のです。
が、正直速度は何を使ってもほとんど変わりありません。何十万回とループを回してやっと違いが出てくる程度です。
そうだとしても、数値を渡して正常(?)に判定して欲しい!というのであれば、ラッパー関数を作っちゃえばいいんじゃないでしょうか。
<?php
if (!function_exists('is_digit')) {
function is_digit($target) {
return ctype_digit((string) $target);
}
}
以上!\e
-- ここから追記 --
コメントでfilter_var()
のFILTER_VALIDATE_INT
についても併記して欲しい旨、ご要望をいただきましたので、以下に記します。
まず、filter_var()
関数とはなんぞやというところから。
PHP5.2 から追加された関数で、指定したフィルタでデータのフィルタリングができる非常に便利な関数です。
こちらも、非常に高速な処理で検証を行うことが可能です。
ctype_digit()
関数より高速かもしれません。
(※実際に1000000件のデータで検証してみましたが、0.02秒ほどこちらの方が早かったです。)
(※環境によって変わるかもしれませんので、あしからず。)
記述例としては、以下の様な感じでしょうか。
<?php
if ((false !== filter_var($target, FILTER_VALIDATE_INT)) && ($target >= 0)) {
}
じゃあこっちのがいいじゃん!と思われるかもしれませんが、ちょっとまった!
実はこれ、'0123'
という文字列を渡すと、false
を返してしまいます。
これは、0から始まる数字は10進数の整数として正しい形ではないと判断される為です。
オプションで指定できるFILTER_FLAG_ALLOW_OCTAL
フラグを用いて8進数表現(8進数の場合0から始まる必要がある)を許可することも可能ですが、「ゼロ (0) で始まる入力を八進数とみなします。 ゼロの後には 0-7 しか続けることができません。」と説明にもある通り'0912'
のような数字はNGとなってしまいます。
今回の条件は「変数が数字だけであるかどうか」なので、これらをfalse
と返してしまうのは問題です。
また、"+123"という文字列を渡すと、true
を返してしまうので、コレも問題ですね。
実はこれ、私自身見落としておりましたが、is_****()
で比較する場合もそうでしたね。。ごめんなさい。。
<?php
if (!is_null($target) && !is_bool($target)
&& is_int($target + 0) && ($target >= 0) && ($target[0] !== '+') {
}
訂正するならこう・・・ですかね。。もうここまでするなら正規表現使えよって感じがしないでも無いですが・・・
なんかでもまだ抜けが有りそう・・・。
まぁ、今回のこの関数(フィルタ)は、「整数」を検証する物なので、「数字のみであるか」という判定で用いない方が良いかもですね。
以上!\e
-- ここから追記 --
えー、だんだん本題とそれて参りましたのでこれで本当に\eにしたいと思いますが、
コメントでご指摘頂いております通り、php5.4以上ですと、array
を文字列にキャストする場合、E_NOTICE
レベルのエラーを返すようです。また、__toString()
メソッドを実装していないオブジェクトを文字列キャストすると、E_ERROR
(fatal error)レベルのエラーでそこで動作が止まってしまいます。
ユーザ入力を判定するレベルでこの記事を書いておりましたので、オブジェクトはそこまで厳密に考えておりませんでしたが、array
がE_NOTICE
エラーを吐くのは問題です。
例えば、以下の様なGETリクエストが投げられた場合、該当のエラーが発生してしまう可能性があります。
/index.php?target[]=123
<?php
if (ctype_digit((string) $_GET['target']) {
}
※なお、preg_match()
を利用する場合はE_WARNING
を吐きますので、いずれにせよis_array()
等での判定は必須と考えたほうが良いでしょうね。
まぁ、このような書き方してたら、リクエスト変数を直接判定に使うなって別の所からお叱りを受けそうなので、このへんにしておきます。
結論として、何が言いたかったのかというと、
数字かどうかを判定するのであれば、ctype_digit()
って関数が正規表現より高速で便利だよ!
ってことでした。
それでは、今度こそエンイー!