LoginSignup
49
50

More than 5 years have passed since last update.

PHPのissetの罠

Posted at

これはADVENTARのPHP Advent Calendar17日目のエントリです。

早速ですが、このコードのvar_dumpは何を出力すると思いますか?

$hoge = "fuga";
var_dump(isset($hoge["fuga"]));

実行してみましょう

$ php -r '$hoge="fuga";var_dump(isset($hoge["fuga"]));'
bool(true)

trueです。なぜだか分かりますか?僕は最初このphpの処理系が茶目っ気を出してふざけてるのかと思いました。

一度issetを外してみましょう

$ php -r '$hoge="fuga";var_dump($hoge["fuga"]);'
string(1) "f"

fが返りました。このfはなんでしょう?
実は$hogeに格納された文字列の1文字目です。

つまり、$hoge["fuga"]"fuga"が数値にキャストされ0になり、$hoge[0]が評価され文字列の1文字目が取り出されたわけです。で、最初の話に戻ってisset($hoge["fuga"])trueになったわけです。PHPの豆腐のような柔軟性にはほとほと困ったものです。

ですが、流石にPHPの中の人も「流石にねーわ」と思ったのか、PHP5.4.0からは挙動が変わりました。

Changelog
Version     Description
5.4.0       Checking non-numeric offsets of strings now returns FALSE.

5.4.0から文字列入った変数のnon-numericなoffsetsのチェックはfalseを返すよ、って感じですね。
ちなみにさっきの実行したphpは5.3でした。

$ php -v
PHP 5.3.26 (cli) (built: Jul  7 2013 19:05:08)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies

では5.4で実行してみましょう。

$ phpenv global 5.4snapshot
$ php -v
PHP 5.4.24-dev (cli) (built: Dec 11 2013 01:49:21)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies
    with Xdebug v2.2.1, Copyright (c) 2002-2012, by Derick Rethans
$ php -r '$hoge="fuga";var_dump(isset($hoge["fuga"]));'
bool(false)

確かにfalseが返りますね。ちなみにほぼこれやるためだけにphpenv入れました。
ここで一つ疑問に思ったんですが、"non-numeric"ってどこまでがnumericでどこからnon-numericなんでしょう?
PHPにはis_numericという関数がありますし、それに従う気もします。
というわけでis_numericと合わせて試してみました。versionはさっきと同じ5.4.24です。

$ php -r '$hoge="fuga";
$numerics = array(0, "0", 0x0, "0x0", 0.0, "0.0", 0.5, "0.5", 01, "01", 0b0, "0b0", 1e0, "1e0", false, null, array());
foreach ($numerics as $numeric) {
    var_dump($numeric, isset($hoge[$numeric]), is_numeric($numeric));
    echo "\n";
}'

このスクリプトを実行した結果を表にしたのが以下です。

$numeric isset($hoge[$numeric]) is_numeric($numeric)
0 true true
"0" true true
0x0 true true
"0x0" true true
0.0 true true
"0.0" false true
0.5 true true
"0.5" false true
01 true true
"01" true true
0b0 true true
"0b0" false false
1e0 true true
"1e0" false true
false true false
null true false
array() false false

一貫性がなさすぎてびっくりですね...
この結果をまとめると

  • $hogeの文字列の長さの範囲の)数値を渡した場合はisset, is_numericともにtrueになる
    • 0.5issettrueになるのが気に食わない
    • ちなみに$hoge[0.5]を使おうとするとNoticeが出る
  • nullfalseis_numericfalseのくせにissettrue
  • array()は当然どちらもfalse
  • 文字列のis_numeric"0b0"false、それ以外はtrue
    • 0bで始まるのは2進数の整数リテラル
    • is_numericは本題ではないが"0x0"とかtrueになるのに"0b0"falseになるのが気に食わない(バグ?)
  • 文字列のisset"0", "01", "0x0"true、他はfalse
    • "0b0""1e0"false"0x0"がtrueなのが気に食わない。

この実験から分かることは、そもそも文字列にisset使うな、という話ですね。issetを使う時は配列を相手にすることが多いと思うので、そういう場合はis_arrayで配列であることを確認してからissetを使うのが良さそうです。

ちなみにphp5.3で(0b0を抜いて)実行した場合、issetの列は全てtrueになりました。

49
50
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
49
50