array_map は「全要素を変換する」、array_filter は「条件に合う要素だけ残す」、array_reduce は「全要素を1つの値にまとめる」関数です。 どれもコールバック(処理を書いた関数)を渡して使いますが、array_map だけコールバックが第1引数で、ほかの2つは配列が先です。また、array_filter は元のキーをそのまま残すため、絞り込んだあとに添字が飛び、json_encode の結果が配列ではなくオブジェクトになることがあります。

この記事では、3関数の引数順・戻り値・キーの扱いを1枚の早見表にまとめ、すべてのサンプルに PHP 8.4.21 で実行した var_dump の結果を付けました。アロー関数 fn、第一級 callable 構文(strlen(...))、ARRAY_FILTER_USE_KEY / USE_BOTH、foreach との書き換え対応、PHP 8.4 で追加された array_find / array_any / array_all まで、コピーしてそのまま動く形で紹介します。

array_map・array_filter・array_reduceの早見表

関数 やること 引数の順番 コールバックが受け取るもの 戻り値 キーは維持されるか
array_map 全要素を変換 コールバック, 配列(配列は複数可) 値(配列を複数渡すとそれぞれの値) 同じ件数の配列 配列1つなら維持。2つ以上渡すと 0 からの連番
array_filter 条件で絞り込む 配列, コールバック, モード 値(モードでキー、または値とキー) 件数が減った配列 維持(添字が飛ぶ)
array_reduce 1つの値に集約 配列, コールバック, 初期値 途中結果と値 任意の値(数値・文字列・配列など) ― (配列を返すとは限らない)

PHP 8.4.21 でリフレクションから取得した実際のシグネチャです。

array_map(?callable $callback, array $array, array ...$arrays): array
array_filter(array $array, ?callable $callback = NULL, int $mode = 0): array
array_reduce(array $array, callable $callback, mixed $initial = NULL): mixed
やりたいこと 書き方
全要素を2倍にする array_map(fn($n) => $n * 2, $nums)
8以上だけ残す array_filter($nums, fn($n) => $n >= 8)
残したあと添字を 0 から振り直す array_values(array_filter($nums, fn($n) => $n >= 8))
合計を出す array_reduce($nums, fn($carry, $n) => $carry + $n, 0)
キーで絞り込む array_filter($arr, fn($k) => $k !== 'password', ARRAY_FILTER_USE_KEY)
値とキーの両方で絞り込む array_filter($arr, fn($v, $k) => ..., ARRAY_FILTER_USE_BOTH)
array_map でキーも使う array_map(fn($k, $v) => "$k=$v", array_keys($arr), $arr)
既存の関数をそのまま渡す array_map(strlen(...), $words)
最初の1件だけ探す(PHP 8.4〜) array_find($nums, fn($n) => $n >= 8)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PHP array_map/filter/reduceの使い方とキー維持早見表」

3つの関数の基本を実行結果で確認する

以降のサンプルは、次の配列を使います。ファイルの先頭に置けば、各コードをそのまま貼って実行できます。

<?php
$prices = [100, 250, 400];
$nums   = [5, 12, 8, 20, 3];

array_mapで全要素を変換する

var_dump(array_map(fn($p) => $p * 2, $prices));
array(3) {
  [0]=>
  int(200)
  [1]=>
  int(500)
  [2]=>
  int(800)
}

元の配列と同じ件数の、新しい配列が返ります。 元の $prices は変わりません。

array_filterで条件に合う要素だけ残す

$big = array_filter($nums, fn($n) => $n >= 8);
var_dump($big);
array(3) {
  [1]=>
  int(12)
  [2]=>
  int(8)
  [3]=>
  int(20)
}

コールバックが true を返した要素だけが残ります。キーが [1] [2] [3] のままで、[0] がない点に注目してください。これが次の章で扱う「添字が飛ぶ」状態です。

array_reduceで1つの値にまとめる

var_dump(array_reduce($nums, fn($carry, $n) => $carry + $n, 0));
int(48)

コールバックの第1引数 $carry は「ここまでの途中結果」、第2引数は「今の要素」です。第3引数の 0 が最初の $carry になり、5 → 17 → 25 → 45 → 48 と順に足されていきます。

回 $carry(途中結果) $n(今の要素) 返す値
1 0(初期値) 5 5
2 5 12 17
3 17 8 25
4 25 20 45
5 45 3 48

引数の順番を逆にしたときのエラー

array_map だけコールバックが先、という違いは間違えやすい点です。逆に書くと次の TypeError になります。

array_map($prices, fn($p) => $p * 2);
array_filter(fn($n) => $n > 1, $nums);
TypeError: array_map(): Argument #1 ($callback) must be a valid callback or null, array callback must have exactly two members
TypeError: array_filter(): Argument #1 ($array) must be of type array, Closure given

「Argument #1 ($callback) must be a valid callback」と出たら array_map の引数順、「Argument #1 ($array) must be of type array, Closure given」と出たら array_filter / array_reduce の引数順を見直します。array_map がコールバックを先に取るのは、うしろに配列をいくつでも並べられるようにするためです。

キーが維持されるかどうかを整理する

array_filter後にjson_encodeがオブジェクトになる現象

API のレスポンスを作るときによく出会う現象を再現します。

$nums = [5, 12, 8, 20, 3];
$big  = array_filter($nums, fn($n) => $n >= 8);

echo json_encode($nums), "\n";
echo json_encode($big), "\n";
[5,12,8,20,3]
{"1":12,"2":8,"3":20}

絞り込む前は配列 [ ] だったのに、絞り込んだあとは { } のオブジェクトになりました。 json_encode は「キーが 0 から始まる連番」のときだけ JSON の配列として出力し、それ以外は連想配列とみなしてオブジェクトにします。array_filter は元のキー 1・2・3 を残すので、0 から始まらなくなったのが理由です。

受け取る側の JavaScript では、配列のつもりで map や length を使うと期待と違う動きになります。

array_valuesで添字を振り直す

echo json_encode(array_values($big)), "\n";
var_dump(array_is_list($big), array_is_list(array_values($big)));
[12,8,20]
bool(false)
bool(true)

array_values で包むと、キーが 0 からの連番に振り直され、JSON の配列に戻ります。 array_is_list(PHP 8.1〜)は「0 からの連番か」を調べる関数で、json_encode が配列として出すかどうかの確認に使えます。

先頭の要素が残った場合でも、途中が抜ければ同じ現象になります。

echo json_encode(array_filter($nums, fn($n) => $n < 10)), "\n";
{"0":5,"2":8,"4":3}

「テストデータではたまたま連番のまま残って配列になり、別のデータではオブジェクトになる」ということが起こるので、JSON で返す配列や、for 文で回す配列は、array_filter のあとに array_values を付けると決めておくと安定します。

for 文で回した場合の動きも確かめておきます。

$b = array_filter($nums, fn($n) => $n >= 8);
for ($i = 0; $i < count($b); $i++) {
    echo $i, ':', $b[$i] ?? '(none)', "\n";
}
0:(none)
1:12
2:8

count は 3 ですが、キーは 1・2・3 なので、0 番は存在せず 3 番(20)には届きません。?? を付けずに $b[0] を読むと Warning: Undefined array key 0 が出ます。foreach なら添字に関係なく全要素を回れます。

array_mapのキーは「配列を1つ渡したか」で決まる

array_map は、配列を1つだけ渡したときはキーを維持します。

var_dump(array_map(fn($v) => $v * 2, ['a' => 1, 'b' => 2]));
var_dump(array_map(fn($v) => $v * 2, [3 => 1, 7 => 2]));
array(2) {
  ["a"]=>
  int(2)
  ["b"]=>
  int(4)
}
array(2) {
  [3]=>
  int(2)
  [7]=>
  int(4)
}

文字列キーも、飛び飛びの数値キーもそのままです。そのため、array_filter の結果を array_map に渡しても、飛んだ添字は飛んだままになります。

var_dump(array_map(fn($n) => $n * 2, $big));
array(3) {
  [1]=>
  int(24)
  [2]=>
  int(16)
  [3]=>
  int(40)
}

配列を2つ以上渡すと、キーは 0 からの連番になります。

var_dump(array_map(fn($a, $b) => "$a:$b", ['x' => 'A', 'y' => 'B'], [10, 20]));
array(2) {
  [0]=>
  string(4) "A:10"
  [1]=>
  string(4) "B:20"
}

キー維持のまとめ表

操作 入力のキー 出力のキー json_encode の形
array_map(配列1つ) 0,1,2 0,1,2 配列
array_map(配列1つ) 'a','b' 'a','b' オブジェクト
array_map(配列2つ以上) 何でも 0,1,2…に振り直し 配列
array_filter 0,1,2,3,4 残った要素の元のキー(例:1,2,3) オブジェクトになることがある
array_filter → array_values 0,1,2,3,4 0,1,2 配列
array_filter → array_map 0,1,2,3,4 飛んだまま オブジェクトになることがある
array_reduce ― コールバックの作り方しだい ―

絞り込みと変換を続けて行い、JSON にする例です。

$orders = [
    ['id' => 1, 'status' => 'paid', 'total' => 1200],
    ['id' => 2, 'status' => 'new',  'total' => 800],
    ['id' => 3, 'status' => 'paid', 'total' => 3000],
];
$paidIds = array_map(
    fn($o) => $o['id'],
    array_filter($orders, fn($o) => $o['status'] === 'paid')
);
echo json_encode($paidIds), "\n";
echo json_encode(array_values($paidIds)), "\n";
{"0":1,"2":3}
[1,3]

array_filterのモードとコールバック省略

ARRAY_FILTER_USE_KEYでキーを条件にする

第3引数に ARRAY_FILTER_USE_KEY を渡すと、コールバックには値ではなくキーが渡されます。

$user = ['id' => 7, 'name' => 'Sato', 'password' => 'secret', 'email' => 'sato@example.com'];
var_dump(array_filter($user, fn($k) => $k !== 'password', ARRAY_FILTER_USE_KEY));
array(3) {
  ["id"]=>
  int(7)
  ["name"]=>
  string(4) "Sato"
  ["email"]=>
  string(16) "sato@example.com"
}

「残すキー」「外すキー」が決まっている場合は、専用の関数でも同じ結果が得られます。

$user = ['id' => 7, 'name' => 'Sato', 'password' => 'secret'];
var_dump(array_intersect_key($user, array_flip(['id', 'name']))); // 残すキーを指定
var_dump(array_diff_key($user, ['password' => 1]));               // 外すキーを指定

どちらも id と name の2件が残る同じ結果になりました。

ARRAY_FILTER_USE_BOTHで値とキーの両方を使う

$stock = ['pen' => 0, 'note' => 12, 'tmp_box' => 5, 'eraser' => 3];
var_dump(array_filter(
    $stock,
    fn($v, $k) => $v > 0 && !str_starts_with($k, 'tmp_'),
    ARRAY_FILTER_USE_BOTH
));
array(2) {
  ["note"]=>
  int(12)
  ["eraser"]=>
  int(3)
}

引数の順番は「値, キー」です。foreach の「キー => 値」とは逆なので、書くときに一度確認すると安心です。

モード コールバックの引数 使う場面
0(省略時) 値 値で絞り込む
ARRAY_FILTER_USE_KEY キー キー名で絞り込む
ARRAY_FILTER_USE_BOTH 値, キー 値とキーの両方で絞り込む

コールバックを省略すると0や空文字も消える

コールバックを省略すると、「空とみなされる値」がすべて取り除かれます。

var_dump(array_filter([1, 0, 2, null, '', '0', 'a', false, [], 0.0, ' ']));
array(4) {
  [0]=>
  int(1)
  [2]=>
  int(2)
  [6]=>
  string(1) "a"
  [10]=>
  string(1) " "
}

null や空文字だけでなく、数値の 0、文字列の '0'、false、空配列、0.0 も消えています。 半角スペース1文字の ' ' は残りました。

値 コールバック省略時
null、''(空文字)、false、[](空配列) 取り除かれる
0、0.0、'0' 取り除かれる
' '(スペース)、'a'、1、2 残る

在庫数の 0 や、アンケートの回答「0」のように、0 に意味があるデータでは条件を明示します。

var_dump(array_filter([1, 0, null, '', '0'], fn($v) => $v !== null && $v !== ''));
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(0)
  [4]=>
  string(1) "0"
}

コールバックの書き方4種類

アロー関数fnと無名関数functionの違い

アロー関数(PHP 7.4〜)は、外側の変数を use なしでそのまま読めるのが特徴です。

$prices = [100, 250, 400];
$rate = 1.1;

// アロー関数:$rate をそのまま使える
var_dump(array_map(fn($p) => (int) round($p * $rate), $prices));

// 無名関数:use ($rate) が必要
var_dump(array_map(function ($p) use ($rate) {
    return (int) round($p * $rate);
}, $prices));
array(3) {
  [0]=>
  int(110)
  [1]=>
  int(275)
  [2]=>
  int(440)
}

どちらも同じ結果です(2つ目の出力は同じ内容のため省略しています)。

項目 アロー関数 fn 無名関数 function
書ける処理 式を1つだけ(その値が戻り値) 複数の文を書ける
外側の変数 自動で値が取り込まれる use ($x) で明示する
外側の変数を書き換える できない(値のコピー) use (&$x) で参照渡しにすればできる
向いている場面 1行で済む変換・条件 途中で変数を使う、複数行の処理

アロー関数が取り込むのは値のコピーなので、中で書き換えても外側には反映されません。

$count = 0;
array_map(fn($n) => $count++, [1, 2, 3]);
var_dump($count);
int(0)

件数を数えたいときは、array_reduce や count(array_filter(...)) を使うと意図どおりになります。

第一級callable構文 strlen(...) で既存の関数を渡す

PHP 8.1 から、関数名のあとに (...) を付けると、その関数をコールバックとして渡せます。

var_dump(array_map(strlen(...), ['php', 'array', '']));
var_dump(array_filter(['10', 'abc', 3.5, null, '7'], is_numeric(...)));
array(3) {
  [0]=>
  int(3)
  [1]=>
  int(5)
  [2]=>
  int(0)
}
array(3) {
  [0]=>
  string(2) "10"
  [2]=>
  float(3.5)
  [4]=>
  string(1) "7"
}

文字列で 'strlen' と書く方法も動きますが、(...) 構文はエディタの補完や静的解析が効き、関数名の打ち間違いにその場で気づけます。メソッドも同じ書き方で渡せます。

class Tax
{
    public function __construct(private float $rate) {}
    public function add(int $p): int { return (int) round($p * (1 + $this->rate)); }
    public static function half(int $p): int { return intdiv($p, 2); }
}
$tax = new Tax(0.1);
var_dump(array_map($tax->add(...), [100, 250]));
var_dump(array_map(Tax::half(...), [100, 250]));
array(2) {
  [0]=>
  int(110)
  [1]=>
  int(275)
}
array(2) {
  [0]=>
  int(50)
  [1]=>
  int(125)
}
書き方 例 使える版
アロー関数 fn($n) => $n * 2 PHP 7.4〜
無名関数 function ($n) use ($rate) { return $n * $rate; } PHP 5.3〜
第一級 callable 構文 strlen(...)、$tax->add(...)、Tax::half(...) PHP 8.1〜
文字列・配列での指定 'strtoupper'、[$tax, 'add']、'Tax::half' 以前から

文字列・配列での指定も PHP 8.4.21 で動くことを確認しました('strtoupper' で A・B、[$tax, 'add'] で 110、'Tax::half' で 50)。

array_mapの応用:キーを使う・複数配列を渡す

array_mapでキーを使う方法

array_map のコールバックには値しか渡されません。キーも使いたいときは、array_keys で取り出したキーの配列を一緒に渡します。

$stock = ['pen' => 3, 'note' => 12];
var_dump(array_map(fn($k, $v) => "$k=$v", array_keys($stock), $stock));
array(2) {
  [0]=>
  string(5) "pen=3"
  [1]=>
  string(7) "note=12"
}

配列を2つ渡したので、結果のキーは 0 からの連番になります。元のキーを保ちたいときは array_combine で付け直します。

var_dump(array_combine(
    array_keys($stock),
    array_map(fn($k, $v) => "$k:$v", array_keys($stock), $stock)
));
array(2) {
  ["pen"]=>
  string(5) "pen:3"
  ["note"]=>
  string(7) "note:12"
}

コールバックにnullを渡して配列を束ねる

コールバックに null を渡すと、複数の配列を同じ位置どうしで束ねた配列が得られます。

var_dump(array_map(null, [1, 2], ['a', 'b']));
array(2) {
  [0]=>
  array(2) {
    [0]=>
    int(1)
    [1]=>
    string(1) "a"
  }
  [1]=>
  array(2) {
    [0]=>
    int(2)
    [1]=>
    string(1) "b"
  }
}

配列の長さが違うとき・引数の数が合わないとき

長さが違う配列を渡すと、足りない側は null で埋められます。

var_dump(array_map(fn($a, $b) => [$a, $b], [1, 2, 3], ['a']));

2件目と3件目の $b は NULL になりました([1, "a"]、[2, NULL]、[3, NULL])。

コールバックの引数が2つなのに配列を1つしか渡さないと、次のエラーになります(ファイルのパスは環境に合わせて置き換えています)。

array_map(fn($a, $b) => $a + $b, [1, 2]);
ArgumentCountError: Too few arguments to function {closure:/home/taro/p2.php:25}(), 1 passed and exactly 2 expected

コールバックの引数の数と、渡す配列の数をそろえるのがポイントです。

array_mapは元の配列を変えない

$a = [1, 2];
$b = array_map(fn($v) => $v * 10, $a);
var_dump($a, $b);

$a は [1, 2] のまま、$b が [10, 20] になりました。元の配列そのものを書き換えたいときは、参照で受け取る array_walk を使います。

array_walk($a, function (&$v, $k) { $v = $v * 10; });
var_dump($a);
array(2) {
  [0]=>
  int(10)
  [1]=>
  int(20)
}

array_walk のコールバックは「値, キー」の順で受け取るので、キーを使った処理にも向いています。

array_reduceの初期値と応用

初期値を省略すると最初の$carryはnullになる

var_dump(array_reduce([], fn($c, $n) => $c + $n));
var_dump(array_reduce([], fn($c, $n) => $c + $n, 0));
var_dump(array_reduce([1, 2, 3], fn($c, $n) => $c + $n));
NULL
int(0)
int(6)

空の配列で初期値を省略すると、結果は NULL になります。 合計を出すつもりで NULL が返ると、あとの計算や型宣言で止まる原因になります。要素があるときは null + 1 が 1 と計算されるので動きますが、コールバックに型宣言を付けると、最初の null で次のエラーになります。

array_reduce([1, 2, 3], fn(int $c, int $n): int => $c + $n);
TypeError: {closure:/home/taro/p.php:57}(): Argument #1 ($c) must be of type int, null given

(ファイルのパスは環境に合わせて置き換えています。)

作りたい結果 初期値
合計・件数 0
文字列の連結 ''(空文字)
配列(グループ化・索引づくり) []
最大値・最小値 省略(null)して、コールバック側で null を判定

array_reduce は初期値を必ず書く、と決めておくと、空配列でも結果の型が安定します。

連想配列をグループごとに集計する

array_reduce は、初期値を配列にすると「配列から別の形の配列を作る」処理に使えます。

$orders = [
    ['id' => 1, 'status' => 'paid', 'total' => 1200],
    ['id' => 2, 'status' => 'new',  'total' => 800],
    ['id' => 3, 'status' => 'paid', 'total' => 3000],
];
var_dump(array_reduce($orders, function ($c, $o) {
    $c[$o['status']] = ($c[$o['status']] ?? 0) + $o['total'];
    return $c;
}, []));
array(2) {
  ["paid"]=>
  int(4200)
  ["new"]=>
  int(800)
}

コールバックの最後で $c を return するのを忘れないことが大切です。return がないと、次の回の $c が null になります。

最大値・文字列連結・IDをキーにした配列

// 最大値(初期値なし。最初の回は $c が null)
var_dump(array_reduce([5, 12, 8], fn($c, $n) => $c === null || $n > $c ? $n : $c));

// 文字列の連結
var_dump(array_reduce(['a', 'b', 'c'], fn($c, $s) => $c . $s, ''));

// id をキーにした配列を作る
$users = [['id' => 7, 'name' => 'Sato'], ['id' => 9, 'name' => 'Abe']];
var_dump(array_reduce($users, function ($c, $u) {
    $c[$u['id']] = $u['name'];
    return $c;
}, []));
int(12)
string(3) "abc"
array(2) {
  [7]=>
  string(4) "Sato"
  [9]=>
  string(3) "Abe"
}

専用の関数があるときはそちらが短い

やりたいこと array_reduce 専用の関数
合計 array_reduce($a, fn($c, $n) => $c + $n, 0) array_sum($a)
最大値 上の例 max($a)
文字列の連結 上の例 implode('', $a)
特定の列を取り出す array_map で $o['total'] array_column($orders, 'total')
列をキーにした配列 上の例 array_column($orders, 'total', 'id')
$paid   = array_filter($orders, fn($o) => $o['status'] === 'paid');
$totals = array_map(fn($o) => $o['total'], $paid);
var_dump(array_sum($totals), array_reduce($totals, fn($c, $t) => $c + $t, 0));
var_dump(array_column($orders, 'total', 'id'));
int(4200)
int(4200)
array(3) {
  [1]=>
  int(1200)
  [2]=>
  int(800)
  [3]=>
  int(3000)
}

array_column は結果を 0 からの連番で返すので、array_filter のあとに使うと array_values を兼ねられます。json_encode(array_column(array_filter($orders, fn($o) => $o['status'] === 'paid'), 'id')) の結果は [1,3] でした。

foreachとの書き換え対応

3つの関数はどれも foreach で書けます。対応を知っておくと、既存のコードを読むときにも役立ちます。

処理 foreach 配列関数
変換 foreach ($nums as $k => $n) { $r[$k] = $n * 2; } array_map(fn($n) => $n * 2, $nums)
絞り込み foreach ($nums as $k => $n) { if ($n >= 8) { $r[$k] = $n; } } array_filter($nums, fn($n) => $n >= 8)
集約 foreach ($nums as $n) { $sum += $n; } array_reduce($nums, fn($c, $n) => $c + $n, 0)
最初の1件 foreach ($nums as $n) { if ($n >= 8) { $found = $n; break; } } array_find($nums, fn($n) => $n >= 8)

次のコードで、4組とも結果が完全に一致すること(=== で true)を確認しました。

$nums = [5, 12, 8, 20, 3];
$doubled = []; foreach ($nums as $k => $n) { $doubled[$k] = $n * 2; }
$big = [];     foreach ($nums as $k => $n) { if ($n >= 8) { $big[$k] = $n; } }
$sum = 0;      foreach ($nums as $n) { $sum += $n; }
$found = null; foreach ($nums as $n) { if ($n >= 8) { $found = $n; break; } }

var_dump(
    $doubled === array_map(fn($n) => $n * 2, $nums),
    $big === array_filter($nums, fn($n) => $n >= 8),
    $sum === array_reduce($nums, fn($c, $n) => $c + $n, 0),
    $found === array_find($nums, fn($n) => $n >= 8)
);
bool(true)
bool(true)
bool(true)
bool(true)

array_filter と同じ結果にするには、foreach 側で $r[$k] = $n と元のキーを使う必要があります。$r[] = $n と書けば 0 からの連番になり、array_values(array_filter(...)) と同じ形になります。

選び方の目安 向いている書き方
「変換」「絞り込み」「集約」のどれか1つで、1行で書ける 配列関数(意図が関数名で伝わる)
途中で break したい、複数の結果を同時に作りたい foreach
変換と絞り込みを同時に行い、件数が多い foreach(1回のループで済む)
最初の1件だけ欲しい array_find(PHP 8.4〜)か foreach + break

PHP 8.4のarray_find・array_any・array_all

PHP 8.4 で、探す・判定するための関数が4つ追加されました。array_filter で絞り込んでから先頭を取り出していた処理を、1つの関数で書けます。

$nums = [5, 12, 8, 20, 3];
var_dump(array_find($nums, fn($n) => $n >= 8));      // 最初に条件を満たした値
var_dump(array_find_key($nums, fn($n) => $n >= 8));  // そのキー
var_dump(array_find($nums, fn($n) => $n > 100));     // 見つからないとき
var_dump(array_any($nums, fn($n) => $n > 15));       // 1つでも満たすか
var_dump(array_all($nums, fn($n) => $n > 2));        // すべて満たすか
int(12)
int(1)
NULL
bool(true)
bool(true)
関数 戻り値 見つからない/空配列のとき
array_find 最初に条件を満たした値 null
array_find_key 最初に条件を満たした要素のキー null
array_any 1つでも満たせば true 空配列は false
array_all すべて満たせば true 空配列は true

空配列での結果も実行して確かめました(array_all([], ...) は true、array_any([], ...) は false)。

これらの関数のコールバックは、モード指定なしで「値, キー」の両方を受け取れます。

$stock = ['pen' => 0, 'note' => 12, 'tmp_box' => 5, 'eraser' => 3];
var_dump(array_find($stock, fn($v, $k) => str_starts_with($k, 'tmp_')));
int(5)

array_find は見つからないときに null を返すので、値として null が入りうる配列では、array_find_key でキーを調べると区別できます(PHP マニュアルの array_find のページにも同じ注意書きがあります)。PHP 8.3 以前では、foreach と break で同じ処理を書けます。

つまずきやすい点と直し方

起きること 原因 直し方
json_encode の結果が { } になる array_filter でキーが飛んだ array_values で振り直す
for 文で Undefined array key の警告 同上 array_values を付けるか foreach で回す
TypeError: Argument #1 ($callback) must be a valid callback array_map に配列を先に渡した コールバックを第1引数にする
TypeError: Argument #1 ($array) must be of type array, Closure given array_filter / array_reduce にコールバックを先に渡した 配列を第1引数にする
0 や '0' が消えた array_filter のコールバックを省略した 条件を明示する($v !== null && $v !== '')
array_reduce の結果が NULL 空配列で初期値を省略した 第3引数に 0 や [] を渡す
array_reduce の途中から null になる コールバックで return を書いていない 最後に return $c; を書く
array_map の結果のキーが 0,1,2 になった 配列を2つ以上渡した array_combine でキーを付け直す
アロー関数の中で外の変数を増やしても変わらない 値のコピーが取り込まれる array_reduce で集約するか、無名関数で use (&$x)
USE_BOTH で値とキーが逆になった 引数は「値, キー」の順 fn($v, $k) の順で書く

よくある質問

Q. array_map と foreach はどちらを使えばよいですか? A. 結果は同じにできるので、読みやすいほうを選んで構いません。「全要素を変換して新しい配列を作る」だけなら array_map のほうが意図が関数名で伝わります。途中で抜けたい、1回のループで複数の結果を作りたい、といった場合は foreach が書きやすくなります。

Q. array_filter のあとは必ず array_values を付けるべきですか? A. 連想配列(文字列キー)を絞り込む場合は不要です。0 からの連番の配列を絞り込み、その結果を JSON で返す、for 文で回す、$result[0] のように添字で読む、のいずれかに当てはまるときは array_values を付けます。迷ったら array_is_list で確認できます。

Q. array_reduce の初期値は省略してもよいですか? A. 動きはしますが、空配列のときに NULL が返ります。合計なら 0、配列を作るなら [] のように、作りたい結果と同じ型の初期値を渡しておくと、空配列でも型がそろいます。

Q. アロー関数で複数行の処理を書けますか? A. アロー関数に書けるのは式1つです。複数の文が必要なときは function (...) use (...) { ... } の無名関数を使います。アロー関数を入れ子にすること(fn($x) => fn($y) => $x * $y)は可能で、$mul(3)(4) が 12 になることを確認しました。

まとめ

  • array_map は変換、array_filter は絞り込み、array_reduce は集約。array_map だけコールバックが第1引数
  • array_filter は元のキーを維持するので、添字が飛んで json_encode がオブジェクトになることがある。配列として返すなら array_values を付ける
  • array_map は配列1つならキーを維持、2つ以上なら 0 からの連番。キーを使いたいときは array_keys を一緒に渡す
  • array_filter のコールバックを省略すると、0・'0'・false・空配列も消える。0 に意味があるデータでは条件を明示する
  • array_reduce は初期値を必ず渡す。空配列でも結果の型がそろい、コールバックに型宣言も付けられる
  • 1行の処理は fn、既存の関数は strlen(...)、複数行は function + use と使い分ける
  • PHP 8.4 以降は array_find / array_find_key / array_any / array_all で「最初の1件」「1つでも」「すべて」を1関数で書ける

参考・出典

  • PHP マニュアル array_map:キーが維持される条件、null コールバック、長さが違う配列の扱い
  • PHP マニュアル array_filter:キーの維持、コールバック省略時の動き、ARRAY_FILTER_USE_KEY / ARRAY_FILTER_USE_BOTH
  • PHP マニュアル array_find:PHP 8.4.0 で追加、コールバックの引数、null の扱い
  • 実行結果はすべて PHP 8.4.21(CLI)で取得

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「PHP array_map/filter/reduceの使い方とキー維持早見表」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?