ハイパーマッスルエンジニア

Vim、ShellScriptについてよく書く

phpのempty()には気をつけろと言うけど具体的にどういうケースやねん

巷でよく「empty()は挙動をわかっていないと使ってはいけない」というのを見るが具体的にどういう場面で注意したらいいのかイマイチ理解していなかった。

そこで「気をつけてはいたが実際にempty()で痛い目に会った」話をしたい。

empty()とは

empty()で気をつけろと言われているのはtrueとなるケースが様々であることだ。
empty()TRUEになるケースは以下。

empty
$var=1 FALSE
$var="" TRUE
$var="0" TRUE
$var=0 TRUE
$var=NULL TRUE
$var TRUE
$var=array() TRUE
$var=array(1) FALSE

他にも比較値はあるが詳しくは下記サイトの表がよくまとまってる。

d.hatena.ne.jp

ざっくりいえば何かしら値が入ってたらFALSE = 空だったらTRUEというイメージ。
そう、あくまでイメージなのが注意。emptyという関数名に騙されてはいけない。

実際にハマった点

注意すべきは表で言うところの0の場合。

empty
$var="0" TRUE
$var=0 TRUE

0の場合でも空だと判定されてしまう。これでハマった。
巷で「0の場合はtrueになるから気をつけて」と言われているので頭では理解していた。
しかし大事なのは0の場合がtrueになることを「覚えていること」ではなく「0が来る場合を想定できているか」ということだ。

以下のようなCSVから値を取得し、それを元に処理をするコードがあるとする。(実際はもっと前処理などがあるがそこは省く)

test.csv

// 商品コード, 原価, 税率
item_code,cost,tax
item1,3000,8
// CSVから値を取得
$csv = self::getValueFromCsv('test.csv');
$tax = $csv['tax'];
// 税率カラムに値が何もなかったら税率を8%にする
if(empty($tax)) { 
    $tax = 8;
}
// 単価を計算
$price = $csv['cost'] * (1+$tax/100);

割とツッコミどころの多いコードだが今はおいておこう。
一見するとemptyを使いこなせてはいそうだが、ある場合のときに$priceの値が意図しないものとなる。
ちなみに上記の場合だと$price = 3240だ。これは意図した挙動である。

どういうときに何が起こるのか

では以下のような思考CSVの入力者(ユーザー)にあった場合どうなるか。

ユーザー「とりあえず税抜きで単価を出したいから税率は0に設定するか」

// 商品コード, 原価, 税率
item_code,cost,tax
item1,3000,0

この場合、empty($tax)はtrueを返す。つまり税率が8%に設定されてしまう。
もともと税率を設定したくなかったから0にしていたのに税率が設定されてしまうやんけ!とお問い合わせが来てしまうわけだ。(てかこのコードだとtaxに空文字が入ってきてもそうなるな、まあ置いておこう。)
ちなみに想定しているのは$price = 3000だがこれだと$price=3240になってしまう。

もしくはこんな場合

ユーザー「とりあえずテストでCSVアップロードしたいから商品コードは"0"にしよう」

test.csv

// 商品コード, 原価, 税率
item_code,cost,tax
0,3000,8
// CSVから値を取得
$csv = self::getValueFromCsv('test.csv');
$item_code = $csv['item_code'];

// 商品コードに値が何もなかったら例外を出す
if(empty($item_code)) { 
    throw new Exception('商品コードが空です')
}

これもアウト。ユーザーとしては「ちゃんと入力しているのにエラーが出る!」と怒り心頭ですわ。

つまりよ

つまり何が言いたいかってユーザーの入力を信用するなってことなんですよね。
ちょっと上手いことまとめられてないのでアレがアレなんだが、一見すると便利な関数もちゃんと意味わかってないと使ってはいけない。
=> ちゃんと色んなケース想定してる?っていうこと。

別にempty()はダメゼッタイって言っているわけではなく、想定できている=empty()でも問題ないという検証&テストコードが作成されていれば全く問題ない。
ただ、少しでも可能性があるのならば別の方法で判定すべきだ。
先の商品コードの例で言えば、

// 商品コードに値が何もなかったら例外を出す
if($item_code === '') { 
    throw new Exception('商品コードが空です')
}

のように空文字判定で良い。

まとめ

いかんな、、、実際にハマったことを書いて見てくれた人に気をつけてと言いたかったが伝わっている気が全然しない。
やはり一回自分でしくじるのが一番勉強になったりする。