PHPの多次元配列でユニークキー検索っぽい事をする
複数のレコードを持つデータセットを PHP で扱う際に、「ユニークキー検索」みたいなことが出来ると嬉しい時がある。
当然だが、データセットが「ユニークキー」を持っている仕様でないと、期待する結果は得られないので、参考にする場合は注意されたし。
データセットは array(配列型)で持つ
データセットは RDBMS や JSON から取得するシーンが多いと思うので、今回はこの2点を中心に解説する。
RDBMS から PDO 経由で取得
PHP でデータベースを扱う際、管理人は PHP Data Objects(PDO)しか使わないので、解説は PDO のみでご容赦ください。
今回検索で使用するのは「配列用の関数」なので、PDOStatement::fetch の mode に PDO::FETCH_ASSOC を指定しておく。
~(中略)~
$stt = $this->dbo->prepare($sql);
$stt->execute($param);
$data = $stt->fetchAll(PDO::FETCH_ASSOC);
これで「カラム名で添字を付けた配列」が結果セットに返ってきます。
API 等から JSON データを取得
近年、API 等で利用する頻度も高い JSON データの取得、特に何も指定せずに json_decode でパースすると、オブジェクト型で変数に格納される。
json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed
今回は配列型でデータを持ちたいので、第2引数の $associative に true を指定してやると良い。
~(中略)~
$data = json_decode($json, true);
中身はこんな感じ
双方の中身を確認した所、以下のように同じものが格納されていたので準備完了とする。
~(中略)~
var_dump($data);
/*
array(3) {
[0]=>
array(3) {
["ID"]=>
string(1) "1"
["NAME"]=>
string(4) "hoge"
["FAVO"]=>
string(5) "oniku"
}
[1]=>
array(3) {
["ID"]=>
string(1) "2"
["NAME"]=>
string(4) "piyo"
["FAVO"]=>
string(5) "yasai"
}
[2]=>
array(3) {
["ID"]=>
string(1) "3"
["NAME"]=>
string(4) "foo"
["FAVO"]=>
string(5) "oniku"
}
}
*/
配列に含まれる指定キーの値を検索するコード
取り合えず今回のお題を達成できるコードは以下の通り、非常にシンプルな書き方で実現出来る。
// $data には前項で取得した配列が格納されている
$key = array_search("3", array_column($data, "ID"), true);
$row = $key !== false ? $data[$key]: false;
SQL の WHERE 句みたいなイメージ
このコードは SQL のクエリに当てはめて考えると、それぞれの役割が理解しやすいかも知れない。
// PHP
$key = array_search(<検索する値>, array_column(<テーブル名>, <検索カラム名>), true);
<レコード内容> = $key !== false ? $data[$key]: false;
-- MySQL
SELECT <レコード内容> FROM <テーブル名> WHERE <検索カラム名> = <検索する値> LIMIT 1;
こんな感じ。
SQL クエリ側に「LIMIT 1」と書かれているのは、最初に発見したレコードのキーを返すと言う、PHP の array_search の仕様に合わせるため。
これは要するに、必ず単一の結果しか返ってこないと言う事だ。
当然だが、データセットが「ユニークキー」を持っている仕様(同じキーが 2 つ以上無い)でないと、期待する結果は得られないので、参考にする場合は注意されたし。
冒頭でこう書いたのはこの仕様が原因だが、プライマリキー等の「ユニークな値を検索する」と言う用途なら問題無く利用できる。
関数化
検索の度にいちいち書くのは非効率的なので、2回以上実行するなら関数化しておくと良さそうだ。
function searchID(&$data, $id) {
$key = array_search($id, array_column($data, "ID"), true);
return $key !== false ? $data[$key]: false;
}
var_dump(searchID($data, "3"));
/*
array(3) {
["ID"]=>
string(1) "3"
["NAME"]=>
string(3) "foo"
["FAVO"]=>
string(5) "oniku"
}
*/
var_dump(searchID($data, "4"));
/*
bool(false)
*/
先に実行される array_column
折角なので何故こうなるかも勉強しておく。
array_column(array $array, int|string|null $column_key, int|string|null $index_key = null): array
この関数は、第1引数に検索したい配列(前述の<テーブル名>)を指定し、第2引数に取得したいキー(前述の<検索カラム名>)を指定すると、対応する値が配列となって返って来る。
ちょっと $data の中身を見やすく整理して、array_column の実行結果と見比べてみよう。
$data = [
["ID" => "1", "NAME" => "hoge", "FAVO" => "oniku"], // レコード #0
["ID" => "2", "NAME" => "piyo", "FAVO" => "yasai"], // レコード #1
["ID" => "3", "NAME" => "foo", "FAVO" => "oniku"] // レコード #2
];
print_r(array_column($data, "ID"));
/*
Array
(
[0] => 1
[1] => 2
[2] => 3
)
*/
このように、ID の中身が配列になって返ってきている事が確認できる。
この返ってきた配列のキーは、それぞれ $data の「レコード #0~#2 のキーと等しい」と言うのがポイントになっている。
次に実行される array_search
array_column が解決されると、次に実行されるのが array_search となる。
array_search(mixed $needle, array $haystack, bool $strict = false): int|string|false
第1引数に検索したい値(前述の<検索する値>)を渡し、第2引数に検索したい配列(前述の array_column の結果)が渡される。
そして、検索する値が見つかればそのキーを、見つからなかったら false を返してくる。
なお、第3引数の $strict に true を指定すると、比較が厳密化(型比較など)するので、管理人的には true を指定する事をおススメする。(※PHP の暗黙の型変換は、思わぬバグを招く事が稀に良くある。)
array_column の実行結果を見やすく整理して、array_search の実行結果を見比べてみよう。
$column = [
"1", // array_column の実行結果 #0
"2", // array_column の実行結果 #1
"3" // array_column の実行結果 #2
];
$key = array_search("3", $column, true);
var_dump($key);
/*
int(2)
*/
値が文字列の “3" と等しいのは実行結果 #2(キー:2) 、と言う事が分かる。
そして、この実行結果のキーは大元の $data のキーと等しいため、array_search で得られたキーを $data のキーとする事で、検索元のデータセット(前述の<レコード内容>)が取得できる、と言う仕組みだ。
$row = $key !== false ? $data[$key]: false;
$key には 0 も false も入る可能性があるので、厳密な比較が必要な事も忘れないように。
関連記事