普通プログラムといったら、なんらかの入力データに対して処理を行うものだ。
しかしまあこの入力データというヤツ、大抵の場合信用できない。
というか、「いいよ信用して」と言われていたとしても、疑ってかかるのがセオリーだ。
そんなわけで、基本的に入力値というものに対しては、殆どの場合
それから行う処理に対して適切なものかどうかというのを確認することになる。
その中でも基本的なものが、「未入力判定」だ。
そもそもデータが存在していなければその先の処理ができなかったり、
意味がなかったりということは良くある。
だから、必要なデータが存在しているかどうか確認する場面というのは数多くある。
じゃあ、そんなときPHPではどう書くか?
if (!$example_input) { /* ○○を入力してください、などのエラー処理 */ }
実に素直なコードだ。実のところ、殆どの場合これで特に問題はない。
ただ、PHPには妙な癖があって、文字列 ‘0’ を false と判定するのである。
まあ、その仕様が良いのか悪いのかというのは状況によりけりなのだが、
おかげで上記コードでは妙な結果を引き起こすことがある。
つまり、ちゃんと「0」という値を入力したにも関わらず、
入力されていないから入力せよ、というエラーメッセージを受け取ることになるわけだ。
使ってるひとは恐らく思うだろう。「あれ、0ってちゃんと入れたのに、なんで?」
しかし ’00’ や ‘0.0’ ならエラーは出ない。ううむ。
if (empty($example_input)) { /* エラー処理 */ }
PHPのマニュアルを見るととてもそれっぽいempty()という関数が存在する。
なーんだ、ちゃんとそれ用の組み込み関数があるんじゃん。
そして使ってみるに、実に素直なコードができあがる。
読みやすい。意図もすぐわかる。素晴らしい。
…と思いたい。
だがこの組み込み関数、マニュアルにはしっかり下記記載がある。
empty()は、変数が設定されていないときに警告が生成されないことを除けば、(boolean) var の逆です。
つまり、A パターンと全く同じ問題を抱えているのだ。
大変素直で分かりやすいコードなのでオススメな書き方ではあるのだが、
場合によっては意図しないエラーを見せることになるので注意されたい。
if ('' == $example_input) { /* エラー処理 */ }
AやBのパターンで見てきたとおり、PHPでは文字列 ‘0’ が false 判定となるので、
それを回避するために文字列と比較してしまえ、というのがこのCパターン。
この方法を使えば、文字列 ‘0’ 問題は解決する。
まあそこそこ素直なコードだし、これでいいか。
と思いきや、$example_input が数値の 0 だったときに未入力判定になる。
if ('' == 0) // true
if ('' != 0) // false
えっ…?
いや、まあ。PHPはそういう仕様なので仕方ない。
文字列と数値は違うものとして扱われるし扱っているハズなのに、
時折その境界が曖昧になっていて怖い思いをする。
しかもこの境界の曖昧さは意図してやっているように見えるあたり、
若干余計なお世話な感じがあるのがこのPHPという言語である。
まあ、数値の 0 を未入力とみなすなら、これでもいいのかもしれないが。
if ('' === $example_input) { /* エラー処理 */ }
PHPには「型」を含めて判定する比較演算子が存在する。上記の === がそれだ。
確かに、Cパターンであった数値の 0 が未入力判定されるということはなくなるが、
これには大変致命的な問題が存在する。
今度は $example_input が未定義だったり、null値だったりしたときにマズいのだ。
if ('' === null) // false
if ('' === $undefined_variable) // false
さすがにこれらの値は未入力だと判断すべきだろう…。
個人的には、この型厳密な比較演算はお勧めしない。
読む人書く人に型を意識させる、という意味では悪くはないのだが、
PHPはあまり型を意識しないコードが多いので、
それらと混ざるとどうしても混乱を引き起こしがちなのだ。
本当に型を含めた比較が必要なのかどうか、考えてから使うのをお勧めする。
ただ、PHPの組み込み関数がこの演算子を要求することがあるので、
その点面倒だなあというのが素直な感想ではあるのだが…。
if ('' == strval($example_input)) { /* エラー処理 */ }
Aパターン、Bパターンであった文字列 ‘0’ 問題、 Cパターンであった数値 0 問題、
そしてDパターンであった null/undefined 問題を解決するのが、このEパターン。
それぞれ見てみよう。
if ('' == strval('0')) // false [入力]
if ('' == strval(0)) // false [入力]
if ('' == strval(null)) // true [未入力]
if ('' == strval($undefined_variable)) // true [未入力]
当然ながら「自分が何をしたいのか」についてはきちんと確認する必要があるが、
多くの場合はこのやり方が上手くいく。
わざわざstrval()呼び出しがかかるあたりに難点を感じなくもないが、
無難さとの折衷案としては悪くはないハズ。
ただ、これでも $example_input が array() だったりすると困る。
if ('' == strval(array())) // false [入力?]
strval(array()) の結果は、何故か文字列 ‘Array’ なのだ。
空のarrayは入力されているのか、それとも未入力なのか?
という点は確かに場合によりけりではあるが、
多くの場合は未入力で判定したいだろうなあ、とは思う。
そういう意味で、Aパターン、Bパターンの判定方法であれば空arrayは未入力で判定してくれるので、
既に述べたことが問題にならないのであれば、素直なAパターンやBパターンで判定するのが良いと思う。
他にも $example_input がオブジェクトだった場合…、
みたいなことは、入力がなんらかのAPIの呼び出し結果だったりするときには
あり得る話なのではあるが、その場合は空リターンなのかどうかを
判別する方法がAPI毎に決められていることが多いので、素直にそれに従うのが良い。
未入力かどうかをちょいと判定するだけだろ?
と舐めてかかりがちではあるけども、これまで見てきたとおり、
何の入力を未入力と判定すべきなのか、という点や、
PHP自身の仕様についてよく検討しないと、意図しない結果を引き起こすこともある。
有名な話なので既に知っているという人も多いかとは思うものの、
たかが未入力判定と侮らず、是非少し考えてみて欲しい。