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 — use array_values() to reindex.
  • array_map for transformation (produces a new array), array_filter for selection (keeps a subset of elements), array_reduce for aggregation (turns it into a single value) — the combination of all three forms an expressive data pipeline.
  • array_column is 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_array with strict mode (true as the third argument) prevents unwanted type conversion, like in_array('0', [false]) producing true without strict mode.
  • array_merge vs + (union) — a critical difference: array_merge lets right keys win for string keys; + preserves left keys. For numeric keys, array_merge resets indices, + doesn’t.
  • The spread operator [...$a, ...$b] is a modern, more concise alternative to array_merge that can be used directly in expressions.
  • array_unique followed by array_values — to get a unique array with clean indices. array_unique by itself preserves original keys, which can be sparse.

← Previous: Exceptions   Next: Date & Time →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact