unoh.github.com

配列操作でE_NOTICE対策を簡単に行う方法

Wed Nov 01 06:52:57 -0800 2006

こんにちはjokagiです. もう日が変わりそうではありますが,本日はフォト蔵リニューアルお試しが始まりました!! アカウントを持っている人も持っていない人も新しく掲載する写真を用意してぜひぜひお試しください.

ってことで前フリとは関係ないのですが,今回はjokagiが以前常用していた関数を紹介します.

配列の要素を扱うときどうしていますか?

フォームを受け取るときによくあるパターンに下記のようなコードがあります.

if ($_GET['id'] && $_GET['passwd']) {
    login($_GET['id'], $_GET['passwd']);
} else {
    error_function();
}

しかしこのコードは問題があり,PHPの警告をきっちり出力するようにしていると,下記のような警告が出てくる場合があります.

Notice:  Undefined index:  id in /home/.../index.php on line 3
Notice: Undefined index: passwd in /home/.../index.php on line 3

これは未定義な配列要素を使用しているということで,URLにパラメーターidやpasswdを付加しなかった場合に出力されます. 一般的にこのような警告がでるPHPスクリプトのできはよくないということで,回避するために下記のようなコードを書く場合が多いです.

if (isset($_GET['id']) && $_GET['id'] &&
  isset($_GET['passwd']) && $_GET['passwd']) {
    do_login($_GET['id'], $_GET['passwd']);
} else {
    error_function();
}

これはisset()を用い,配列要素があるかどうか調べつつ処理を行うといった感じです. しかしこれでは2つの配列要素を扱うだけなのに可読性が下がってしまいます. ってことでまずこういう関数を用意します.

/**
 *  配列から値を取り出す
 *
 *  配列から値を取り出します。もし連想キーが存在しない場合、
 *  第3引数の値を返します。
 *
 *  @param array $array 値を取得したい配列
 *  @param mixed $key 配列から値を取得したい連想キー
 *  @return mixed 配列から取り出した値、$keyが存在しなければ
 *                $defaultを返す
 */
function array_get_value($array, $key, $default = NULL)
{
    return isset($array[$key]) ? $array[$key]: $default;
}

array_get_value()は見てすぐわかる感じですが,配列に指定要素があればそれを,なければ 第3引数の$default(省略時はNULL)を返す関数です. 個人的にはこんな使い方をよくやっていた気がします.

$first_name = array_get_value($user_info, 'first_name', 'ごんべ');
$last_name = array_get_value($user_info, 'last_name', 'ななし');

この関数を用意すると下記のように書き直すことができます.関数名は好みに応じて短くしたりしてもいいかもしれません.

if (array_get_value($_GET['id']) && array_get_value($_GET['passwd'])) {
    do_login($_GET['id'], $_GET['passwd']);
}

//  関数名を短くした雰囲気
if (avg($_GET['id']) && avg($_GET['passwd'])) {
    do_login($_GET['id'], $_GET['passwd']);
} else {
    error_function();
}

実際はid=0などこのままでは値があると認識してくれないのでもう少し修正する必要がありますね. 最近の流行のようにちゃんと===や!==でチェックしよう!!ってことでもうちょっとアレンジして第3引数の型に応じて取得した配列値の型変換を行うようにしてみました.

/**
 *  配列から値を取り出す
 *
 *  配列から値を取り出します。もし連想キーが存在しない場合、
 *  第3引数の値を返します。array_get_value()と異なるのは
 *  取得した値を第3引数の型に変換します。
 *
 *  @param array $array 値を取得したい配列
 *  @param mixed $key 配列から値を取得したい連想キー
 *  @return mixed 配列から取り出した値、$keyが存在しなければ
 *                $defaultを返す
 */
function array_get_value2($array, $key, $default = NULL)
{
    if (isset($array[$key])) {
        $result = $array[$key];

        $type = gettype($default);
        switch ($type) {
        case 'double':
            //  一応マニュアル推奨の値に変換しておく
            $type = 'float';
        case 'boolean':
        case 'integer':
        case 'string':
        case 'array':
            settype($result, $type);
            break;

        default:
            //  objectなどその他の型は変換しない
        }
    } else {
        $result = $default;
    }
    return $result;
}

array_get_value2()を使えば,例えば配列の値が「"0"(文字列型の0)」でも第3引数に「0(数値型の0)」を指定することで0が取得できるようになります. これで安心して===などで比較ができます. 文章で読んでもよく分からない方はarray_get_value()とarray_get_value2()の簡単な実行例を下記に出しておきますので参考にしてください.

<?php

/**
 *  配列から値を取り出す
 *
 *  配列から値を取り出します。もし連想キーが存在しない場合、
 *  第3引数の値を返します。
 *
 *  @param array $array 値を取得したい配列
 *  @param mixed $key 配列から値を取得したい連想キー
 *  @return mixed 配列から取り出した値、$keyが存在しなければ
 *                $defaultを返す
 */
function array_get_value($array$key$default NULL)
{
    return isset($array[$key]) ? $array[$key]: $default;
}

/**
 *  配列から値を取り出す
 *
 *  配列から値を取り出します。もし連想キーが存在しない場合、
 *  第3引数の値を返します。array_get_value()と異なるのは
 *  取得した値を第3引数の型に変換します。
 *
 *  @param array $array 値を取得したい配列
 *  @param mixed $key 配列から値を取得したい連想キー
 *  @return mixed 配列から取り出した値、$keyが存在しなければ
 *                $defaultを返す
 */
function array_get_value2($array$key$default NULL)
{
    if (isset($array[$key])) {
        $result $array[$key];

        $type gettype($default);
        switch ($type) {
        case 'double':
            $type 'float';
        case 'boolean':
        case 'integer':
        case 'string':
        case 'array':
            settype($result$type);
            break;

        default:
        }
    } else {
        $result $default;
    }
    return $result;
}

ini_set('display_errors''on');
error_reporting(E_ALL);

$array = array('foo'=>'foo'100=>100'20.2'=>'20.2');

echo '<pre>';
echo "<strong>\$array</strong>\n";
var_dump($array);
echo '</pre><hr /><pre>';

echo "<strong>\$array['foo']</strong>\n";
var_dump($array['foo']);
echo "<strong>\$array['bar']</strong>\n";
var_dump($array['bar']);
echo '</pre><hr /><pre>';

echo "<strong>array_get_value(\$array, 'foo');</strong>\n";
var_dump(array_get_value($array'foo'));
echo "<strong>array_get_value(\$array, 'bar');</strong>\n";
var_dump(array_get_value($array'bar'));
echo '</pre><hr /><pre>';

echo "<strong>array_get_value2(\$array, 'foo');</strong>\n";
var_dump(array_get_value2($array'foo'));
echo "<strong>array_get_value2(\$array, 'bar');</strong>\n";
var_dump(array_get_value2($array'bar'));
echo "<strong>array_get_value2(\$array, '100');</strong>\n";
var_dump(array_get_value2($array'100'));
echo "<strong>array_get_value2(\$array, '20.2');</strong>\n";
var_dump(array_get_value2($array'20.2'));
echo "<strong>array_get_value2(\$array, '20.2', '');</strong>\n";
var_dump(array_get_value2($array'20.2'''));
echo "<strong>array_get_value2(\$array, '20.2', 0);</strong>\n";
var_dump(array_get_value2($array'20.2'0));
echo "<strong>array_get_value2(\$array, '20.2', 0.0);</strong>\n";
var_dump(array_get_value2($array'20.2'0.0));

echo "</pre><hr />\n";
?>

$array
array(3) {
  ["foo"]=>
  string(3) "foo"
  [100]=>
  int(100)
  ["20.2"]=>
  string(4) "20.2"
}

$array['foo']
string(3) "foo"
$array['bar']

Notice: Undefined index: bar in /home/.../index.php on line 3
NULL

array_get_value($array, 'foo');
string(3) "foo"
array_get_value($array, 'bar');
NULL

array_get_value2($array, 'foo');
string(3) "foo"
array_get_value2($array, 'bar');
NULL
array_get_value2($array, '100');
int(100)
array_get_value2($array, '20.2');
string(4) "20.2"
array_get_value2($array, '20.2', '');
string(4) "20.2"
array_get_value2($array, '20.2', 0);
int(20)
array_get_value2($array, '20.2', 0.0);
float(20.2)

大体雰囲気はつかめたでしょうか? 結果的に第3引数を使用してarray_get_value2()を用いるとこういう風に記述することができます.

$id = array_get_value2($_GET, 'id', 0);
$passwd = array_get_value2($_GET,'passwd', '');
if ($id !== 0 && $passwd !== '') {
    do_login($id, $passwd);
} else {
    error_function();
}

array_get_value()はisset()し,かつ値をチェックするという手間を1回ですませる実装ができるようになります. しかし欠点もあります. それは,最初のコードではtypoなどが存在した場合,配列から値が取れなかったときにPHPのE_NOTICEが発生し,ミスを発見することができますが,array_get_value()を用いるとE_NOTICEが発生しなくなるため,この問題に気づきづらくなるという諸刃の剣の状態になることがあります.

最後に実はarray_get_value()はかなりの頻度で使用しましたが,array_get_value2()相当までの実装はまだ実運用で使用したことはありません(苦笑) もしご興味がある方はガツガツ使って不都合などあれば教えていただければと思います.