文字列を配列扱いするとisset+配列記法はtrueを返す

文字列を配列として扱うと、第2階層以降ではエラーになるのですが、第1階層だけはスライスしてくれるという仕様で微妙な動作をしているのでメモです。
http://d.hatena.ne.jp/noopable/20090726/1248589117
こちらで、よくあるPHPコードとして

<?php
$q = isset($_GET['q']) ? $_GET['q'] : NULL;

こういうのを提示していましたが、似たソースで

<?php
$foo  = isset($test['foo']) ? $test['foo'] : NULL;

こんな感じでコードしてあったとします。$testのインデックスにfooがあるかどうかを調べて代入するというだけのコードですが、もし$testが文字列だと

<?php
$test = 'abc';
$foo  = isset($test['foo']) ? $test['foo'] : NULL;

$fooはNULLになりません。

<?php
$test='test' and isset($test['foo']) and print('true');
//true

$testが文字列だと、'foo'を数値評価して10が返り1文字目が返るからなんですね。そこで数値化されてしまうのが微妙な感じですが、これはバージョン依存があるのか基本仕様なのか、ちょっと知りたいところです。配列やArrayAccess可なオブジェクトかどうかが自明であればいいんでしょうけど。

追記:

fbisさん、ブクマコメントありがとうございます。

'foo'は数値評価すると0なので、1文字目が返るってことですね。

mztさん、コメントありがとうございます。

対象が配列として扱える状況であることが分かっていれば、array_key_existsでいいんですけど、その点ではissetと対等かなという気はします。ただ、文字列のときにwarningを出してくれる点でarray_key_existsの方がはまらなくてよさそうな気がしますね。パフォーマンスについてはわかりません。

id:koyhogeさん、ブクマコメントありがとうございます。

関数/メソッドのオプションを柔軟に受け渡す - Blog::koyhoge::Tech
こちら、拝見しました。配列を対象とした処理とわかっていれば、array_val 便利ですね。個人的にはArrayAccessを実装したオブジェクトの場合を含めたいので、is_arrayとis_objectも含めたうえで、array_key_existsするかもしれません。ArrayAccessできるかどうかを確かめられる関数があればもっと正しく実装できるんでしょうけど。

追記2

引数が文字列の場合、配列の場合、通常のオブジェクトの場合、ArrayAccessなオブジェクトの場合で、

<?php
$string = 'bar';
$stdObject = new stdClass;
$stdObject->foo = 'bar';
$arrayObject = new ArrayObject(array('foo' => 'bar'));
$array = array('foo' => 'bar');

これらをチェックしてみると、

  • issetは文字列の場合にtrueを返す。
  • array_key_existsは文字列のときにwarningが出る。
  • is_arrayのチェックでは、ArrayAccessなオブジェクトを拒否してしまう。

ということで、配列記法でアクセスできるキーがあるかどうかを書いてみました。koyhogeさんの関数と引数順は同じです。

<?php
function array_val(&$data, $key, $default = null)
{
    if (is_array($data) || $data instanceof ArrayAccess and isset($data[$key])) {
        return $data[$key];
    }
    return $default;
}

上記のような関数にして短く実装するか、関数化しないで、

<?php
$foo = is_array($test) || $test instanceof ArrayAccess and isset($test['foo']) ? $test['foo'] : NULL;

と書くか。
まぁ、通常はこういうのを書く前に何物か確定しているケースがほとんどだと思いますが。。