これは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になりました。