これは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.5でissetがtrueになるのが気に食わない - ちなみに
$hoge[0.5]を使おうとするとNoticeが出る
-
-
nullやfalseはis_numericはfalseのくせにissetはtrue -
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になりました。