Free Handbook · Every example compiled & verified

Arrays

Indexed and associative arrays, adding and removing, array_map/filter/reduce, sorting with usort and <=>, searching, slicing, destructuring and copy-on-write.

0 / 148 lessons🔥 0 day streak
ShareXLinkedIn

Module 04 · what you'll be able to do

  • Build indexed, associative and nested arrays and read them without "Undefined array key" warnings
  • Add and remove elements with [], array_push/pop/shift/unshift and unset, and explain the gaps unset leaves
  • Transform data with array_map, array_filter and array_reduce, and sort it with sort, asort, ksort and usort
  • Search with in_array (strict), array_search, array_key_exists and isset, and cut with array_slice and array_splice
  • Destructure arrays, spread them, and predict when an assignment copies an array
01

Indexed and associative arrays

PHP has one array type, and it does two jobs. An indexed array uses integer keys 0, 1, 2… — a list. An associative array uses keys you choose, usually strings — a dictionary or map. Under the hood both are the same thing: an ordered map from keys to values. It remembers insertion order, and keys can be ints or strings (nothing else). Write arrays with square brackets; the old array(...) syntax still works but modern code does not use it.

phpmain.php
<?php
$langs = ['PHP', 'SQL', 'JavaScript'];          // indexed: keys 0, 1, 2
$user = ['name' => 'Asha', 'age' => 31];        // associative

echo $langs[0], " and ", $langs[2], "\n";
echo $user['name'], " is ", $user['age'], "\n";
echo count($langs), " languages\n";

$user['city'] = 'Pune';                         // add a key
$user['age'] = 32;                              // overwrite a key
print_r($user);

// keys are normalised: "5" becomes int 5, true becomes 1
$mixed = ["5" => 'a', true => 'b', 1 => 'c'];
var_dump(array_keys($mixed));
Outputcompiled & run with real PHP
PHP and JavaScript
Asha is 31
3 languages
Array
(
    [name] => Asha
    [age] => 32
    [city] => Pune
)
array(2) {
  [0]=>
  int(5)
  [1]=>
  int(1)
}

The string "5" became the int key 5, and true became the int key 1, so 'c' overwrote 'b'. A float key is cut to an int and PHP 8.1+ prints a deprecation. Stick to int and string keys and this never surprises you.

Your turn

Add a 'skills' key to $user whose value is the $langs array, then print the second skill with $user['skills'][1].

print_r, var_dump and json_encode for looking inside
print_r is the readable dump, var_dump adds types and lengths, and json_encode($arr) gives a one-line view. An indexed array with keys 0..n-1 encodes to a JSON list [...]; anything else encodes to an object {...} — which matters once you build APIs (Module 09).
Error you will hit

Warning: Undefined array key

php
<?php
$user = ['name' => 'Asha', 'city' => 'Pune'];
echo "Email: " . $user['email'] . "\n";
Warning: Undefined array key "email" in main.php on line 3
Email: 
Why PHP said that

Reading a key that does not exist is not fatal in PHP: it warns, gives you null, and carries on — so the page prints an empty email and a warning. With an indexed array the message is Undefined array key 3, the classic off-by-one.

The fix

When a key is optional, give it a default with ??. When it must exist, check with isset() or array_key_exists() and handle the missing case.

php
<?php
$user = ['name' => 'Asha', 'city' => 'Pune'];
echo "Email: " . ($user['email'] ?? 'not given') . "\n";
02

Adding and removing elements

$arr[] = $x appends with the next integer key (one more than the largest int key so far). array_push / array_pop work on the end; array_unshift / array_shift work on the front and renumber integer keys. unset($arr[$k]) removes one key and does not renumber — it leaves a gap.

phpmain.php
<?php
$queue = ['a', 'b'];
$queue[] = 'c';                    // append
array_push($queue, 'd', 'e');      // append several
echo implode(" ", $queue), "\n";

$last = array_pop($queue);         // remove from the end
$first = array_shift($queue);      // remove from the front
echo "popped $last, shifted $first: ", implode(" ", $queue), "\n";

array_unshift($queue, 'z');        // add to the front
echo implode(" ", $queue), "\n";

$empty = [];
var_dump(array_pop($empty));       // null, no error
Outputcompiled & run with real PHP
a b c d e
popped e, shifted a: b c d
z b c d
NULL

unset leaves gaps

VisualizeWhy $nums[] after unset does not fill the holeStep 1 / 6
<?php
$nums = [10, 20, 30];
unset($nums[1]);
$nums[] = 40;
echo count($nums), "\n";
$nums = array_values($nums);
echo implode(",", array_keys($nums));
Line 2

Three elements with keys 0, 1, 2.

Variables now
$nums[0=>10, 1=>20, 2=>30]
All 6 steps as a table
StepLineWhat happenedVariables now
12Three elements with keys 0, 1, 2.$nums = [0=>10, 1=>20, 2=>30]
23unset removes key 1. Keys 0 and 2 stay as they are — there is now a gap.$nums = [0=>10, 2=>30]
34[] uses the largest int key ever used plus one: 3. The gap at 1 is not reused.$nums = [0=>10, 2=>30, 3=>40]
45Three elements, but $nums[1] would be an undefined key and a for ($i = 0; $i < count(...) loop would hit it.
56array_values returns the values with fresh keys 0, 1, 2.$nums = [0=>10, 1=>30, 2=>40]
67The keys are a clean list again.
phpmain.php
<?php
$nums = [10, 20, 30];
unset($nums[1]);
var_dump(array_is_list($nums));     // PHP 8.1: keys 0..n-1 in order?
echo json_encode($nums), "\n";      // a gap turns a JSON list into an object

$nums = array_values($nums);
var_dump(array_is_list($nums));
echo json_encode($nums), "\n";
Outputcompiled & run with real PHP
bool(false)
{"0":10,"2":30}
bool(true)
[10,30]

This is the real-world cost of a gap: an API that returned a JSON list suddenly returns an object and the mobile app crashes. Call array_values() after removing items from a list.

Error you will hit

Error: Cannot use a scalar value as an array

php
<?php
$total = 0;
$total[] = 5;
Fatal error: Uncaught Error: Cannot use a scalar value as an array in main.php:3
Stack trace:
#0 {main}
  thrown in main.php on line 3
Why PHP said that

$total already holds an int. PHP will create an array from null or an undefined variable when you write $x[] = ..., but it will not turn an int, float or true into one. (A string gives a different message: [] operator not supported for strings.) It usually means a variable was reused for two jobs.

The fix

Initialise the variable as an array, and give the number its own name.

php
<?php
$amounts = [];
$amounts[] = 5;
$total = array_sum($amounts);
03

Iterating over arrays

foreach is the way to walk an array, with or without keys — Module 02 covers it in depth, including foreach by reference and its trap. A for loop with an index only works on a clean list, so prefer foreach. A few helpers answer common questions without a loop at all.

phpmain.php
<?php
$stock = ['pen' => 120, 'notebook' => 0, 'stapler' => 8];

foreach ($stock as $item => $qty) {
    $label = $qty === 0 ? "out of stock" : "$qty left";
    echo "$item: $label\n";
}

echo implode(", ", array_keys($stock)), "\n";
echo array_sum($stock), " items in total\n";
echo "Largest: ", max($stock), "\n";
echo array_key_first($stock), " .. ", array_key_last($stock), "\n";
print_r(array_count_values(['php', 'sql', 'php', 'go', 'php']));
print_r(array_combine(['a', 'b'], [1, 2]));
Outputcompiled & run with real PHP
pen: 120 left
notebook: out of stock
stapler: 8 left
pen, notebook, stapler
128 items in total
Largest: 120
pen .. stapler
Array
(
    [php] => 3
    [sql] => 1
    [go] => 1
)
Array
(
    [a] => 1
    [b] => 2
)
Your turn

Use array_flip($stock) and print the result. What happens to the keys, and why is it only safe when every value is unique?

04

array_map, array_filter and array_reduce

These three replace most loops that build a new array. array_map($fn, $arr) returns a new array of $fn(value). array_filter($arr, $fn) keeps the elements where $fn returns true. array_reduce($arr, $fn, $start) folds everything into one value. Note the argument order: map takes the callback first, filter and reduce take the array first — a famous PHP inconsistency. The fn($x) => ... arrow functions are explained in Module 05.

phpmain.php
<?php
$prices = [120, 45, 300, 80];

$withTax = array_map(fn($p) => $p * 1.18, $prices);
echo implode(" ", $withTax), "\n";

$cheap = array_filter($prices, fn($p) => $p < 100);
print_r($cheap);                        // keys 1 and 3 are kept!
echo implode(" ", array_values($cheap)), "\n";

$total = array_reduce($prices, fn($carry, $p) => $carry + $p, 0);
echo "Total: $total\n";

// filter by key, or by key and value
$user = ['name' => 'Asha', 'password' => 'x9', 'city' => 'Pune'];
$safe = array_filter($user, fn($k) => $k !== 'password', ARRAY_FILTER_USE_KEY);
echo json_encode($safe), "\n";

// no callback: drops every falsy value ("", 0, null, "0", false)
echo json_encode(array_values(array_filter(['a', '', null, 'b', 0]))), "\n";
Outputcompiled & run with real PHP
141.6 53.1 354 94.4
Array
(
    [1] => 45
    [3] => 80
)
45 80
Total: 545
{"name":"Asha","city":"Pune"}
["a","b"]
Your turn

Use array_map with two arrays — array_map(fn($name, $qty) => "$name x$qty", $names, $qtys) — to zip a list of names and a list of quantities.

array_filter keeps the original keys
Filtering [120, 45, 300, 80] down to the cheap items gives keys 1 and 3, not 0 and 1. $cheap[0] is an undefined key, and json_encode produces an object. Wrap the result in array_values() whenever you want a list back.
Error you will hit

TypeError: count(): Argument #1 must be of type Countable|array

php
<?php
$rows = null;   // e.g. a query helper that returned null for "no rows"
echo count($rows);
Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given in main.php:3
Stack trace:
#0 {main}
  thrown in main.php on line 3
Why PHP said that

Since PHP 8, array functions check their argument types: count, implode, array_map, in_array and friends throw a TypeError when given null, a string or an int instead of an array. In PHP 7 this was only a warning, so old code that "worked" breaks on upgrade.

The fix

Make "nothing" an empty array, not null: return [] from your helpers, or default at the call site with ?? [].

php
<?php
$rows = null;
echo count($rows ?? []);   // 0
05

Sorting: sort, asort, ksort and usort

PHP sort functions sort the array in place (they take it by reference) and return true — not the sorted array. The name tells you what they keep: sort/rsort sort values and throw the keys away; asort/arsort sort by value and keep each key with its value; ksort/krsort sort by key. usort, uasort and uksort take your own comparison function, which returns a negative number, 0 or a positive number — exactly what the spaceship operator <=> produces.

phpmain.php
<?php
$scores = ['Ravi' => 91, 'Asha' => 72, 'Kiran' => 85];

$copy = $scores;
sort($copy);                         // values only, keys lost
echo json_encode($copy), "\n";

$copy = $scores;
arsort($copy);                       // by value, high to low, keys kept
echo json_encode($copy), "\n";

$copy = $scores;
ksort($copy);                        // by key
echo json_encode($copy), "\n";

$users = [
    ['name' => 'Ravi', 'age' => 27],
    ['name' => 'Asha', 'age' => 31],
    ['name' => 'Kiran', 'age' => 27],
];
// by age ascending, then name ascending
usort($users, fn($a, $b) => [$a['age'], $a['name']] <=> [$b['age'], $b['name']]);
echo implode(", ", array_column($users, 'name')), "\n";

// descending: swap the sides
usort($users, fn($a, $b) => $b['age'] <=> $a['age']);
echo $users[0]['name'], " is the oldest\n";
Outputcompiled & run with real PHP
[72,85,91]
{"Ravi":91,"Kiran":85,"Asha":72}
{"Asha":72,"Kiran":85,"Ravi":91}
Kiran, Ravi, Asha
Asha is the oldest

Comparing two arrays with <=> compares them element by element, which gives a multi-column sort in one line. Since PHP 8.0 sorting is stable: equal elements keep their original order.

Your turn

Sort $users by name length, shortest first, using strlen inside the comparison.

$sorted = sort($arr) is a bug
sort() returns true, so $sorted is a boolean and $arr is the one that changed. If you need to keep the original, copy it first ($sorted = $arr; sort($sorted);). array_column($rows, 'name'), used above, pulls one field out of every row.
06

Searching: in_array, array_search, isset and array_key_exists

Two questions, two sets of tools. Is this value in the array? — in_array($needle, $arr, true) answers yes/no and array_search returns its key (or false). Always pass true as the third argument for a strict === comparison. Does this key exist? — isset($arr[$k]) is fast but says false when the value is null; array_key_exists($k, $arr) answers exactly "is the key there".

phpmain.php
<?php
$codes = ['0', 'abc', '10'];
var_dump(in_array(0, $codes));          // loose: 0 == "0"
var_dump(in_array('1e1', $codes));      // loose: "1e1" == "10" as numbers
var_dump(in_array('1e1', $codes, true));

$roles = ['admin', 'editor', 'viewer'];
$pos = array_search('admin', $roles, true);
var_dump($pos);                          // 0 - found at the start
echo $pos !== false ? "admin found\n" : "no admin\n";

$config = ['debug' => null, 'port' => 8080];
var_dump(isset($config['debug']));               // null counts as "not set"
var_dump(array_key_exists('debug', $config));    // the key is there
var_dump(isset($config['port'], $config['debug']));
Outputcompiled & run with real PHP
bool(true)
bool(true)
bool(false)
int(0)
admin found
bool(false)
bool(true)
bool(false)

array_search has the same "0 is falsy" trap as strpos in Module 03: compare its result with !== false. isset with several arguments is true only if all of them are set.

isset($a[$k])

  • Language construct, very fast
  • false when the key is missing or the value is null
  • Never warns, works on nested keys: isset($a['x']['y'])
  • Right for "do I have a usable value?"

array_key_exists($k, $a)

  • A normal function call
  • true whenever the key exists, even with a null value
  • Checks one level of one array
  • Right for "was this field sent at all?" (e.g. a PATCH that sets a value to null)
Quick check

What does var_dump(in_array("abc", [0])); print in PHP 8?

07

Slicing, splicing, merging and spreading

array_slice($arr, $offset, $length) returns a piece and leaves the original alone. array_splice($arr, $offset, $length, $replacement) changes the original — it cuts a piece out, optionally inserts new elements, and returns what it removed. To combine arrays, the spread operator [...$a, ...$b] and array_merge append lists and let later string keys win; the + operator is different — it keeps the left value for any key both arrays have.

phpmain.php
<?php
$letters = ['a', 'b', 'c', 'd', 'e'];

echo implode("", array_slice($letters, 1, 3)), "\n";    // b c d
echo implode("", array_slice($letters, -2)), "\n";      // last two

$removed = array_splice($letters, 1, 2, ['X', 'Y', 'Z']);
echo implode("", $letters), " (removed ", implode("", $removed), ")\n";

array_splice($letters, 1, 0, ['+']);                      // insert, remove nothing
echo implode("", $letters), "\n";

$a = [1, 2];
$b = [3, 4];
echo json_encode([...$a, ...$b, 5]), "\n";

$defaults = ['theme' => 'light', 'lang' => 'en'];
$chosen = ['lang' => 'hi'];
echo json_encode([...$defaults, ...$chosen]), "\n";     // later key wins
echo json_encode(array_merge($defaults, $chosen)), "\n";
echo json_encode($chosen + $defaults), "\n";            // left key wins
echo json_encode([1, 2] + [7, 8, 9]), "\n";             // + is by key!
Outputcompiled & run with real PHP
bcd
de
aXYZde (removed bc)
a+XYZde
[1,2,3,4,5]
{"theme":"light","lang":"hi"}
{"theme":"light","lang":"hi"}
{"lang":"hi","theme":"light"}
[1,2,9]

[1, 2] + [7, 8, 9] keeps keys 0 and 1 from the left and only adds key 2 from the right. Use + for "fill in missing defaults", and spread or array_merge to join lists.

08

Destructuring and swapping

Destructuring unpacks an array into variables in one assignment. [$a, $b] = $pair takes elements by position; ['id' => $id] = $row takes them by key. You can skip positions with an empty slot, nest patterns, and swap two variables without a temporary. list($a, $b) = ... is the older spelling of the same thing.

phpmain.php
<?php
[$first, $second] = ['gold', 'silver', 'bronze'];
echo "$first, $second\n";

[, , $third] = ['gold', 'silver', 'bronze'];         // skip positions
echo "$third\n";

$row = ['id' => 7, 'email' => '[email protected]', 'active' => true];
['id' => $id, 'email' => $email] = $row;             // by key, any order
echo "#$id $email\n";

[$x, [$y, $z]] = [1, [2, 3]];                          // nested
echo "$x $y $z\n";

$a = 'left';
$b = 'right';
[$a, $b] = [$b, $a];                                   // swap
echo "$a $b\n";

// functions "return several values" by returning an array
function minMax(array $nums): array
{
    return [min($nums), max($nums)];
}
[$lo, $hi] = minMax([4, 9, 1, 7]);
echo "min $lo, max $hi\n";
Outputcompiled & run with real PHP
gold, silver
bronze
#7 [email protected]
1 2 3
right left
min 1, max 9
Your turn

Split "[email protected]" with explode("@", ...) and destructure the result into $user and $domain in one line.

09

Multidimensional arrays

An array value can be another array, which is how PHP represents tables, JSON documents and database result sets: a list of associative arrays. Chain the brackets to reach inside, and use array_column to pull a column out or re-key the rows.

phpmain.php
<?php
$orders = [
    ['id' => 101, 'customer' => 'Asha', 'items' => ['pen', 'ink']],
    ['id' => 102, 'customer' => 'Ravi', 'items' => ['notebook']],
    ['id' => 103, 'customer' => 'Asha', 'items' => []],
];

echo $orders[0]['items'][1], "\n";
echo implode(",", array_column($orders, 'id')), "\n";

$byId = array_column($orders, null, 'id');       // re-key rows by id
echo $byId[102]['customer'], "\n";

// group rows by a field
$byCustomer = [];
foreach ($orders as $order) {
    $byCustomer[$order['customer']][] = $order['id'];
}
echo json_encode($byCustomer), "\n";

$grid = [[1, 2, 3], [4, 5, 6]];
foreach ($grid as $r => $row) {
    foreach ($row as $c => $cell) {
        echo "[$r][$c]=$cell ";
    }
    echo "\n";
}
Outputcompiled & run with real PHP
ink
101,102,103
Ravi
{"Asha":[101,103],"Ravi":[102]}
[0][0]=1 [0][1]=2 [0][2]=3
[1][0]=4 [1][1]=5 [1][2]=6

$byCustomer[$name][] = $id creates the inner array on first use — a PHP idiom you will see in every codebase that groups rows.

Your turn

Count the items per customer: build ['Asha' => 2, 'Ravi' => 1] with a loop and count($order['items']).

10

Arrays are values: copy on assignment

In PHP, arrays are values, not shared objects. $b = $a gives $b its own array; changing one never changes the other. The same happens when you pass an array to a function. PHP makes this cheap with copy-on-write: both variables point at the same memory until one of them is modified, and only then is the real copy made. If you truly want two names for one array, use a reference ($b = &$a) — rarely a good idea. Objects behave differently; Module 06 shows why.

phpmain.php
<?php
$a = [1, 2, 3];
$b = $a;          // shares memory until someone writes
$b[] = 4;         // now $b gets its own copy
echo json_encode($a), " ", json_encode($b), "\n";

function addItem(array $list): array
{
    $list[] = 'new';          // changes the function's copy
    return $list;
}
$items = ['old'];
$more = addItem($items);
echo json_encode($items), " ", json_encode($more), "\n";

$c = &$a;         // reference: two names, one array
$c[] = 99;
echo json_encode($a), "\n";
Outputcompiled & run with real PHP
[1,2,3] [1,2,3,4]
["old"] ["old","new"]
[1,2,3,99]

Passing a large array into a function costs nothing until the function writes to it. Do not pass arrays by reference "for speed" — it makes code harder to follow and is often slower.

Indexed array
An array whose keys are 0, 1, 2… in order — a list. array_is_list() checks this.
Associative array
An array with keys you choose, usually strings: ['name' => 'Asha'].
Ordered map
What a PHP array really is: keys (int or string) mapped to values, in insertion order.
array_values
Returns the values with fresh keys 0..n-1. Use it after unset or array_filter.
Spaceship operator
$a <=> $b returns -1, 0 or 1; the natural return value for a usort callback.
Destructuring
Unpacking an array into variables: [$a, $b] = $arr or ['k' => $v] = $arr.
Spread operator
...$arr inside an array literal or a function call unpacks the elements in place.
Copy-on-write
Assigning an array shares its memory until one side is modified; then PHP copies it. Arrays behave as values.
Quick check

After $a = [3, 1, 2]; $b = $a; sort($b); what is $a?

Frequently asked questions

What is the difference between an indexed and an associative array in PHP?
Technically none: every PHP array is an ordered map from int or string keys to values. An indexed array is one whose keys are 0, 1, 2 in order (a list), and an associative array uses keys you choose, such as names. array_is_list() tells you which one you have.
Why does array_filter return an array with missing keys?
array_filter keeps each surviving element under its original key, so filtering [10, 20, 30] down to the elements above 15 gives keys 1 and 2. Wrap the call in array_values() to get a list with keys starting at 0 again.
Should I use isset or array_key_exists?
Use isset (or the ?? operator) when you want to know whether a usable, non-null value is present; it is fast and never warns. Use array_key_exists when a key that holds null must still count as present, for example to tell "field not sent" from "field set to null".

Finish the PHP handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.