PHPで配列に安全にアクセスしたいだけだが

2010.09.18追記 多次元配列に対応してみました。


phpにおいて配列とは、生成時にarray()とかいう妙なリテラル*1を書くことを強制されるためそれだけで既に書くにも読むにもだいぶ残念ではあるが、加えて連想配列との境がないために挙動が予想つかないarray_*系関数が、なおかつ速度も大抵遅いと聞いてがっかりせざるをえない代物であると分かり残念な昨今。

まあ分からなくもない。恐らくPHP言語設計者は抽象度の高い(連想)配列を目指したのだろう。例えばCだと配列最後null文字が入り、その実装(仕様だっけ?)に依存したコードが多い。というか普通だ。
それに対しPHPでは数値のインデックスでさえ配列順序とは無関係だ。何故そうしたか分からんが。でもメモリ消費多いし、array_*関数は遅いし、foreachのためだけに設計されたんじゃねーのかこの配列は、と疑いたくもなる。
まあいい。全部憶測だし。


phpで配列の存在しないkeyにアクセスしようとすると、noticeだかwarningだかが出て怒られてしまう。
よってisset()によってkeyに対するvalueが存在するかどうか確かめる訳だ。

<?= isset($arr['key']) ? $arr['key'] : ''; ?>


たまにこれを大量に書きたいことがあるのだけど、長い割にたいした意味もなく、三項演算子だから可読性も落ちてしまい。残念です。
ということで配列アクセッサ生成関数つくった。

<?php
function array_accessor(array $array) {
    return function($key, $in_false='') use($array) {
        return isset($array[$key]) ? $array[$key] : $in_false;
    };
}


要PHP5.3以上。

<?php
$_data = array(
	'hoge' => 1,
	'moge' => 2,
	'fugu' => 3,
);
$data = array_accessor($_data);
echo $data('hoge');  #=> 1
echo $data('moge');  #=> 2
echo $data('not_exist_key');  #=> 空文字
echo $data('yeasterday', date('Y-m-d H:i:s'));  #=> 'yesterday'に対応する値が存在しないので、現在時刻表示

オブジェクト化するより少しだけ抵抗感がなくなるかもしれない。

default value

でフォルト値はaccessor生成時に決めた方がいいだろうかと思ったけど

<?php
function array_accessor(array $array, $def='') {
    return function($key, $in_false=$def) use($array, $def) {
        return isset($array[$key]) ? $array[$key] : $in_false;
    };
}

これはエラー。


<?php
function array_accessor(array $array, $def='') {
	define('def', $def);
    return function($key, $in_false=def) use($array) {
        return isset($array[$key]) ? $array[$key] : $in_false;
    };
}

通ったけどこれはひどい

<?php
function array_accessor(array $array, $default='') {
    return function($key, $in_false=null) use($array, $default) {
        if ($in_false === null) {
            return isset($array[$key]) ? $array[$key] : $default;
        } else {
            return isset($array[$key]) ? $array[$key] : $in_false;
        }
    };
}

こんなところか。


動作。

<?php
$_data = array(
	'hoge' => 1,
	'moge' => 2,
	'fugu' => 3,
);
$data = array_accessor($_data, '---');  # デフォルト値を決められる。指定しないとデフォルト値は空文字。
echo $data('hoge');  #=> 1
echo $data('moge');  #=> 2
echo $data('not_exist_key');  #=> '---'
echo $data('yeasterday', date('Y-m-d H:i:s'));  #=> デフォルト値より第2引数が優先される


ところでmacvimで日本語打ってるとたまに動作おかしくないかこれ。と思ったけどSKKの人は関係ないのか...

多次元配列版

ルーさんからコメントを頂いたので、多次元配列版も考えてみました。
残念ながらPHPではラムダ関数を変数に代入しても再帰出来ない(Lisp系はlabelで再帰出来るしHaskellはwhere節内で関数定義出来るしScalaは関数内関数とか出来るのに。)ようなので、名前空間汚染しますがヘルパー関数(dig)を定義しました。他に方法あるかもだけど。

<?php
function dig($arr, $keys, $default) {
    $key = array_shift($keys);
    if (isset($arr[$key])) {
        $val = $arr[$key];
        if (is_array($val) && $keys) {
            return dig($val, $keys, $default); // 再帰
        } elseif (is_scalar($val) && $keys) {
            return $default;
        } else {
            return $val;
        }
    } else {
        return $default;
    }
}

function array_accessor(array $array, $default='') {
    return function() use($array, $default) {
        $keys = func_get_args();
        return dig($array, $keys, $default);
    };
}

動作。accessor生成したら、引数に配列のkeyを指定していきます。

<?php
$_data = array(
    'aaa' => array(
        'Anna' => array(
            1 => 'Ann',
            2 => 'Nancy',
        ),
        'Alice' => 'Allie',
    ),
    'bbb'=> array(
        'Bella'=>'Isabella',
        'Bill'=>'William',
    ),
);
$data = array_accessor($_data, 'WRYYYYYYYYYY!!!');
var_dump($data('aaa'));  #=> array('Anna'=>array(...略
var_dump($data('aaa', 'Anna', 2));  #=> Nancy
var_dump($data('aaa', 'Anna', 2, 'XXXX'));  #=> WRYYYYYYYYYY!!!

結構自然に出来たと思うのですが、デフォルト値がaccessor生成時しか指定出来なくなってしまいました。まあ実用上十分かもしれないけど。

ところでBellaの愛称がIsabellaとかBillの愛称がWilliamとか、海外の愛称は面白いですね。

*1:関数じゃないらしい