- PHP
PHP array_map/filter/reduceの使い方とキー維持早見表
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期連続の黒字決算。