Arrays #
PHP arrays are far more flexible data structures than they appear on the surface. Underneath, every PHP array is an ordered map — a map that preserves insertion order, supports both integer and string keys in the same array, and can function as a list, dictionary, stack, queue, or even a set depending on how you use it. PHP has more than 70 built-in functions dedicated to arrays — but not all of them need to be memorized. What matters is mastering the most frequently used patterns: transformation with array_map, filtering with array_filter, aggregation with array_reduce, sorting with usort, and column extraction with array_column. This article covers all aspects of PHP arrays in depth along with the idiomatic patterns that make code cleaner than explicit loops.
Defining Arrays #
PHP supports two syntaxes for creating arrays: the classic array() and the modern, concise []. Use [] for all new code.
<?php
// Modern syntax (PHP 5.4+) — use this
$empty = [];
$indexed = [1, 2, 3, 4, 5];
$associative = ['name' => 'Budi', 'age' => 28, 'city' => 'Jakarta'];
$mixed = [0 => 'zero', 'one' => 1, 2 => 'two']; // valid but avoid
// Legacy syntax — still valid, but not idiomatic
$indexedLegacy = array(1, 2, 3);
$assocLegacy = array('name' => 'Budi', 'age' => 28);
// Multidimensional arrays
$table = [
['id' => 1, 'name' => 'Laptop', 'price' => 15_000_000, 'stock' => 5],
['id' => 2, 'name' => 'Monitor', 'price' => 5_000_000, 'stock' => 12],
['id' => 3, 'name' => 'Mouse', 'price' => 250_000, 'stock' => 0],
['id' => 4, 'name' => 'Keyboard','price' => 800_000, 'stock' => 8],
];
// Access elements
echo $indexed[0]; // 1
echo $associative['name']; // Budi
echo $table[1]['name']; // Monitor
echo $table[0]['price']; // 15000000
Arrays Are Ordered Maps #
A PHP array isn’t just a list — every element has a key that can be an integer or a string. Understanding this matters because unset() doesn’t reset indices:
<?php
$a = ['apple', 'mango', 'orange', 'grape'];
// Keys: 0 1 2 3
unset($a[1]); // remove 'mango'
print_r($a);
// Array ( [0] => apple [2] => orange [3] => grape )
// Index 1 is GONE — not automatically reindexed!
// To reindex after unset, use array_values()
$a = array_values($a);
print_r($a);
// Array ( [0] => apple [1] => orange [2] => grape )
Adding, Modifying, and Removing Elements #
<?php
$fruits = ['apple', 'mango'];
// Append at the end
$fruits[] = 'orange'; // automatic index — now [0,1,2]
$fruits[] = 'grape';
array_push($fruits, 'watermelon'); // same as $fruits[] = 'watermelon'
// Prepend at the beginning
array_unshift($fruits, 'avocado'); // [avocado, apple, mango, orange, grape, watermelon]
// Remove from the end — returns the removed element
$last = array_pop($fruits); // 'watermelon'
// Remove from the beginning — returns the removed element
$first = array_shift($fruits); // 'avocado'
// Remove a specific element
unset($fruits[2]); // index 2 disappears, not reindexed
// Change a value
$fruits[0] = 'red apple'; // override the first element
// Insert at a specific position with array_splice()
// array_splice($array, $offset, $lengthToDelete, $insertion)
array_splice($fruits, 1, 0, ['kiwi', 'lemon']); // insert at index 1 without deleting
Accessing Elements Safely #
<?php
$config = ['host' => 'localhost', 'port' => 3306, 'debug' => false];
// Direct access — an error if the key doesn't exist
echo $config['host']; // localhost
// Null coalescing — safe for keys that might not exist
echo $config['user'] ?? 'root'; // 'root' — key doesn't exist
echo $config['debug'] ?? true; // false — key exists, value is false
echo $config['timeout'] ?? 30; // 30 — key doesn't exist
// array_key_exists — distinguish between a missing key and a null value
$data = ['name' => null];
var_dump(isset($data['name'])); // false — null counts as missing for isset
var_dump(array_key_exists('name', $data)); // true — the key exists even with a null value
// Safe nested access
$user = ['profile' => ['city' => 'Jakarta']];
echo $user['profile']['city'] ?? 'Unknown'; // Jakarta
echo $user['profile']['country'] ?? 'Indonesia'; // Indonesia — key missing
echo $user['address']['city'] ?? 'Unknown'; // Unknown
Array Transformations #
Transformation functions produce a new array without modifying the original — this is a cleaner pattern than explicit loops with appends.
array_map — Transform Every Element
#
<?php
$prices = [10000, 25000, 50000, 100000];
// Add 11% VAT to every price
$pricesWithVat = array_map(fn($p) => $p * 1.11, $prices);
// [11100, 27750, 55500, 111000]
// Format conversion
$formattedRupiah = array_map(
fn($p) => 'Rp ' . number_format($p, 0, ',', '.'),
$prices
);
// ['Rp 10.000', 'Rp 25.000', 'Rp 50.000', 'Rp 100.000']
// array_map with several arrays — iterated in parallel
$names = ['Budi', 'Siti', 'Dani'];
$scores = [85, 92, 78];
$reports = array_map(
fn($n, $s) => ['name' => $n, 'score' => $s, 'passed' => $s >= 70],
$names,
$scores
);
// [['name'=>'Budi','score'=>85,'passed'=>true], ...]
// First-class callables — cleaner for existing functions
$words = [' hello ', ' WORLD ', ' php '];
$trimmed = array_map(trim(...), $words); // trim every element
$lower = array_map(strtolower(...), $trimmed); // lowercase
array_filter — Select Elements by Condition
#
<?php
$products = [
['name' => 'Laptop', 'price' => 15_000_000, 'stock' => 5],
['name' => 'Monitor', 'price' => 5_000_000, 'stock' => 0],
['name' => 'Mouse', 'price' => 250_000, 'stock' => 20],
['name' => 'Keyboard','price' => 800_000, 'stock' => 0],
];
// Filter available products (stock > 0)
$available = array_filter($products, fn($p) => $p['stock'] > 0);
// Laptop and Mouse
// Filter expensive products (price > 1 million)
$expensive = array_filter($products, fn($p) => $p['price'] > 1_000_000);
// Laptop and Monitor
// Without a callback — remove falsy values (false, 0, "", null, [])
$data = [1, 0, 'hello', '', null, false, 2, [], 3];
$clean = array_filter($data);
// [0=>1, 2=>'hello', 6=>2, 8=>3] — indices are PRESERVED
// Reindex after filtering if needed
$clean = array_values(array_filter($data));
// [0=>1, 1=>'hello', 2=>2, 3=>3]
// ANTI-PATTERN: explicit loop for a simple filter
$result = [];
foreach ($products as $p) {
if ($p['stock'] > 0) {
$result[] = $p;
}
}
// CORRECT: array_filter is more expressive
$result = array_values(array_filter($products, fn($p) => $p['stock'] > 0));
array_reduce — Aggregate to a Single Value
#
<?php
$items = [
['name' => 'Laptop', 'price' => 15_000_000, 'qty' => 1],
['name' => 'Mouse', 'price' => 250_000, 'qty' => 2],
['name' => 'Monitor', 'price' => 5_000_000, 'qty' => 1],
];
// Calculate the shopping total
$total = array_reduce(
$items,
fn($carry, $item) => $carry + ($item['price'] * $item['qty']),
0 // initial value
);
// 15_000_000 + 500_000 + 5_000_000 = 20_500_000
// Build a lookup table from an array
$lookup = array_reduce(
$items,
fn($carry, $item) => array_merge($carry, [$item['name'] => $item['price']]),
[]
);
// ['Laptop' => 15000000, 'Mouse' => 250000, 'Monitor' => 5000000]
// More efficient with spread
$lookup = array_reduce(
$items,
fn($carry, $item) => [...$carry, $item['name'] => $item['price']],
[]
);
Pipeline: Combining map + filter + reduce #
<?php
$orders = [
['id' => 1, 'total' => 150_000, 'status' => 'completed', 'discount' => 0],
['id' => 2, 'total' => 800_000, 'status' => 'cancelled', 'discount' => 0.1],
['id' => 3, 'total' => 2_500_000,'status' => 'completed', 'discount' => 0.15],
['id' => 4, 'total' => 300_000, 'status' => 'completed', 'discount' => 0],
['id' => 5, 'total' => 900_000, 'status' => 'processing','discount' => 0.05],
];
// Pipeline: filter completed → calculate after discount → sum
$netRevenue = array_reduce(
array_map(
fn($o) => $o['total'] * (1 - $o['discount']), // step 2: calculate net
array_filter($orders, fn($o) => $o['status'] === 'completed') // step 1: filter
),
fn($carry, $net) => $carry + $net, // step 3: sum
0
);
// (150000 * 1) + (2500000 * 0.85) + (300000 * 1) = 2_675_000
array_column — Extract a Column from a Multidimensional Array
#
array_column is one of the most useful array functions that’s often overlooked:
<?php
$users = [
['id' => 1, 'name' => 'Budi', 'email' => '[email protected]', 'role' => 'admin'],
['id' => 2, 'name' => 'Siti', 'email' => '[email protected]', 'role' => 'editor'],
['id' => 3, 'name' => 'Dani', 'email' => '[email protected]', 'role' => 'viewer'],
['id' => 4, 'name' => 'Rina', 'email' => '[email protected]', 'role' => 'editor'],
];
// Extract one column as a flat array
$allNames = array_column($users, 'name');
// ['Budi', 'Siti', 'Dani', 'Rina']
$allEmails = array_column($users, 'email');
// ['[email protected]', '[email protected]', ...]
// Extract a column keyed by another column (build a lookup table)
$emailById = array_column($users, 'email', 'id');
// [1 => '[email protected]', 2 => '[email protected]', ...]
$userByEmail = array_column($users, null, 'email');
// ['[email protected]' => ['id'=>1,'name'=>'Budi',...], ...]
// Practical example: find a user by ID in O(1)
$userById = array_column($users, null, 'id');
$user = $userById[2] ?? null; // directly, no loop
// Grouping with array_reduce + array_column
$byRole = array_reduce(
$users,
function($carry, $user) {
$carry[$user['role']][] = $user;
return $carry;
},
[]
);
// ['admin' => [...], 'editor' => [..., ...], 'viewer' => [...]]
Sorting #
PHP provides many sorting functions. Choosing the right one matters because the results can differ significantly.
Basic Sorting #
<?php
$numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3];
// sort() — ascending, RESETS keys
sort($numbers); // [1,1,2,3,3,4,5,5,6,9]
// rsort() — descending, RESETS keys
rsort($numbers); // [9,6,5,5,4,3,3,2,1,1]
// asort() — ascending, PRESERVES keys (for associative arrays)
$scores = ['Budi' => 85, 'Siti' => 92, 'Dani' => 78];
asort($scores);
// ['Dani' => 78, 'Budi' => 85, 'Siti' => 92]
// arsort() — descending, preserves keys
arsort($scores);
// ['Siti' => 92, 'Budi' => 85, 'Dani' => 78]
// ksort() — sort by KEY ascending
ksort($scores);
// ['Budi' => 85, 'Dani' => 78, 'Siti' => 92]
// krsort() — sort by KEY descending
krsort($scores);
usort — Sorting with Custom Criteria
#
<?php
$products = [
['name' => 'Laptop', 'price' => 15_000_000, 'rating' => 4.5],
['name' => 'Monitor', 'price' => 5_000_000, 'rating' => 4.2],
['name' => 'Mouse', 'price' => 250_000, 'rating' => 4.8],
['name' => 'Keyboard','price' => 800_000, 'rating' => 4.1],
];
// Sort by price ascending
usort($products, fn($a, $b) => $a['price'] <=> $b['price']);
// Sort by price descending
usort($products, fn($a, $b) => $b['price'] <=> $a['price']);
// Sort by rating descending, then name ascending when ratings match
usort($products, fn($a, $b)
=> [$b['rating'], $a['name']] <=> [$a['rating'], $b['name']]
);
// Array comparison element-by-element — an elegant tuple-sorting technique
// uasort() — like usort but preserves keys
$ratings = ['Laptop' => 4.5, 'Monitor' => 4.2, 'Mouse' => 4.8];
uasort($ratings, fn($a, $b) => $b <=> $a); // descending, keys preserved
// uksort() — sort by key with a custom comparator
uksort($ratings, fn($a, $b) => strlen($a) <=> strlen($b)); // sort by name length
Natural Sort — Sorting Strings Containing Numbers #
<?php
$files = ['file10.txt', 'file2.txt', 'file1.txt', 'file20.txt'];
sort($files);
// ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'] — string sort, 10 < 2 :(
natsort($files);
// ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt'] — natural, correct!
natcasesort($files); // like natsort but case-insensitive
Searching and Checking #
<?php
$fruits = ['apple', 'mango', 'orange', 'grape', 'mango'];
// Check whether a value exists
var_dump(in_array('orange', $fruits)); // true
var_dump(in_array('watermelon', $fruits)); // false
var_dump(in_array('0', [false, null, ''])); // true! — use strict mode
// Strict mode — === comparison
var_dump(in_array('0', [false, null, ''], true)); // false — strict
// Find the index/key of a value
$index = array_search('mango', $fruits); // 1 (first one found)
var_dump(array_search('watermelon', $fruits)); // false — doesn't exist
// Check whether a key exists
var_dump(array_key_exists('name', ['name' => null, 'age' => 28])); // true
var_dump(isset(['name' => null]['name'])); // false!
// Get all keys or all values
$assoc = ['a' => 1, 'b' => 2, 'c' => 3];
$keys = array_keys($assoc); // ['a', 'b', 'c']
$values = array_values($assoc); // [1, 2, 3]
// array_keys with a value filter — find all keys with a specific value
$duplicates = ['apple', 'mango', 'orange', 'mango', 'apple'];
$mangoKeys = array_keys($duplicates, 'mango'); // [1, 3]
Merging and Splitting #
<?php
// array_merge — right string keys override left
$defaults = ['timeout' => 30, 'retry' => 3, 'debug' => false];
$custom = ['timeout' => 10, 'host' => 'api.example.com'];
$config = array_merge($defaults, $custom);
// ['timeout'=>10, 'retry'=>3, 'debug'=>false, 'host'=>'api.example.com']
// Spread operator — like merge but more concise
$config2 = [...$defaults, ...$custom];
// Identical result to array_merge for non-integer-keyed arrays
// WARNING: array_merge vs + (union)
$a = ['x' => 1, 'y' => 2];
$b = ['y' => 9, 'z' => 3];
print_r(array_merge($a, $b)); // y => 9 (right wins)
print_r($a + $b); // y => 1 (left wins!)
// array_slice — take a slice of an array
$numbers = [10, 20, 30, 40, 50, 60];
$slice = array_slice($numbers, 1, 3); // [20, 30, 40]
$tail = array_slice($numbers, -2); // [50, 60]
$withKeys = array_slice($numbers, 1, 3, true); // [1=>20, 2=>30, 3=>40]
// array_chunk — split an array into several parts
$data = range(1, 10);
$batch = array_chunk($data, 3);
// [[1,2,3], [4,5,6], [7,8,9], [10]]
$batchWithKeys = array_chunk($data, 3, true); // preserve keys
// array_combine — join two arrays into key-value pairs
$names = ['Budi', 'Siti', 'Dani'];
$scores = [85, 92, 78];
$result = array_combine($names, $scores);
// ['Budi' => 85, 'Siti' => 92, 'Dani' => 78]
Set Operations — Unique, Intersection, Difference #
<?php
$a = [1, 2, 3, 4, 5];
$b = [3, 4, 5, 6, 7];
// Unique elements in $a that aren't in $b (difference)
$onlyInA = array_diff($a, $b); // [0=>1, 1=>2]
// Unique elements in $b that aren't in $a
$onlyInB = array_diff($b, $a); // [3=>6, 4=>7]
// Elements in both (intersection)
$inBoth = array_intersect($a, $b); // [2=>3, 3=>4, 4=>5]
// Remove duplicates
$duplicates = [1, 2, 2, 3, 3, 3, 4];
$unique = array_unique($duplicates); // [0=>1, 1=>2, 3=>3, 6=>4]
$uniqueReindexed = array_values(array_unique($duplicates)); // [1,2,3,4]
// Operations on associative arrays
$profile1 = ['name' => 'Budi', 'city' => 'Jakarta', 'age' => 28];
$profile2 = ['name' => 'Budi', 'city' => 'Bandung', 'hobby' => 'coding'];
$valueDiff = array_diff_assoc($profile1, $profile2); // ['city'=>'Jakarta']
$keyDiff = array_diff_key($profile1, $profile2); // ['age'=>28]
$sameBoth = array_intersect_assoc($profile1, $profile2); // ['name'=>'Budi']
Arrays as Stacks and Queues #
<?php
// Stack — LIFO (Last In, First Out)
$stack = [];
array_push($stack, 'first'); // or: $stack[] = 'first'
array_push($stack, 'second');
array_push($stack, 'third');
$top = array_pop($stack); // 'third' — take from the top
echo array_pop($stack); // 'second'
// Queue — FIFO (First In, First Out)
$queue = [];
array_push($queue, 'queue1');
array_push($queue, 'queue2');
array_push($queue, 'queue3');
$front = array_shift($queue); // 'queue1' — take from the front (FIFO)
// Deque — Double-ended queue
$deque = ['middle'];
array_unshift($deque, 'front'); // add to the front
array_push($deque, 'back'); // add to the back
$from_front = array_shift($deque); // take from the front
$from_back = array_pop($deque); // take from the back
Math Functions on Arrays #
<?php
$data = [5, 3, 8, 1, 9, 2, 7, 4, 6];
echo min($data); // 1
echo max($data); // 9
echo array_sum($data); // 45
echo array_product($data); // 362880 (5 × 3 × 8 × 1 × ... × 6)
// Average
$average = array_sum($data) / count($data); // 5
// Advanced math functions (need a sorted array)
sort($data);
$median = $data[intdiv(count($data), 2)]; // the middle element
// array_fill and array_fill_keys
$defaultStock = array_fill(0, 5, 0); // [0,0,0,0,0] — 5 zeros starting at index 0
$defaultConfig = array_fill_keys(
['debug', 'cache', 'log'],
false
); // ['debug'=>false, 'cache'=>false, 'log'=>false]
// range() — create a sequential array
$numbers1to10 = range(1, 10); // [1,2,3,4,5,6,7,8,9,10]
$evens = range(0, 20, 2); // [0,2,4,6,8,10,12,14,16,18,20]
$letters = range('a', 'f'); // ['a','b','c','d','e','f']
array_walk and array_map — When to Use Which
#
<?php
$prices = ['laptop' => 15_000_000, 'mouse' => 250_000, 'monitor' => 5_000_000];
// array_map — produce a NEW array, doesn't modify the original
$pricesWithVat = array_map(fn($p) => $p * 1.11, $prices);
// $prices is unchanged
// array_walk — modifies the array IN-PLACE via reference
// The callback receives: &$value, $key, $extra_data (optional)
array_walk($prices, function(&$value, $key) {
$value = $value * 1.11;
}); // $prices now contains prices + VAT
// array_walk_recursive — traverse multidimensional arrays
$nested = ['a' => [1, 2], 'b' => [3, 4], 'c' => 5];
array_walk_recursive($nested, function(&$value) {
$value *= 2;
});
// ['a' => [2, 4], 'b' => [6, 8], 'c' => 10]
Summary #
- Every PHP array is an ordered map — it preserves insertion order and supports both integer and string keys.
unset()doesn’t reset indices — usearray_values()to reindex.array_mapfor transformation (produces a new array),array_filterfor selection (keeps a subset of elements),array_reducefor aggregation (turns it into a single value) — the combination of all three forms an expressive data pipeline.array_columnis the most concise way to extract a column from a multidimensional array, and with a third argument it can build a[key => row]lookup table for O(1) lookups.usort+ the spaceship operator<=>is the idiomatic way to sort with custom criteria. For multi-criteria, compare arrays directly:[$a['price'], $a['name']] <=> [$b['price'], $b['name']].in_arraywith strict mode (trueas the third argument) prevents unwanted type conversion, likein_array('0', [false])producingtruewithout strict mode.array_mergevs+(union) — a critical difference:array_mergelets right keys win for string keys;+preserves left keys. For numeric keys,array_mergeresets indices,+doesn’t.- The spread operator
[...$a, ...$b]is a modern, more concise alternative toarray_mergethat can be used directly in expressions.array_uniquefollowed byarray_values— to get a unique array with clean indices.array_uniqueby itself preserves original keys, which can be sparse.