こんにちは。ワラゴンです。
PHPで実装する時、いつもモヤっとしていた変数の存在確認についての実装方法をまとめてみました。
PHPは厳格な型付けが不要なので、便利に使える言語ですが、その半面、厳密にやろうとするとなかなかうまく行かないことも多いです。
ただ実際、業務で使うほとんどの場面では、そこまでの厳密さを要求されない(=品質にクリティカルな影響を及ぼさない)ことの方が多いと思うので、イイ感じのところで手を抜きたいものです。
手を抜きたいとは思っていても、変数の存在を確認する書き方が色々あるので、実際にどのような書き方が失敗が少ないのか迷ってしまう場面も多いのではないのでしょうか。
そこで、ここでは私が思うレベルで変数の存在確認をする方法の考え方を紹介したいと思います。
まあ、私はそんな感じのゆるゆるプログラマなので、きちんとしたプログラマさんには怒られてしまうかもしれないですね。
結論
まずは結論から。
・nullに加えて、0,"",[],falseであるときも含めて処理を分けたい → !empty
・0でも空配列でも良いから、定義さえされていれば良い時の処理を分けたい
配列のキーの存在確認の場合→array_key_exists
その他→isset
それではこのような結論となった経緯を解説してみます。
issetや!emptyが必要なのはどんな場面かを知る
変数の存在確認や、有効性を確認する必要がある場合というのを具体的に想像してみましょう。
私が思うには、大きく以下のような2つのケースに分けられるのではないかと思います。
・null に加えて、0,"",[],falseであるような時には処理をさせたくない(有効な値である時の処理を分けたい)
・値が0,"",[],falseであっても良いから、とにかく定義されているかどうかを知りたい(未定義の処理を分けたい)
そして、これらの目的を達成するために、異なるアプローチがあります。
もう少し具体的に見てみましょう。
null に加えて、0,"",[],falseであるような時には処理をさせたくない(有効な値である時の処理を分けたい)
ある変数に、何かしらの有効な値、具体的には0でもnullでも''でもなく、空の配列でもない意味がありそうな「何か」が入っていることを確認したい場合があります。
もう少し具体的な例で言うと、ある関数GetUser($user_id)は、ユーザーIDを渡すとUserオブジェクトを返してくれるとします。
そして、対応するデータが見つからなかった場合はnullを返してくるとします。
呼び出し側では、当然Userオブジェクトが返ってくることを期待しているので、続いての処理で$user->GetName()とかのメソッドの呼び出しをするでしょう。
しかし、GetUserがnullを返してきた場合は、存在しないメソッドであるGetName()を使うとFatalエラーとなり致命的な結果となります。
また、別の例ではstrtotime()などのPHPの関数では、与えられた文字列を正しくタイムスタンプに変換できなかった場合はfalseを返してきます。
このような時も、strtotime()の戻り値がfalseであるかどうかの確認が必要になってきます。
このように、相手の関数が何であれ、戻り値として何かの有効な値(=空値でなく、nullでもない)が入っていることを確認したいという用途です。
値が0,"",[],falseであっても良いから、とにかく定義されているかどうかを知りたい(未定義の処理を分けたい)
もう一つのパターンが、とにかく定義されているかどうかを知りたいというケースです。
値が定義されているかどうかを判断しなければならないケースというと、よくあるのがPOSTやGETで送られてきたデータの中に、期待のキーがあるかどうかですね。
最も分かりやすいケースで言うと、フォームのチェックボックスです。
チェックボックスは、チェックが入っていないと値が送信されてこないという厄介者な特徴があります(私は今でもたまにハマります)。
なので、POST配列の中に、お目当てのキーがあるかどうかを知りたい場合というのがあります。
このような時は、実際に格納されている値は0でも空文字でも良いから、とにかくキーが存在しているかどうか?が焦点となります。
-issetやemptyを使う上で知っておきたいこと
以上を踏まえてissetやemptyを使う上で知っておきたいことを以下に紹介します。
似たような意味を持つ書き方がある
issetや!emptyと同じように、if($var){...}という書き方を見ることもあると思います。
ここで、以下は同じ意味合いの処理であることを覚えておきましょう。
if(isset($var)){...} は if($var !== null){...} と同じ
if(!empty($var)){...} は if($var){...} と同じ
右側の書き方の場合、$varが未定義の時にNoticeレベルのエラーが出るという欠点があります。
本番環境ではNoticeエラーは表示させないような設定になっていると思うので、見かけ上は右側の書き方でも問題ありません。
しかし、内部的にはエラーをログに残している(PHPの設定にもよる)ため、パフォーマンスが少し落ちるのと、エラーログを見たときにノイズが多くなるという問題があります。
この辺りは本当に思想の問題になるので、「Noticeならエラー出てもイイよ」というおおらかな環境なら右側の書き方でも良いと思います。
ただ、この記事ではissetとemptyの使用を推奨します。
空値とは0,"0","",[],false,null。文字列の"0"だけは特殊
空値とはif($var)で評価した時にfalseを返す値です。
具体的には0, "0", 空の配列、空文字、false、nullです。
ここで、"0"というのは文字列の0を表していますが、これだけは注意が必要です。
というのも、一般的には(感覚的には?)文字列の0は「有効な値」と思いがちですが、PHPでは空値として扱われてしまいます。
ですので、何かの関数なり外部からの入力が文字列の"0"になる可能性がある場合は、少し注意して実装する必要があります。
(まあ、そんなケースはそんなに多くはないと思いますが。)
null か 未定義かを区別できないが、区別しなくてもいいと割り切っても良い
issetやemptyはnullか未定義かを区別できないという特徴があります。
つまり、変数は宣言されているが、nullという値が設定されている場合と、変数そのものが存在していない場合を区別できない、ということです。
ちなみにif($var !== null){...}もif($var){...}も区別できません。
しかし、私の経験上、この二つを区別しなければ正しく動かないというケースは非常に少ないため、あまり気にしないようにしています。
評価したい相手が、配列のキーである場合はarray_key_existsが良い。nullと未定義を区別できるため
評価したい変数が、配列である場合はarray_key_existsを使った方が良いです。
この関数は、nullと未定義を区別することができるためです。
例えば、$a = ['name' => null]がある時に、array_key_exists('name', $a)はtrueを返しますが、array_key_exists('hoge', $a)はfalseを返します。
上記を踏まえると・・・
存在確認をするための処理をどのように書くかを決めるためのファクターは以下のようになります。
1. nullに加えて、0,"",[],falseであるときも含めて処理を分けたい
2. 0でも空配列でも良いから、定義さえされていれば良い時の処理を分けたい
3. if($var !== null){...}/if($var) ではなく isset/!emptyを使う
4. 配列のキーに対して存在有無を確認したい場合はarray_key_exists
これをまとめたのが、最初に書いた結論になります。
考え方としては、目的の変数がnullである場合はissetを使おうがemptyを使おうが、trueにはなりません。
nullに加えて、0やfalse、空の配列が入っている場合もfalseと判定させたいなら!emptyを使うことになります。
そうではなく、0やfalse、空の配列でも良いから、変数が定義されているかどうかを知りたい場合、対象が配列のキーであるかどうかによって変わります。
それが配列のキーの存在有無の場合はarray_key_existsが安全です。
配列のキー以外、通常の変数の存在有無を確認したい時(頻度は少ないか?)は、issetを使いましょう、ということになります。
まとめ
いかがでしたでしょうか?
issetやemptyの使い方は、私自身がこれまでかなりあいまいにしていたことなので、今後の実装方法について良い方針ができたと思います。
ちょっと怪しい部分もあるかもしれませんが、概ねこんな考え方でやっていき、何か致命的な問題が出たら考えを改めようと思います。
最後に、参考までにissetと!emptyで評価した時の結果マトリクスを以下にあげておきます。
if(isset($var){…} | if(!empty($var){…} | |
数値のゼロ 0, 0.0 |
TRUE | FALSE |
文字列のゼロ "0" |
TRUE | FALSE |
空文字列 "" |
TRUE | FALSE |
空配列 [], array() |
TRUE | FALSE |
偽 FALSE |
TRUE | FALSE |
定義済みNull null |
FALSE | FALSE |
未定義 | FALSE | FALSE |
Qiita isset, empty, is_null の動作まとめ