array_walkで配列を書き換えるときの参照渡しの罠
array_walkは、配列の各要素にコールバック関数を適用して処理する関数です。値を書き換えたいときはコールバックの第一引数を参照(&)で受け取る必要があり、これを忘れると何も変わりません。
array_mapとの使い分けで迷った経験がある方は多いんじゃないでしょうか。array_mapは新しい配列を返すので副作用がなく安全ですが、array_walkは元の配列を直接いじる前提の関数です。用途が違うので、どちらが「正しい」という話でもないんですよね。
array_walkの基本の形
まずは素の形から見てみます。コールバックの第一引数が値、第二引数がキーです。
$prices = [100, 200, 300];
array_walk($prices, function ($value, $key) {
echo "{$key}: {$value}\n";
});
// 0: 100
// 1: 200
// 2: 300
これだけだと単なる読み取りです。元の配列は一切変わりません。値を変更するには、コールバックの引数に参照を付ける必要があります。
なぜ参照を付けないと書き換わらないのか
PHPの関数呼び出しは基本的に値渡しです。array_walkがコールバックに渡すのも、素のままなら値のコピーになります。だから参照(&)を明示しない限り、コールバック内でいくら$valueを書き換えても呼び出し元の配列には反映されません。
$prices = [100, 200, 300];
// 参照を付ける
array_walk($prices, function (&$value) {
$value += 10; // 一律10円上乗せする
});
print_r($prices);
// [110, 210, 310]
&$valueにしたことで、コールバックの中の書き換えが元の配列の要素そのものに対する操作になります。「一律10円上乗せ」のような、既存の値をそのまま更新したい処理でよく使う形です。ここが一番のハマりどころで、「array_walkを使ったのに配列が変わらない」というバグの九割方はこの参照忘れです。
キーは変えられない、要素の追加・削除もできない
array_walkで自由に変えられるのは値だけです。公式ドキュメントにも明記されていますが、コールバックの中で配列に要素を足したり、キーを付け替えたりすることは想定されていません。構造をいじりたい場合はforeachやarray_map、あるいは新しい配列を組み立てる書き方に切り替えたほうが素直です。
userdataで第三引数を渡す
コールバックには第三引数としてuserdataを渡せます。外部の値をクロージャのuseで持ち込んでもいいのですが、array_walk側の仕組みとして用意されているのがこれです。
$items = ['apple', 'banana', 'cherry'];
array_walk($items, function (&$value, $key, $prefix) {
$value = "{$prefix}: {$value}";
}, 'fruit');
print_r($items);
// ["fruit: apple", "fruit: banana", "fruit: cherry"]
use句でも同じことはできますが、userdataのほうがコールバックの使い回しがしやすい場面もあります。好みの範囲だと思います。
ネストした配列にはarray_walk_recursiveを使う
array_walkは1階層しか見ません。多次元配列の中身まで触りたいときはarray_walk_recursiveを使います。
$data = [
'a' => 1,
'b' => [2, 3, ['c' => 4]],
];
array_walk_recursive($data, function (&$value) {
$value *= 10;
});
print_r($data);
// ['a' => 10, 'b' => [20, 30, ['c' => 40]]]
ただしarray_walk_recursiveも、配列そのもの(入れ子の配列自体)はコールバックに渡ってきません。渡ってくるのはスカラー値の末端だけです。ネスト構造を変えたい処理には向かないので、そこは注意が必要です。
array_mapで済むならarray_mapのほうが安全
元の配列を壊さずに変換結果だけ欲しいなら、array_mapのほうが素直です。array_walkは「既存の配列を直接更新したい」という明確な意図があるときに使う関数、くらいの位置づけで考えています。参照渡しを使う分、コードを読む側からすると副作用が見えにくくなるので、チームで書くコードなら特に、必要な場面に絞って使うのがいいと思います。
まとめ
array_walkは配列を直接書き換えるための関数で、コールバックの第一引数を&$valueにしない限り何も変わりません。キーの変更や要素の追加・削除はできず、多次元配列にはarray_walk_recursiveが必要です。新しい配列が欲しいだけならarray_mapで十分なことが多く、array_walkは「元の配列そのものを更新したい」という場面に絞って使うのが個人的にはしっくりきています。
よくある質問
Q. array_walkとarray_mapの違いは何ですか?
A. array_walkは元の配列を参照渡しで直接書き換える関数で、戻り値は成功・失敗を表すbool値です。array_mapは元の配列を変更せず、変換結果を新しい配列として返します。
Q. array_walkのコールバックで配列にキーを追加できますか?
A. できません。array_walkで変更できるのは既存要素の値だけで、キーの追加・削除・並び替えはできない仕様になっています。
Q. array_walk_recursiveでネストした配列自体を書き換えることはできますか?
A. 基本的にはスカラー値の末端にしかコールバックが呼ばれないため、入れ子の配列構造自体を組み替える処理には向いていません。構造を変えたい場合はforeachで再帰処理を自分で書くほうが安全です。