array_diffとarray_diff_keyとarray_diff_assocの違いを整理する

array_diffは値だけを比較して差分を取る関数で、array_diff_keyはキーだけ、array_diff_assocはキーと値の両方を比較する関数です。名前が似ている割に挙動がまったく違うので、混同するとバグの原因になります。

「配列の差分を取りたい」と思って何となくarray_diffを使ったら、キーがずれているのに気づかず変な結果になった、という経験がある方は多いんじゃないでしょうか。この3つは似ているようで見ている場所が違うので、いったん整理しておく価値はあると思います。

array_diffは値だけを見る

array_diffは、1番目の配列の要素のうち、2番目以降の配列に同じ値が存在しないものだけを返します。キーは一切見ません。

$a = ['x' => 'apple', 'y' => 'banana', 'z' => 'cherry'];
$b = ['p' => 'banana', 'q' => 'cherry'];

print_r(array_diff($a, $b));
// ['x' => 'apple']

キー名がまったく違っても、値が一致していれば「同じもの」として除外されます。値の集合として差分を取りたいときには便利ですが、「同じキーの値を比較したい」という意図で使うと、思っていたのと違う結果になりがちです。

なぜキーがずれても気にせず比較するのか

array_diffは内部的に値を文字列にキャストして比較します。配列の「集合」としての差分を目的にした関数なので、キーの対応関係はそもそも考慮の対象外という設計です。値が数値や文字列ならこれで困ることは少ないですが、値がオブジェクトや配列だと文字列化がうまくいかず警告や意図しない結果になるので、スカラー値の配列で使うのが基本です。

array_diff_keyはキーだけを見る

array_diff_keyは逆に、値を無視してキーだけで比較します。1番目の配列のキーのうち、2番目以降の配列に同じキーが存在しないものを返します。

$a = ['id' => 1, 'name' => 'taro', 'age' => 20];
$b = ['id' => 0, 'name' => ''];

print_r(array_diff_key($a, $b));
// ['age' => 20]

値のidが1と0で違っていても、キーとしては両方に’id’が存在するので差分には出てきません。「必須キーが揃っているか確認したい」「不要なキーだけ抜き出したい」といった、値の中身を問わずキーの集合で扱いたい場面に向いています。連想配列から特定のキーだけを除いた新しい配列を作るときにもよく使います。

$input = ['name' => 'taro', 'age' => 20, 'csrf_token' => 'abc'];

$safe = array_diff_key($input, ['csrf_token' => null]);
// ['name' => 'taro', 'age' => 20]

除外用の配列側の値は何でもよく、キーが存在するかどうかしか見られません。この「除外リストのキーだけ揃えておけばいい」という手軽さが、実務で地味に使い勝手がいいところだと思います。

array_diff_assocはキーと値の両方を見る

array_diff_assocは、キーと値の組み合わせが一致しているかどうかで判定します。キーが同じでも値が違えば差分として残りますし、値が同じでもキーが違えば差分として残ります。

$a = ['id' => 1, 'name' => 'taro', 'age' => 20];
$b = ['id' => 1, 'name' => 'jiro', 'age' => 20];

print_r(array_diff_assoc($a, $b));
// ['name' => 'taro']

idとageはキー・値とも一致しているので消え、nameはキーは同じでも値が違うので残ります。「同じキーに対して値が変わった項目だけ知りたい」というときは、この関数が一番目的に合っています。フォームの入力値と元データを比較して「どの項目が変更されたか」を検出する処理などで使う場面があります。

3つを混同すると起きること

実務でよくあるのは、キー付きの連想配列同士で「変更点を出したい」つもりでarray_diffを使ってしまうケースです。値だけで比較するので、キーがずれていても値さえ一致すれば差分から消えてしまい、本来検出したい変更が漏れることがあります。逆に、値の集合として差分を取りたいのにarray_diff_assocを使うと、キーの並びが違うだけで別物として扱われ、余計な差分が大量に出てしまいます。どの軸で比較したいのかを先に決めてから関数を選ぶ、というくらいの意識でちょうどいい気がしています。

キー・値どちらも自作の比較にしたいときはudiff系

標準の比較では足りない場合、array_udiff(値をコールバックで比較)、array_diff_ukey(キーをコールバックで比較)、array_udiff_assoc・array_diff_uassoc(キーと値を組み合わせてコールバックで比較)といった関数も用意されています。オブジェクトのプロパティ単位で差分を取りたいときなどは、こちらを検討する価値があります。

まとめ

array_diffは値だけ、array_diff_keyはキーだけ、array_diff_assocはキーと値の両方を比較します。見た目が似た関数ですが、比較の軸がまったく違うので、「何を基準に差分を取りたいのか」を先に決めてから選ぶのが結局は一番早いと思います。迷ったときは小さいサンプルデータで一度print_rして確認する、という地味な方法が案外確実です。

よくある質問

Q. array_diffとarray_diff_assocの違いは何ですか?
A. array_diffは値だけを比較し、キーが違っても値が一致すれば同じものとして扱います。array_diff_assocはキーと値の両方が一致しないと同じものとみなしません。

Q. 連想配列から特定のキーだけを除いた配列を作りたい場合はどうすればいいですか?
A. array_diff_keyに、除外したいキーを持つ配列を渡すと簡単に作れます。除外用配列の値は何でもよく、キーの有無だけが判定に使われます。

Q. 値がオブジェクトの配列でもarray_diffは使えますか?
A. array_diffは値を内部で文字列にキャストして比較するため、オブジェクトや配列を値に含む配列では意図した比較にならないことがあります。その場合はarray_udiffでコールバックによる比較を指定するのが安全です。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です