PHP BLT #4 で PHP の興味深い挙動を知った

photo by Dana McMahan

昨日は PHP BLT #4 でメルカリに行ってきた。

phpblt.connpass.com

そこで @uzulla さんが雑談的に発表された内容に面白いものがありまして。

たしかこんな感じのやつ。

$values = [
  'a' => null,
  'b' => 'abc',
];
foreach ($values as $key => &$value) {
    $value[$key] = '123';
    // 本当は $key[$value] = '123' の意図
}

ここで間違えて $key と $value を入れ替えて記述したのはいいけど、$value が null の時にはエラーにならなくて、abc の時に初めて

PHP Warning: Illegal string offset 'b' in /some/path/hoge.php on line X

なんてのが出るという話。

値が null の変数は未定義と等しく扱われるので、そこで変数の生成と代入が同時に行われる。null は未定義と等しいのは以下の例をみても分かる。

$a = null;
var_dump(isset($a)); // 結果は bool(false)

で、先の uzulla さんのコードを、エラーを出さないようにキーが 'b' の要素を抜いて実行すると、当然エラーにはならず $values は以下のようになる。

$values = [
    'a' => null,
];
foreach ($values as $key => &$value) {
    $value[$key] = '123';
}
var_dump($values);

実行結果:

array(1) {
  ["a"]=>
  &array(1) {
    ["a"]=>
    string(3) "123"
  }
}

あれ? なんで配列が入れ子になるんだろうと不思議に思ったので、色々試してみたところ挙動が理解できた。

この場合 foreach でループを回す際の $value に & がついて参照になっていることがキモで、ループ内で新たな配列が作られた後、それが $values キー 'a' の値として上書きされているということになる。

分かりやすく代替コードで示すとこう。

foreach ($values as $key => &$value) {
    $tmp[$key] = '123';
    $value = $tmp;
}

参照をやめれば、当然ながら要素の上書きは起こらない。

$values = [
    'a' => null,
];
foreach ($values as $key => $value) {
    $value[$key] = '123';
}
var_dump($values);

実行結果:

array(1) {
  ["a"]=>
  NULL
}