Aikido

PHPでarray_filter()をarray_values()でラップすべき理由

バグのリスク

ルール
ラップ 配列 フィルタリング 結果  array_values()
関数 のような array_filter() 保持する 元の キーを
これと同様に  バグを バグ  コードが コードが 数値
数値 インデックス 開始 から 0

対応言語: PHP

はじめに

PHPの array_filter() 要素をフィルタリングする際に、数値インデックス付き配列の場合でも元の配列キーを保持します。フィルタリング後 [0 => 'a', 1 => 'b', 2 => 'c'] インデックス1を削除すると、得られるのは [0 => 'a', 2 => 'c'] 数値キーにギャップがある場合、0から始まる連続したインデックスを期待するコードは、アクセス時に破損します。 $array[1] または連続するインデックスに関する仮定で反復処理を行うこと。この動作は、フィルタリングによって再インデックス化された配列が返される他の言語の出身の開発者を驚かせます。

なぜ重要なのか

コードの保守性: 非連続な配列キーは、特定の条件下でのみ現れる微妙なバグを引き起こします。フィルタリングされていない配列では正常に動作するコードが、ある仮定に基づいてフィルタリング後に不可解に失敗することがあります。 count($array) - 1 有効な最大インデックスです。根本原因が不明瞭なため、これらの問題のデバッグは時間の無駄になります。3つの要素を持つ配列を見ても、インデックス1で2番目の要素にアクセスできないからです。

JSONエンコーディングの問題: ~の場合 json_encode() 非連続なキーを持つ配列の場合、PHPはそれを配列ではなくオブジェクトとして扱い、その結果、 {"0":"a","2":"c"} ~の代わりに ["a","c"]。JSON配列を期待するフロントエンドコードが代わりにオブジェクトを受け取り、イテレーションや配列メソッドが機能しなくなります。PHP配列とJavaScript配列のこの不一致は、フィルタリング操作後にのみ現れる統合バグを引き起こします。

イテレーションとページネーションのエラー: 結果をページ分割したり、配列をチャンクに分割したりするコードは、キーが連続していない場合に機能しなくなります。0からループする count($array) 未定義のインデックスにアクセスします。使用中 array_slice() ページネーションの場合、位置に基づいて動作するものの元のキーを返すため、予期せぬ結果が生じます。これらのバグは、複雑なデータ処理パイプラインで複合的に発生します。

コード例

❌ 非準拠:

function getActiveUsers(array $users): array {
    $activeUsers = array_filter($users, function($user) {
        return $user['status'] === 'active';
    });

    // Bug: assumes index 0 exists and keys are sequential
    $firstActive = $activeUsers[0] ?? null;

    // Bug: JSON encodes as object if keys aren't sequential
    return json_encode($activeUsers);
}

$users = [
    ['id' => 1, 'status' => 'inactive'],
    ['id' => 2, 'status' => 'active'],
    ['id' => 3, 'status' => 'active']
];

// Returns {"1":{"id":2...},"2":{"id":3...}} (object, not array)
getActiveUsers($users);

誤っている理由: フィルタリングによってインデックス 0 が削除された後、配列にはキーがあります。 [1, 2] ~の代わりに [0, 1], となり $activeUsers[0] 未定義。JSONエンコーディングは、キーが連続的ではないため、配列ではなくオブジェクトを生成し、配列を期待するフロントエンドコードを破損させます。

✅ 準拠済み:

function getActiveUsers(array $users): string {
    $activeUsers = array_filter($users, function($user) {
        return $user['status'] === 'active';
    });

    // Reindex to sequential keys starting from 0
    $activeUsers = array_values($activeUsers);

    // Now index 0 always exists for non-empty arrays
    $firstActive = $activeUsers[0] ?? null;

    // JSON encodes as proper array: [{"id":2...},{"id":3...}]
    return json_encode($activeUsers);
}

$users = [
    ['id' => 1, 'status' => 'inactive'],
    ['id' => 2, 'status' => 'active'],
    ['id' => 3, 'status' => 'active']
];

// Returns [{"id":2...},{"id":3...}] (array, as expected)
getActiveUsers($users);

これが重要である理由: array_values() フィルタリングされた配列を0から始まる連続した数値キーに再インデックスし、インデックスアクセスを予測可能にし、JSONエンコーディングがオブジェクトではなく配列を生成するようにします。この関数は、どの要素がフィルタリングされたかに関わらず、期待通りに動作します。

まとめ

常にラップしてください array_filter(), array_diff(), および同様の関数と array_values() 連続した数値インデックスが必要な場合。これにより、非連続キーによる微妙なバグを防ぎ、JSONエンコーディングがオブジェクトではなく配列を生成することを保証します。パフォーマンスコストは、デバッグ時間の節約と比較して無視できる程度です。

よくある質問

ご質問がありますか?

キーを保持し、array_values()が必要なPHPの配列関数はどれですか?

array_filter()、array_diff()、array_diff_key()、array_intersect()、およびarray_intersect_key()はすべて元のキーを保持します。単一の配列パラメータを持つarray_map()のような関数もキーを保持します。連続したインデックスが必要な場合は、キーを保持すると文書化されているすべての関数をarray_values()でラップする必要があります。

array_values()はパフォーマンスに影響がありますか?

最小限です。array_values()は配列を一度反復処理して再インデックス化します。これはO(n)ですが、実際には非常に高速です。このコストは、フィルタリング操作自体と比較して無視できるほど小さく、非連続キーによる本番環境の問題をデバッグするよりもはるかに少ないです。常に早期最適化よりも正確性を優先してください。

文字列キーを持つ連想配列についてはどうでしょうか?

キーが意味のある文字列である連想配列で array_values() を使用しないでください。これは文字列キーを数値インデックスに置き換え、重要な情報を失うことになります。このルールは、0から始まる連続したキーが期待される数値インデックス配列にのみ適用されます。

代わりにarray_splice()や他の関数を使用できますか?

array_splice()は自動的に再インデックス化されますが、フィルタリングではなく挿入/削除を目的としています。フィルタリング操作には、array_values()と組み合わせたarray_filter()の方が明確で慣用的です。副作用として再インデックス化を得るために関数を悪用するのではなく、目的に合った適切なツールを使用してください。

コードベースにおいて、この問題をどのようにテストしますか?

数値インデックスで結果にアクセスされる、JSONにエンコードされる、またはシーケンシャルキーを期待する関数に渡されるarray_filter()の呼び出しを探してください。フィルタリングされた要素が配列の末尾にないデータでテストしてください。最初または中間の要素を削除するとバグが発生する場合、array_values()が必要です。

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です。 | スキャン結果は32秒で表示されます。