PHPの多次元配列について

採用はこちら

PHPで複数のユーザー情報や商品データ、APIレスポンスなどを扱う際によく登場するのが、多次元配列です。

多次元配列は、配列の中にさらに配列を持つデータ構造です。

PHPでは非常によく使われる一方で、階層が深くなると値の取得方法やループ処理、並べ替え、削除などで混乱しやすくなります。

この記事では、PHPの多次元配列について、基本的な作り方から値の取得、追加・変更・削除、検索、並べ替え、JSON変換まで詳しく解説します。

コード例は、原則としてPHP 8.0以降を想定しています。

目次

PHPの配列の基本

PHPの配列は、キーと値を関連付けて管理するデータ構造です。

一般的には、添字配列と連想配列の2種類に分けて説明されます。

添字配列

添字配列は、0から始まる数値キーを使って値を管理する配列です。

$fruits = [
    'りんご',
    'みかん',
    'バナナ',
];

実際には、次のような数値キーが割り当てられています。

$fruits = [
    0 => 'りんご',
    1 => 'みかん',
    2 => 'バナナ',
];

値を取得する場合は、数値キーを指定します。

echo $fruits[0];

実行結果は次のとおりです。

りんご

連想配列

連想配列は、文字列をキーとして値を管理する配列です。

$user = [
    'name' => '田中太郎',
    'age' => 30,
    'email' => 'tanaka@example.com',
];

名前を取得する場合は、次のように記述します。

echo $user['name'];

実行結果は次のとおりです。

田中太郎

PHP内部では、添字配列と連想配列が別々の型として存在するわけではありません。

PHPの配列は、厳密にはキーと値を関連付ける順序付きマップです。

数値キーを中心に使う配列を添字配列、文字列キーを使う配列を連想配列と呼ぶのが一般的です。

PHPの多次元配列とは

多次元配列とは、配列の中に別の配列が格納されている構造です。

たとえば、複数のユーザー情報を管理する場合、次のように記述できます。

$users = [
    [
        'name' => '田中太郎',
        'age' => 30,
        'email' => 'tanaka@example.com',
    ],
    [
        'name' => '佐藤花子',
        'age' => 25,
        'email' => 'sato@example.com',
    ],
];

外側の配列には複数のユーザーが入り、内側の配列には各ユーザーの詳細情報が入っています。

PHPに「二次元配列」や「三次元配列」という専用の型があるわけではありません。

配列の値として別の配列が格納されているだけです。

二次元配列

配列の中に1段の配列が入っている構造を、一般的に二次元配列と呼びます。

$users = [
    [
        'name' => '田中太郎',
        'age' => 30,
    ],
    [
        'name' => '佐藤花子',
        'age' => 25,
    ],
];

田中太郎の名前を取得する場合は、次のように記述します。

echo $users[0]['name'];

最初の[0]で外側の配列から1人目を取得し、['name']で内側の名前を取得しています。

三次元配列

内側の配列にさらに配列が格納されている場合、一般的に三次元配列と呼びます。

$departments = [
    [
        'name' => '営業部',
        'members' => [
            [
                'name' => '田中太郎',
                'position' => '部長',
            ],
            [
                'name' => '佐藤花子',
                'position' => '主任',
            ],
        ],
    ],
];

営業部の1人目のメンバー名を取得する場合は、次のように記述します。

echo $departments[0]['members'][0]['name'];

階層が深くなるほど角括弧が増えるため、配列構造を把握しておくことが重要です。

多次元配列をforeachで処理する方法

多次元配列を順番に処理する場合は、foreachを使うのが基本です。

二次元配列をループする

$users = [
    [
        'name' => '田中太郎',
        'age' => 30,
    ],
    [
        'name' => '佐藤花子',
        'age' => 25,
    ],
];
foreach ($users as $user) {
    echo $user['name'] . '<br>';
    echo $user['age'] . '歳<br>';
}

foreachによって、外側の配列からユーザー情報を1件ずつ取り出しています。

キーも同時に取得する

外側のキーも利用したい場合は、次のように記述します。

foreach ($users as $index => $user) {
    echo '番号:' . ($index + 1) . '<br>';
    echo '名前:' . $user['name'] . '<br>';
}

数値キーは0から始まるため、表示用の番号を1から始めたい場合は1を加えます。

foreachを入れ子にする

三次元配列のように、内側にも一覧がある場合は、foreachを入れ子にします。

foreach ($departments as $department) {
    echo '<h2>' . $department['name'] . '</h2>';

    foreach ($department['members'] as $member) {
        echo '<p>' . $member['name'] . '</p>';
    }
}

外側のforeachで部署を処理し、内側のforeachで各部署のメンバーを処理しています。

多次元配列に要素を追加する方法

外側の配列に要素を追加する

配列の末尾に新しい要素を追加する場合は、空の角括弧を使います。

$users[] = [
    'name' => '鈴木一郎',
    'age' => 40,
];

array_push()でも追加できますが、単純に末尾へ追加するだけであれば、$users[]のほうが簡潔です。

内側の配列に要素を追加する

部署のメンバー一覧に新しいメンバーを追加する場合は、対象の階層を指定します。

$departments[0]['members'][] = [
    'name' => '高橋美咲',
    'position' => '担当',
];

多次元配列の値を変更する方法

既存の値を変更する場合は、対象のキーを指定して新しい値を代入します。

$users[0]['age'] = 31;

複数の要素をまとめて変更したい場合は、参照付きのforeachを使えます。

foreach ($users as &$user) {
    $user['age']++;
}

unset($user);

&$userとすることで、元の配列要素を直接変更できます。

参照付きforeachを使った後は、必ずunset($user)で参照を解除します。

参照を残したまま別の処理を行うと、最後の要素が意図せず変更される可能性があります。

多次元配列から要素を削除する方法

要素の削除にはunset()を使います。

unset($users[1]);

ただし、unset()を実行しても数値キーは自動的に振り直されません。

$users = [
    0 => ['name' => '田中'],
    1 => ['name' => '佐藤'],
    2 => ['name' => '鈴木'],
];

unset($users[1]);

削除後は次のようになります。

[
    0 => ['name' => '田中'],
    2 => ['name' => '鈴木'],
]

数値キーを連番に戻したい場合は、array_values()を使います。

$users = array_values($users);

ただし、ユーザーIDなど意味のあるキーを使っている場合は、array_values()を使わないようにします。

$users = [
    101 => ['name' => '田中'],
    205 => ['name' => '佐藤'],
];

この配列にarray_values()を使うと、101205というIDが失われます。

多次元配列のキーが存在するか確認する方法

存在しないキーへアクセスすると、Undefined array keyの警告が発生することがあります。

issetを使う

if (isset($users[0]['address'])) {
    echo $users[0]['address'];
}

isset()は、キーが存在し、かつ値がnullではない場合にtrueを返します。

array_key_existsを使う

値がnullでも、キー自体が存在するか確認したい場合は、array_key_exists()を使います。

$data = [
    'name' => null,
];

var_dump(array_key_exists('name', $data));

結果はtrueです。

null合体演算子を使う

値が存在しない場合に初期値を設定したい場合は、??が便利です。

$address = $users[0]['address'] ?? '未登録';

ただし、値が明示的にnullの場合も右側の値が使われます。

未定義とnullを区別したい場合は、array_key_exists()を使います。

多次元配列から条件に一致するデータを検索する方法

foreachで検索する

特定のIDを持つユーザーを検索する場合は、次のように記述できます。

$targetUser = null;

foreach ($users as $user) {
    if ($user['id'] === 2) {
        $targetUser = $user;
        break;
    }
}

見つかった時点でbreakを使うと、不要なループを避けられます。

array_filterで絞り込む

条件に一致する複数のデータを取得する場合は、array_filter()が便利です。

$filteredUsers = array_filter(
    $users,
    fn(array $user): bool =>
        isset($user['age'])
        && is_numeric($user['age'])
        && (int) $user['age'] >= 30
);

array_filter()は元のキーを維持します。

数値キーを連番にしたい場合は、array_values()を組み合わせます。

$filteredUsers = array_values($filteredUsers);

多次元配列から特定の列だけを取得する方法

array_column()を使うと、内側の配列から特定の値だけを取り出せます。

$names = array_column($users, 'name');

結果は次のようになります。

[
    '田中太郎',
    '佐藤花子',
]

IDをキー、名前を値にすることもできます。

$namesById = array_column($users, 'name', 'id');

結果は次のとおりです。

[
    1 => '田中太郎',
    2 => '佐藤花子',
]

ただし、インデックスとして使うIDが重複している場合は、後の値で上書きされます。

多次元配列の各要素を変換する方法

array_map()を使うと、各要素に同じ処理を適用できます。

$usersWithStatus = array_map(
    function (array $user): array {
        $age = isset($user['age']) && is_numeric($user['age'])
            ? (int) $user['age']
            : 0;

        $user['age'] = $age;
        $user['is_adult'] = $age >= 18;

        return $user;
    },
    $users
);

この処理では、元の配列を直接変更せず、新しい配列を作成します。

多次元配列の値を集計する方法

array_sumとarray_columnを組み合わせる

年齢の合計を求める場合は、次のように記述できます。

$totalAge = array_sum(
    array_column($users, 'age')
);

平均年齢を求める場合は、0除算を防ぐために件数を確認します。

$userCount = count($users);

$averageAge = $userCount > 0
    ? $totalAge / $userCount
    : 0;

array_reduceを使う

複雑な集計では、array_reduce()も利用できます。

$totalAge = array_reduce(
    $users,
    fn(int $total, array $user): int =>
        $total + (int) ($user['age'] ?? 0),
    0
);

多次元配列を並べ替える方法

usortで昇順に並べ替える

年齢の昇順に並べ替える場合は、usort()を使います。

usort(
    $users,
    fn(array $a, array $b): int =>
        $a['age'] <=> $b['age']
);

<=>は宇宙船演算子と呼ばれ、左右の値を比較して-101のいずれかを返します。

降順に並べ替える

比較する順番を逆にすると、降順になります。

usort(
    $users,
    fn(array $a, array $b): int =>
        $b['age'] <=> $a['age']
);

usortを使う際の注意点

usort()には、次の特徴があります。

  • 元の配列を直接変更する
  • 既存のキーを削除する
  • キーを0から始まる連番に振り直す

元の配列を残したい場合は、コピーしてから並べ替えます。

$sortedUsers = $users;

usort(
    $sortedUsers,
    fn(array $a, array $b): int =>
        $a['age'] <=> $b['age']
);

キーを維持したい場合は、uasort()を使います。

uasort(
    $users,
    fn(array $a, array $b): int =>
        $a['age'] <=> $b['age']
);

複数条件で並べ替える

年齢が同じ場合に名前順で並べ替える例です。

usort(
    $users,
    function (array $a, array $b): int {
        $ageComparison = $a['age'] <=> $b['age'];

        if ($ageComparison !== 0) {
            return $ageComparison;
        }

        return strcmp($a['name'], $b['name']);
    }
);

日本語の名前を五十音順に並べたい場合は、漢字の名前とは別に読み仮名を持たせる方法が確実です。

$users = [
    [
        'name' => '田中太郎',
        'name_kana' => 'たなかたろう',
    ],
    [
        'name' => '佐藤花子',
        'name_kana' => 'さとうはなこ',
    ],
];

多次元配列をグループ化する方法

都道府県ごとにユーザーをまとめる例です。

$groupedUsers = [];

foreach ($users as $user) {
    $prefecture = $user['prefecture'] ?? '未設定';

    if (!is_string($prefecture) || $prefecture === '') {
        $prefecture = '未設定';
    }

    $groupedUsers[$prefecture][] = $user;
}

外側のキーに都道府県名を使い、その中に該当するユーザーを追加しています。

多次元配列の重複を削除する方法

array_unique()は、多次元配列の特定キーを基準に重複を削除する用途には向いていません。

メールアドレスを基準に重複を削除する場合は、メールアドレスを外側のキーとして使います。

$uniqueUsers = [];

foreach ($users as $user) {
    $email = $user['email'] ?? null;

    if (!is_string($email) || $email === '') {
        continue;
    }

    if (!array_key_exists($email, $uniqueUsers)) {
        $uniqueUsers[$email] = $user;
    }
}

$uniqueUsers = array_values($uniqueUsers);

後から登場したデータを残したい場合は、存在確認をせずに代入します。

$uniqueUsers[$email] = $user;

多次元配列を結合する方法

array_mergeを使う

$result = array_merge($user1, $user2);

文字列キーが重複した場合は、後の配列の値で上書きされます。

配列結合演算子を使う

$result = $user1 + $user2;

同じキーが存在する場合は、左側の値が優先されます。

array_replaceを使う

設定値を右側の配列で上書きしたい場合は、array_replace()も便利です。

$result = array_replace(
    $defaultSettings,
    $userSettings
);

JSONと多次元配列を相互変換する方法

JSONを配列へ変換する

$users = json_decode(
    $json,
    true
);

第2引数にtrueを指定すると、JSONオブジェクトを連想配列として取得できます。

ただし、戻り値が必ず配列になるわけではありません。

JSONの最上位が文字列や数値であれば、PHP側でも文字列や数値として返されます。

安全に処理するなら、例外処理と型確認を行います。

try {
    $users = json_decode(
        $json,
        true,
        512,
        JSON_THROW_ON_ERROR
    );

    if (!is_array($users)) {
        throw new UnexpectedValueException(
            'JSONの最上位要素が配列ではありません。'
        );
    }
} catch (JsonException $e) {
    echo 'JSONの構文が不正です。';
} catch (UnexpectedValueException $e) {
    echo $e->getMessage();
}

配列をJSONへ変換する

$json = json_encode(
    $users,
    JSON_UNESCAPED_UNICODE
    | JSON_PRETTY_PRINT
    | JSON_THROW_ON_ERROR
);

JSON_UNESCAPED_UNICODEを指定すると、日本語が読みやすい形式で出力されます。

APIレスポンスとして返す場合は、Content-Typeも設定します。

header(
    'Content-Type: application/json; charset=UTF-8'
);

echo json_encode(
    $users,
    JSON_UNESCAPED_UNICODE
    | JSON_THROW_ON_ERROR
);

数値キーが連続していない場合の注意点

PHP配列の数値キーが連続していない場合、JSONでは配列ではなくオブジェクトとして出力されることがあります。

$data = [
    0 => 'A',
    2 => 'B',
];

JSON結果は次のようになります。

{
    "0": "A",
    "2": "B"
}

JSON配列として出力したい場合は、array_values()でキーを振り直します。

$json = json_encode(
    array_values($data)
);

PHP 8.1以降では、array_is_list()を使ってリスト形式か確認できます。

if (array_is_list($data)) {
    echo '連続した数値キーを持つ配列です';
}

HTMLで多次元配列を表示する方法

データをHTMLへ出力する場合は、XSS対策としてhtmlspecialchars()を使います。

<table>
    <thead>
        <tr>
            <th>名前</th>
            <th>メールアドレス</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($users as $user): ?>
            <tr>
                <td>
                    <?= htmlspecialchars(
                        $user['name'] ?? '',
                        ENT_QUOTES,
                        'UTF-8'
                    ) ?>
                </td>
                <td>
                    <?= htmlspecialchars(
                        $user['email'] ?? '',
                        ENT_QUOTES,
                        'UTF-8'
                    ) ?>
                </td>
            </tr>
        <?php endforeach; ?>
    </tbody>
</table>

htmlspecialchars()は、HTML本文やHTML属性への出力に適しています。

JavaScriptの中へ値を出力する場合は、json_encode()を使います。

<script>
const userName = <?= json_encode(
    $user['name'] ?? '',
    JSON_UNESCAPED_UNICODE
    | JSON_HEX_TAG
    | JSON_HEX_AMP
    | JSON_HEX_APOS
    | JSON_HEX_QUOT
) ?>;
</script>

出力先によってエスケープ方法を使い分けることが重要です。

フォームから多次元配列を受け取る方法

HTMLフォームのname属性に角括弧を使うと、多次元配列として値を送信できます。

<input type="text" name="users[0][name]">
<input type="email" name="users[0][email]">

PHP側では、次のような構造で受け取れます。

$users = $_POST['users'] ?? [];

入力値は信頼せず、必ず型や内容を検証します。

if (!is_array($users)) {
    $users = [];
}

$validatedUsers = [];

foreach ($users as $user) {
    if (!is_array($user)) {
        continue;
    }

    $name = trim((string) ($user['name'] ?? ''));
    $email = trim((string) ($user['email'] ?? ''));

    if ($name === '') {
        continue;
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        continue;
    }

    $validatedUsers[] = [
        'name' => $name,
        'email' => $email,
    ];
}

実務では、CSRF対策、文字数制限、配列要素数の制限なども必要です。

データベースの取得結果を多次元配列として扱う方法

PDOで複数行を取得すると、多くの場合は多次元配列として扱えます。

$pdo = new PDO(
    'mysql:host=localhost;dbname=sample;charset=utf8mb4',
    'username',
    'password',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]
);

$stmt = $pdo->query(
    'SELECT id, name, email FROM users ORDER BY id'
);

$users = $stmt->fetchAll();

取得結果は次のような構造になります。

[
    [
        'id' => 1,
        'name' => '田中太郎',
        'email' => 'tanaka@example.com',
    ],
    [
        'id' => 2,
        'name' => '佐藤花子',
        'email' => 'sato@example.com',
    ],
]

件数が非常に多い場合は、fetchAll()ではなく1行ずつ処理する方法も検討します。

while ($user = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // 1行ずつ処理する
}

深い階層の値を安全に取得する方法

多次元配列の階層が深い場合、毎回キーの存在確認を書くとコードが長くなります。

補助関数を用意すると、深い階層の値を安全に取得できます。

function getNestedValue(
    array $data,
    array $keys,
    mixed $default = null
): mixed {
    $current = $data;

    foreach ($keys as $key) {
        if (
            !is_array($current)
            || !array_key_exists($key, $current)
        ) {
            return $default;
        }

        $current = $current[$key];
    }

    return $current;
}

使用例は次のとおりです。

$host = getNestedValue(
    $config,
    ['database', 'connections', 'mysql', 'host'],
    'localhost'
);

多次元配列を再帰的に処理する方法

階層数が決まっていない配列を処理する場合は、再帰処理を使います。

独自の再帰関数を使う

すべての文字列から前後の空白を取り除く例です。

function trimNestedArray(array $data): array
{
    foreach ($data as $key => $value) {
        if (is_array($value)) {
            $data[$key] = trimNestedArray($value);
            continue;
        }

        if (is_string($value)) {
            $data[$key] = trim($value);
        }
    }

    return $data;
}

array_walk_recursiveを使う

末端の値だけを処理する場合は、array_walk_recursive()も利用できます。

array_walk_recursive(
    $data,
    function (&$value): void {
        if (is_string($value)) {
            $value = trim($value);
        }
    }
);

無名関数の引数である$valueは関数内だけの変数なので、外側でunset($value)を実行する必要はありません。

多次元配列の構造を設計する際のポイント

キー名を統一する

同じ種類のデータでは、キー名を統一します。

$users = [
    [
        'name' => '田中太郎',
        'email' => 'tanaka@example.com',
    ],
    [
        'name' => '佐藤花子',
        'email' => 'sato@example.com',
    ],
];

nameuser_nameemailmailのように表記が混在すると、処理が複雑になります。

同じキーには同じ型を入れる

$users = [
    [
        'name' => '田中太郎',
        'age' => 30,
    ],
    [
        'name' => '佐藤花子',
        'age' => 25,
    ],
];

年齢は整数として統一し、表示時に「歳」を付けるほうが扱いやすくなります。

階層を深くしすぎない

次のように階層が深くなると、値の取得や変更が難しくなります。

$data['company']['departments'][0]['teams'][0]['members'][0]['profile']['name'];

複雑になりすぎた場合は、クラスやDTOの利用を検討します。

多次元配列とクラスの使い分け

多次元配列は柔軟で扱いやすい一方、キー名のタイプミスを検出しにくいという弱点があります。

echo $user['nmae'];

規模が大きくなった場合は、クラスやDTOを使うと、IDE補完や静的解析を活用しやすくなります。

final class User
{
    public function __construct(
        public readonly string $name,
        public readonly int $age,
    ) {
    }
}

多次元配列は、APIレスポンスやデータベース結果、一時的なデータ変換に向いています。

クラスやDTOは、データ構造が固定されている場合や、厳密な型管理が必要な場合に向いています。

PHPDocで多次元配列の構造を定義する

実務では、PHPDocを使って多次元配列の構造を記述すると、コードの可読性が高まります。

/**
 * @var array<int, array{
 *     id: int,
 *     name: string,
 *     age: int,
 *     email: string
 * }> $users
 */
$users = [
    [
        'id' => 1,
        'name' => '田中太郎',
        'age' => 30,
        'email' => 'tanaka@example.com',
    ],
];

PHPStanやPsalmなどの静的解析ツールを使う場合にも有効です。

PHPの多次元配列でよく使う関数

関数主な用途
count()要素数を数える
isset()値が存在し、nullでないか確認する
array_key_exists()キーの存在を確認する
array_column()特定の列を取得する
array_filter()条件に一致する要素を抽出する
array_map()各要素を変換する
array_reduce()配列を集計する
array_values()数値キーを振り直す
array_merge()配列を結合する
array_replace()同じキーの値を置き換える
usort()値を基準に並べ替える
uasort()キーを維持して並べ替える
unset()要素を削除する
is_array()配列か確認する
json_encode()配列をJSONへ変換する
json_decode()JSONをPHPの値へ変換する

まとめ

PHPの多次元配列は、ユーザー一覧や商品データ、データベースの取得結果、APIレスポンスなどを扱う際に欠かせないデータ構造です。

基本的な取得方法は、次の形です。

$users[0]['name'];

一覧処理ではforeachを使います。

foreach ($users as $user) {
    echo $user['name'];
}

実務では、次の点を意識することが重要です。

  • キー名と値の型を統一する
  • 存在しないキーへのアクセスを避ける
  • isset()array_key_exists()を使い分ける
  • array_filter()array_map()を活用する
  • usort()によるキーの振り直しに注意する
  • HTML出力時は適切にエスケープする
  • 外部から受け取ったデータは必ず検証する
  • 複雑になった場合はクラスやDTOを検討する

多次元配列の基本操作を理解しておくと、PHPによるWeb開発のさまざまな場面で応用できるようになります。

以上、PHPの多次元配列についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次