Chapter 4: Mastering PHP Arrays — From Basic Lists to Functional Data Pipelines
By Ali Naqi | November 08, 2026
Modern PHP Mastery: From First Steps to Production
Chapter 4: Mastering PHP Arrays — From Basic Lists to Functional Data Pipelines
In Chapter 3, we explored control flow, guard clauses, and modern expression matching. But in any real application, data rarely arrives as isolated scalar variables. You fetch rows from a database, decode JSON payloads from REST APIs, process lists of uploaded files, or handle batches of user records.
In PHP, the primary vehicle for handling grouped data is the Array.
PHP's array implementation is unique among mainstream programming languages. While languages like C or Java make sharp distinctions between static arrays, dynamic array lists, and hash maps (dictionaries), PHP collapses all of these into a single, highly optimized structure: an ordered map.
In this chapter, we will break down how PHP arrays operate under the hood, master both indexed and associative arrays, explore modern array destructuring and unpacking, and learn functional transformation techniques (array_map, array_filter, and array_reduce) that keep data pipelines concise and readable.
What Is a PHP Array?
Under the hood, a PHP array is an ordered hash table. It associates keys with values, and it preserves the exact insertion order of its elements regardless of whether the keys are sequential numbers or arbitrary strings.
A PHP array can store any data type—integers, floats, strings, booleans, objects, resources, or even other arrays.
Declaration Syntax
Historically, arrays were created using the array() construct:
<?php
// Legacy syntax (still valid, but outdated)
$frameworks = array('Laravel', 'Symfony', 'Laminas');
Since PHP 5.4, the short array syntax [...] is the universal standard:
<?php
// Modern short syntax (PSR-12 standard)
$frameworks = ['Laravel', 'Symfony', 'Laminas'];
Indexed vs. Associative Arrays
PHP arrays generally fall into two categories based on how their keys are structured:
1. Indexed Arrays (Sequential Numeric Keys)
An indexed array uses non-negative integer keys. If you do not provide explicit keys when adding elements, PHP assigns them automatically starting at index 0.
<?php
$servers = ['web-01', 'web-02', 'db-primary'];
// Accessing by numeric index
echo $servers[0]; // Output: web-01
echo $servers[2]; // Output: db-primary
// Appending an item to the end of an indexed array
$servers[] = 'cache-redis';
// PHP assigns index 3 automatically
echo $servers[3]; // Output: cache-redis
> Warning on Gaps in Numeric Indexes:
> When you remove an element from an indexed array using unset($servers[1]), PHP does not re-index the remaining keys. The array now contains indexes 0, 2, and 3. If your downstream logic relies on zero-indexed sequential sequences (e.g., when converting to a JSON list via json_encode), run array_values($servers) to reset the keys to continuous integers.
>
2. Associative Arrays (String Key Mappings)
Associative arrays use named string keys, creating key-value pairs comparable to dictionaries or hash maps:
<?php
$databaseConfig = [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'app_production',
'username' => 'db_user',
'charset' => 'utf8mb4',
];
// Accessing values by named key
echo $databaseConfig['database']; // Output: app_production
// Adding or modifying keys
$databaseConfig['timeout'] = 5.0;
Safe Element Access: Avoiding Undefined array key Warnings
In PHP 8.0+, accessing an array key that does not exist triggers an E_WARNING (Warning: Undefined array key "..."). To access values safely without triggering warnings:
<?php
// 1. Using isset() to check existence and non-nullity
if (isset($databaseConfig['password'])) {
$dbPass = $databaseConfig['password'];
}
// 2. Using array_key_exists() when the value might explicitly be null
if (array_key_exists('optional_field', $databaseConfig)) {
// Key is present, even if its value is null
}
// 3. Modern default assignment with the null-coalescing operator (??)
$port = $databaseConfig['port'] ?? 3306;
Array Destructuring and Unpacking
Modern PHP offers concise syntax for breaking arrays apart and assembling them back together.
1. Symmetric Array Destructuring
Instead of assigning individual elements manually line by line, you can unpack values directly into distinct variables using [...]:
<?php
// Indexed array destructuring
$coordinates = [24.8607, 67.0011];
[$latitude, $longitude] = $coordinates;
echo "Lat: {$latitude}, Long: {$longitude}";
// Skipping elements
$response = [200, 'OK', ['payload' => 'data']];
[, $statusText] = $response; // Ignores index 0 and 2
echo $statusText; // Output: OK
// Associative array destructuring (PHP 7.1+)
$userRecord = [
'id' => 942,
'username' => 'dev_ali',
'email' => 'ali@example.com',
];
['username' => $accountUser, 'email' => $accountEmail] = $userRecord;
echo "User: {$accountUser} ({$accountEmail})";
2. The Spread Operator (...) for Unpacking
The spread operator (...) unpacks an array in place, making list merging and concatenation fast and readable:
<?php
$corePlugins = ['auth', 'logging'];
$extraPlugins = ['analytics', 'caching'];
// Merging indexed arrays
$activePlugins = ['profiler', ...$corePlugins, ...$extraPlugins];
// Result: ['profiler', 'auth', 'logging', 'analytics', 'caching']
Starting in PHP 8.1, the spread operator supports string keys as well. If duplicate keys occur, later keys overwrite earlier ones:
<?php
$baseSettings = [
'theme' => 'dark',
'per_page' => 20,
'show_banner' => true,
];
$userOverrides = [
'per_page' => 50,
'show_banner' => false,
];
$resolvedSettings = [...$baseSettings, ...$userOverrides];
/*
Result:
[
'theme' => 'dark',
'per_page' => 50,
'show_banner' => false,
]
*/
Essential Array Operations: Mutation vs. Functional Streams
PHP includes over 80 built-in array functions. Rather than memorizing all of them, focus on the two main paradigms: in-place mutation and functional transformations.
In-Place Modification Functions
These functions modify the array directly by reference:
| Function | Action | Notes |
|---|---|---|
| array_push($arr, $val) | Appends one or more elements to the end | Prefer $arr[] = $val for single values; it avoids function-call overhead. |
| array_pop($arr) | Removes and returns the last element | Shortens the array by 1. |
| array_shift($arr) | Removes and returns the first element | Re-indexes numeric keys automatically. |
| array_unshift($arr, $val) | Prepends elements to the start | Re-indexes numeric keys automatically. |
Functional Transformations
In modern software design, immutable data transformations are preferred because they avoid unpredictable side effects. PHP provides three foundational higher-order functions: array_filter, array_map, and array_reduce.
Raw Input Data: [1, 2, 3, 4, 5, 6]
│
▼ array_filter() (Keep only even numbers: $n % 2 === 0)
Filtered Data: [2, 4, 6]
│
▼ array_map() (Multiply each by 10: $n * 10)
Mapped Data: [20, 40, 60]
│
▼ array_reduce() (Sum elements into a single value: $carry + $item)
Scalar Result: 120
1. array_filter(): Pruning Data
Extracts elements that pass a truth test.
<?php
declare(strict_types=1);
$inventory = [
['sku' => 'A1', 'stock' => 12, 'active' => true],
['sku' => 'A2', 'stock' => 0, 'active' => true],
['sku' => 'A3', 'stock' => 5, 'active' => false],
['sku' => 'A4', 'stock' => 8, 'active' => true],
];
// Keep only items that are active AND have stock
$availableItems = array_filter($inventory, function (array $item): bool {
return $item['active'] && $item['stock'] > 0;
});
Note: array_filter preserves original keys. Run array_values() afterward if you need clean 0-indexed positions.
2. array_map(): Transforming Data
Applies a callback function to every element and returns a new array with the transformed values:
<?php
declare(strict_types=1);
$skus = ['sku_882', 'sku_914', 'sku_102'];
// Modern arrow function syntax (fn() => expression)
$cleanSkus = array_map(fn(string $sku): string => strtoupper($sku), $skus);
3. array_reduce(): Aggregating into a Single Result
Iterates through an array to reduce it to a single value (such as a total sum, a combined string, or a grouped object):
<?php
declare(strict_types=1);
$orders = [
['order_id' => 101, 'amount' => 45.00],
['order_id' => 102, 'amount' => 12.50],
['order_id' => 103, 'amount' => 89.20],
];
$totalRevenue = array_reduce(
$orders,
fn(float $accumulator, array $order): float => $accumulator + $order['amount'],
0.0 // Initial starting value
);
echo "Total: \${$totalRevenue}"; // Output: Total: $146.7
Sorting Arrays Reliably
Sorting in PHP requires attention because different functions behave differently regarding key preservation and value comparison:
| Function | Sorts By | Preserves Keys? | Order |
|---|---|---|---|
| sort() | Values | No (re-indexes with 0, 1, 2...) | Ascending |
| rsort() | Values | No (re-indexes with 0, 1, 2...) | Descending |
| asort() | Values | Yes | Ascending |
| arsort() | Values | Yes | Descending |
| ksort() | Keys | Yes | Ascending |
| krsort() | Keys | Yes | Descending |
| usort() | Custom callback | No | Defined by callback |
| uasort() | Custom callback | Yes | Defined by callback |
For custom sorting logic, use usort() or uasort() along with PHP’s spaceship operator (<=>):
<?php
$articles = [
['title' => 'Getting Started with Docker', 'views' => 1200],
['title' => 'Modern PHP Mastery', 'views' => 4500],
['title' => 'Demystifying SQL Indexes', 'views' => 2800],
];
// Sort descending by view count using the spaceship operator (<=>)
usort($articles, fn(array $a, array $b): int => $b['views'] <=> $a['views']);
The spaceship operator ($b <=> $a) returns -1 if $b < $a, 0 if $b == $a, and 1 if $b > $a, providing clean, one-line custom sorting.
Practical Example: Building an E-Commerce Cart Aggregator
Let’s build a production-style cart management pipeline. We will take a raw cart array, filter out invalid or deleted items, calculate line-item totals with quantity discounts, compute running tax sums, and format the output for display.
Create a file named cart_pipeline.php:
<?php
declare(strict_types=1);
// Raw input data, simulating items retrieved from a session or JSON payload
$rawCart = [
[
'id' => 'PRD-01',
'name' => 'Ergonomic Chair',
'price' => 199.99,
'quantity' => 2,
'status' => 'available',
],
[
'id' => 'PRD-02',
'name' => 'USB-C Multi-Port Hub',
'price' => 45.00,
'quantity' => 0, // Should be excluded: invalid quantity
'status' => 'available',
],
[
'id' => 'PRD-03',
'name' => 'Desk Mat (Leather)',
'price' => 29.50,
'quantity' => 1,
'status' => 'discontinued', // Should be excluded: status discontinued
],
[
'id' => 'PRD-04',
'name' => 'Wireless Noise-Cancelling Headphones',
'price' => 149.00,
'quantity' => 3, // Eligible for a 5% bulk discount (> 2 units)
'status' => 'available',
],
];
const TAX_RATE = 0.085; // 8.5%
/**
* Filter 1: Retain only valid and available items.
*/
$purchasableItems = array_filter($rawCart, function (array $item): bool {
return $item['status'] === 'available' && $item['quantity'] > 0;
});
/**
* Transformation: Normalize line items and calculate discounts.
*/
$processedItems = array_map(function (array $item): array {
$basePrice = $item['price'];
$qty = $item['quantity'];
// Apply 5% bulk discount on line items with more than 2 units
$discountRate = ($qty > 2) ? 0.05 : 0.0;
$rawLineTotal = $basePrice * $qty;
$discountAmount = $rawLineTotal * $discountRate;
$finalLineTotal = $rawLineTotal - $discountAmount;
return [
'id' => $item['id'],
'name' => $item['name'],
'unit_price' => $basePrice,
'quantity' => $qty,
'discount' => round($discountAmount, 2),
'total' => round($finalLineTotal, 2),
];
}, $purchasableItems);
// Re-index to clean sequential integers
$processedItems = array_values($processedItems);
/**
* Aggregation: Calculate order financial totals.
*/
$subtotal = array_reduce(
$processedItems,
fn(float $sum, array $item): float => $sum + $item['total'],
0.0
);
$taxTotal = round($subtotal * TAX_RATE, 2);
$grandTotal = $subtotal + $taxTotal;
function formatCurrency(float $amount): string
{
return '$' . number_format($amount, 2);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cart Review</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; padding: 2rem; }
.container { max-width: 720px; margin: 0 auto; background: #1e293b; border-radius: 10px; padding: 2rem; border: 1px solid #334155; }
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid #334155; font-size: 0.95rem; }
th { color: #94a3b8; font-weight: 600; text-transform: uppercase; font-size: 0.8rem; letter-spacing: 0.05em; }
.num { text-align: right; }
.summary-card { margin-top: 2rem; padding-top: 1rem; border-top: 2px solid #334155; }
.summary-row { display: flex; justify-content: space-between; margin-bottom: 0.5rem; }
.grand-total { font-size: 1.25rem; font-weight: bold; color: #38bdf8; }
.discount-badge { color: #f87171; font-size: 0.85rem; }
</style>
</head>
<body>
<div class="container">
<h2>Review Your Selected Items</h2>
<table>
<thead>
<tr>
<th>Description</th>
<th class="num">Unit</th>
<th class="num">Qty</th>
<th class="num">Total</th>
</tr>
</thead>
<tbody>
<?php foreach ($processedItems as $lineItem): ?>
<tr>
<td>
<?= htmlspecialchars($lineItem['name'], ENT_QUOTES, 'UTF-8') ?>
<?php if ($lineItem['discount'] > 0): ?>
<br><span class="discount-badge">Bulk savings (-<?= formatCurrency($lineItem['discount']) ?>)</span>
<?php endif; ?>
</td>
<td class="num"><?= formatCurrency($lineItem['unit_price']) ?></td>
<td class="num"><?= $lineItem['quantity'] ?></td>
<td class="num"><?= formatCurrency($lineItem['total']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="summary-card">
<div class="summary-row">
<span>Subtotal:</span>
<span><?= formatCurrency($subtotal) ?></span>
</div>
<div class="summary-row">
<span>Estimated Sales Tax (8.5%):</span>
<span><?= formatCurrency($taxTotal) ?></span>
</div>
<div class="summary-row grand-total">
<span>Grand Total:</span>
<span><?= formatCurrency($grandTotal) ?></span>
</div>
</div>
</div>
</body>
</html>
Run this file using php -S localhost:8000 and open http://localhost:8000/cart_pipeline.php.
Notice how array_filter cleanly pruned out the invalid stock items, array_map applied dynamic discounts per line item, and array_reduce compiled our financial figures without mutating the original dataset.
Chapter 4 Key Takeaways
* PHP arrays are ordered maps: They preserve insertion sequence and serve as lists, dictionaries, or multi-dimensional matrices.
* Use short array syntax ([...]) and symmetric destructuring ([$a, $b] = $array) for cleaner variable extraction.
* Array unpacking (...) provides a clean, fast way to concatenate collections or override configuration options.
* Prefer functional pipelines (array_filter, array_map, array_reduce) over nested, mutating loops when shaping data.
* Remember array keys: Functions like unset() and array_filter() leave keys intact. Use array_values() to reset numeric indexing when downstream consumers (such as JSON APIs) expect continuous sequential arrays.
In Chapter 5, we will explore Functions and Scope: user-defined functions, strict type hinting for parameters and return types, union and intersection types, variadic arguments, closures, and the difference between global, local, and static variable scope.