存在しない要素にアクセスしたときのNoticeがうざい


PHP5で、配列ではない型の変数や、
配列でも存在しない識別子に対して $array["key"] の
アクセスを行ったときの挙動のまとめです

あんな型やこんな型に対して添字アクセスを試みた結果がまとまってます。


  • string型の場合は、識別子をinteger型にキャストした上での各文字へのアクセスとなること
  • 非array型に対して $array["key"] とやっても何もエラーが出ないくせに、array型で存在しない識別子にアクセスするとNotice Errorを吐くこと
  • クラスインスタンスに対してのみ、Fatal Error を吐くこと

1番目と3番目は別にどうでもいいんだが、2番目に関して、自分は結構イライラさせられている。

<?php
$data = get_something();
if ($data['special_type'] == 2) {
    echo "marvelous!\n";
}
?>

special_typeは1〜10といった区分値またはどれにも該当しないNULLがあり得るとして、
このようなコードを書いたとき、区分値はもちろんのことNULLの場合もきちんと入っていればいいんだが、データの構築段階の都合でspecial_type要素自体が格納されない場合が往々にしてあったりする(いや、本当はそういう場合でもきっちり要素は用意すべきなんだろうけど)。
そうすると、こいつは「special_typeなんてねーよ」とご丁寧にNoticeを吐いてくれる。ここは黙ってFALSEにしてくれると嬉しいんだが(と、perl使いは思ってしまう)。


2008/04/05訂正
最初は「0か1かNULL」って例を書いてたんですが、意図するものは真偽の評価ではなかったので書き換えました。


special_flag → special_type
0か1かNULL → 1〜10またはNULL


ここで言いたかったのは

if (isset($data['special_type']) && $data['special_type'] == 2) { ... }

みたいに書かなきゃいけないのうぜー、ってことです。


例が悪すぎました。flagとか書いちゃってるし、そりゃー間違われても仕方ないorz
「0か1かNULL」でTRUE or FALSEを判定したいならコメントで教えて頂いたとおりempty()を使うのがスマートですね。こいつもisset()なんかと同じで要素がなくても黙って仕事してくれます。


というわけで、こういう場合はどうするのがよいのかと思って、こちらも色々試してみた。

<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);

echo "#1\n";
$a = null;
$b = $a['foo'];
var_dump($b);   // NULL

echo "#2\n";
$a = array();
$b = $a['foo']; // notice: undefined index
var_dump($b);   // NULL

echo "#3\n";
$a = array();
$b = array_key_exists('foo', $a) && isset($a['foo']) /* && $a['foo'] !== '' */;
var_dump($b);   // FALSE

echo "#4\n";
$a = array();
$b = isset($a['foo']) /* && $a['foo] !== '' */;
var_dump($b);   // FALSE

echo "#4b\n";
$a = array();
$b = isset($a['foo']['bar']['baz']) /* && $a['foo']['bar']['baz'] !== '' */;
var_dump($b);   // FALSE


// 以下おまけ

echo "#5\n";
$a = new StdClass;
$b = $a['foo']; // fatal error
var_dump($b);

echo "#6\n";
$a = new StdClass;
$b = $a->foo;   // notice: undefined property
var_dump($b);   // NULL

echo "#7\n";
$a = new StdClass;
$b = property_exists($a, 'foo') && $a->foo /* && $a->foo !== '' */;
var_dump($b);   // FALSE

echo "#8\n";
$a = new StdClass;
$b = isset($a->foo);
var_dump($b);   // FALSE

?>

とりあえずisset()ならNoticeも黙らせることができる。値があることを期待するのであればarray_key_exists()は冗長っぽい。
自分はstrictな人間なので長いことご丁寧に#3で書いてきたんだけど階層が深い配列だとarray_key_exists()が並びまくりで発狂しそうになる。しかし今年に入ってから(遅っ)、isset()なら無問題という事実を知ってちょっぴり幸せになった。


ちなみに、中身が入っているかどうか確認する時は、可能な限り厳密に行ってほしいところ。
0や'0'を入力するとFALSE判定で誤動作するプログラム大杉。

<?php

echo "#1\n";
$a;
if ($a) { var_dump($a); } // notice

echo "#2\n";
$a = null;
if ($a) { var_dump($a); }

echo "#3\n";
$a = false;
if ($a) { var_dump($a); }

echo "#4\n";
$a = 0;
if ($a) { var_dump($a); }

echo "#5\n";
$a = 0.0;
if ($a) { var_dump($a); }

echo "#6\n";
$a = '';
if ($a) { var_dump($a); }

echo "#7\n";
$a = '0';
if ($a) { var_dump($a); }

echo "#8\n";
$a = array();
if ($a) { var_dump($a); }

echo "#9\n";
$a = array(null);
if ($a) { var_dump($a); } // array(1) { [0] => NULL }

echo "#10\n";
$a = array(array());
if ($a) { var_dump($a); } // array(1) { [0] => array(0) {} }

?>


■参考
PHP: PHP 型の比較表 - Manual



どーでもいいが、カラフルすぎやしないか。シンタックスハイライト。