Himanshu SharmaSoftware engineer
Let’s talk

PHP · Lesson 24 of 54

Multidimensional arrays

Store tables and nested data in PHP with arrays of arrays, read and change deep values, loop through rows, and group records by a field.

12 minArraysUpdated 22 October 2026

Awaiting technical review. This lesson is readable but not yet listed in search engines.

By the end of this lessonBuild, read, loop through and update arrays that contain other arrays.

Arrays inside arrays

The values in an array can themselves be arrays. That is how PHP represents anything with structure: a table of products, an order with several lines, a menu with sub-menus, the decoded result of a JSON API. An array of arrays is called a multidimensional or nested array.

The most common shape is a list of rows, where each row is an associative array with the same keys, exactly like a database table:

php
<?php
$products = [
    ["id" => 1, "name" => "Mechanical Keyboard", "category" => "Electronics", "price" => 89.5, "stock" => 60],
    ["id" => 2, "name" => "Ceramic Mug Set", "category" => "Home", "price" => 18.75, "stock" => 120],
    ["id" => 3, "name" => "Yoga Mat", "category" => "Sports", "price" => 24.99, "stock" => 80],
    ["id" => 4, "name" => "Noise-Cancelling Headphones", "category" => "Electronics", "price" => 199.99, "stock" => 25],
];

echo $products[1]["name"] . "\n";
echo $products[3]["price"] . "\n";
echo count($products) . " products\n";
result
Ceramic Mug Set
199.99
4 products

Read the keys from left to right: $products[1] is the second row, and ["name"] picks a field in it. If you have done the MySQL course, this is the same data as the products table in the sample shop, and it is exactly what PHP gives you back when it fetches rows from a database.

Looping through rows

A foreach over the list gives you one row at a time:

php
<?php
$products = [
    ["name" => "Mechanical Keyboard", "price" => 89.5, "stock" => 60],
    ["name" => "Ceramic Mug Set", "price" => 18.75, "stock" => 120],
    ["name" => "Noise-Cancelling Headphones", "price" => 199.99, "stock" => 25],
];

foreach ($products as $product) {
    $value = $product["price"] * $product["stock"];
    printf("%-28s stock value %9.2f\n", $product["name"], $value);
}
result
Mechanical Keyboard          stock value   5370.00
Ceramic Mug Set              stock value   2250.00
Noise-Cancelling Headphones  stock value   4999.75

Deeper nesting

Nesting can go as deep as the data needs. An order has a customer and a list of items, and each item has its own fields:

php
<?php
$order = [
    "id" => 1042,
    "customer" => ["name" => "Asha Verma", "city" => "Delhi"],
    "items" => [
        ["product" => "Mechanical Keyboard", "qty" => 1, "price" => 89.5],
        ["product" => "Ceramic Mug Set", "qty" => 2, "price" => 18.75],
    ],
];

echo "Order {$order['id']} for {$order['customer']['name']}\n";
echo "Second item: " . $order["items"][1]["product"] . "\n";

$total = 0;
foreach ($order["items"] as $item) {
    $total += $item["qty"] * $item["price"];
}
echo "Total: $total\n";
result
Order 1042 for Asha Verma
Second item: Ceramic Mug Set
Total: 127

To print a nested value inside double quotes, use braces: "{$order['customer']['name']}".

Changing nested values

Assign through the full chain of keys. PHP creates any missing inner arrays for you:

php
<?php
$order = ["items" => [["product" => "Mug", "qty" => 1]]];

$order["items"][0]["qty"] = 3;                       // change a value
$order["items"][] = ["product" => "Bottle", "qty" => 1];  // add a row
$order["shipping"]["method"] = "courier";            // creates "shipping"

print_r($order);
result
Array
(
    [items] => Array
        (
            [0] => Array
                (
                    [product] => Mug
                    [qty] => 3
                )

            [1] => Array
                (
                    [product] => Bottle
                    [qty] => 1
                )

        )

    [shipping] => Array
        (
            [method] => courier
        )

)

When you loop, remember from for and foreach loops that the loop variable is a copy. To change rows inside a foreach, use the key: $products[$i]["stock"] -= 1;.

Pulling out a column

array_column() takes one field from every row. With a second key argument it also builds a lookup array:

php
<?php
$products = [
    ["id" => 1, "name" => "Mechanical Keyboard", "price" => 89.5],
    ["id" => 2, "name" => "Ceramic Mug Set", "price" => 18.75],
    ["id" => 3, "name" => "Yoga Mat", "price" => 24.99],
];

print_r(array_column($products, "name"));
print_r(array_column($products, "price", "id"));
echo "Total of all prices: " . array_sum(array_column($products, "price")) . "\n";
result
Array
(
    [0] => Mechanical Keyboard
    [1] => Ceramic Mug Set
    [2] => Yoga Mat
)
Array
(
    [1] => 89.5
    [2] => 18.75
    [3] => 24.99
)
Total of all prices: 133.24

The second form, prices keyed by id, lets you look up a price straight away with $prices[3] instead of searching the list.

Grouping rows

A very common task: group records by one of their fields, for example products by category. Build a new array whose keys are the categories and whose values are lists:

php
<?php
$products = [
    ["name" => "Mechanical Keyboard", "category" => "Electronics"],
    ["name" => "Ceramic Mug Set", "category" => "Home"],
    ["name" => "Noise-Cancelling Headphones", "category" => "Electronics"],
    ["name" => "Yoga Mat", "category" => "Sports"],
];

$byCategory = [];
foreach ($products as $product) {
    $byCategory[$product["category"]][] = $product["name"];
}

foreach ($byCategory as $category => $names) {
    echo "$category: " . implode(", ", $names) . "\n";
}
result
Electronics: Mechanical Keyboard, Noise-Cancelling Headphones
Home: Ceramic Mug Set
Sports: Yoga Mat

The line inside the loop does a lot: the first time a category appears, $byCategory["Home"] does not exist, so PHP creates it as an empty array and [] appends the name. This is the PHP equivalent of GROUP BY in SQL, and the same pattern works for any field: orders by customer, posts by month, or log lines by error level.

Common mistakes

  • Leaving out a level: $products["name"] when you need $products[0]["name"].
  • Changing the loop copy instead of the original row.
  • Reading a deep key that may be missing. ?? works through the whole chain: $order["customer"]["phone"] ?? "none".
  • Building huge nested arrays in memory for data that should stay in a database and be queried.

Exercise

Using the four-product list from the start of this lesson (with category, price and stock), print the number of products and the total stock in each category.

Show one possible answer
php
<?php
$products = [
    ["name" => "Mechanical Keyboard", "category" => "Electronics", "price" => 89.5, "stock" => 60],
    ["name" => "Ceramic Mug Set", "category" => "Home", "price" => 18.75, "stock" => 120],
    ["name" => "Yoga Mat", "category" => "Sports", "price" => 24.99, "stock" => 80],
    ["name" => "Noise-Cancelling Headphones", "category" => "Electronics", "price" => 199.99, "stock" => 25],
];

$summary = [];
foreach ($products as $product) {
    $category = $product["category"];
    $summary[$category]["count"] = ($summary[$category]["count"] ?? 0) + 1;
    $summary[$category]["stock"] = ($summary[$category]["stock"] ?? 0) + $product["stock"];
}

foreach ($summary as $category => $totals) {
    echo "$category: {$totals['count']} products, {$totals['stock']} units\n";
}
result
Electronics: 2 products, 85 units
Home: 1 products, 120 units
Sports: 1 products, 80 units

"1 products" reads badly; a real page would choose the word with a ternary, as in Ternary, null coalescing and nullsafe operators.

Key points

  • An array can contain arrays. A list of associative arrays is the usual way to hold rows, like a database table.
  • Chain the keys to reach a value: $orders[0]['items'][1]['price'].
  • Nested foreach loops visit every level; array_column() pulls one field out of every row.
  • Grouping rows by a field means building a new array keyed by that field.

Check yourself

1. Given $products = [['name' => 'Mug', 'price' => 18.75]], how do you read the price?
2. What does array_column($products, 'name') return?
3. What does $byCategory[$p['category']][] = $p['name']; do inside a loop?
4. How many levels deep is $data['a']['b']['c']?