Arrays
PHP's all-purpose array: lists and associative arrays, adding and removing items, looping, sorting, array_map, array_filter and friends, and destructuring.
One type for lists and dictionaries
Most languages have separate types for a numbered list and for a set of named values: Python has lists and dictionaries, JavaScript has arrays and objects. PHP has one type for both. A PHP array is an ordered map: a collection of key–value pairs that remembers the order in which you added them. Internally it is a hash table, so looking up a key is fast however large the array is.
You write an array with square brackets. Without keys, PHP numbers
the values 0, 1, 2 …, and the array behaves as a list. With
key => value pairs, you choose the keys yourself.
<?php
$cities = ['Deventer', 'Zwolle', 'Apeldoorn']; // a list
$meter = [ // an associative array
'id' => 'MTR-100231',
'city' => 'Deventer',
'kwh' => 1542.7,
];
echo $cities[0], ' ', $meter['city'], "\n";
echo count($cities), ' ', count($meter), "\n";
var_dump(array_is_list($cities), array_is_list($meter));
print_r($cities);
Deventer Deventer
3 3
bool(true)
bool(false)
Array
(
[0] => Deventer
[1] => Zwolle
[2] => Apeldoorn
)
count() gives the number of elements.
print_r() prints an array in a readable form,
var_dump() adds the type of every value, and
array_is_list() (PHP 8.1 and later) tells whether the
keys are exactly 0, 1, 2 … in order. The older
array(…) notation means the same as […];
you will still see it in older code and in WordPress.
Lists: keys 0, 1, 2 …
$array[] = $value appends a value. PHP gives it the
next free integer key: one higher than the largest integer key used
so far. Keys do not have to be consecutive, and the order of the
elements is the order of insertion, not the order of the keys.
<?php
$readings = [318.4, 296.0];
$readings[] = 301.5; // append: next free integer key
$readings[10] = 290.0; // keys need not be consecutive
$readings[] = 287.2; // next key = highest integer key + 1
print_r($readings);
echo $readings[count($readings) - 1] ?? 'none', "\n";
echo array_key_last($readings), ' ', end($readings), "\n";
Array
(
[0] => 318.4
[1] => 296
[2] => 301.5
[10] => 290
[11] => 287.2
)
none
11 287.2
$readings from the example. Each slot holds
a key and a value; the order of the slots is the insertion order.
count() is 5, but the last key is 11.
That is why $readings[count($readings) - 1] found
nothing: count() is 5, and there is no key 4. To get
the last element whatever the keys are, use
array_key_last() for its key or end() for
its value.
Associative arrays
An associative array is an array with keys you choose, usually strings: a meter's properties, the settings of a page, a row from a database. It plays the role of a dictionary in Python or a plain object in JavaScript. Assigning to a key adds it or overwrites its value, and a foreach loop visits the pairs in insertion order.
<?php
$tariffs = ['day' => 0.27, 'night' => 0.21];
$tariffs['weekend'] = 0.19; // add or overwrite by key
$tariffs['day'] = 0.28;
$tariffs['peak'] = null;
foreach ($tariffs as $period => $price) {
echo "$period: ", $price ?? 'not set', "\n";
}
var_dump(isset($tariffs['peak'])); // false: the value is null
var_dump(array_key_exists('peak', $tariffs)); // true: the key exists
echo $tariffs['holiday'] ?? 0.25, "\n"; // default for a missing key
day: 0.28 night: 0.21 weekend: 0.19 peak: not set bool(false) bool(true) 0.25
Reading a key that does not exist returns null and
writes Warning: Undefined array key. The
null coalescing operator
?? avoids that and supplies a default in one step. Be
careful with isset(): it returns
false both for a missing key and for a key whose value
is null. When that difference matters, use
array_key_exists().
Which keys are allowed
Keys can only be integers or strings. Anything else is converted when you use it as a key, and some strings are converted too. This array key casting follows fixed rules:
-
A string that is a plain decimal integer, such as
'8'or'-5', becomes that integer.'08','1.5'and' 8'stay strings. -
truebecomes 1 andfalsebecomes 0. -
nullbecomes the empty string''(deprecated since PHP 8.5: write''instead). -
A float is cut off to an integer:
1.7becomes 1. Since PHP 8.1 that gives aDeprecatedmessage when decimals are lost. -
Arrays and objects cannot be keys: using one throws an error
(
Illegal offset typeon PHP 8.2,Cannot access offset of type array on arraysince 8.3).
<?php
$keys = [
'8' => 'string "8" becomes int 8',
'08' => 'string "08" stays a string',
true => 'true becomes 1',
null => 'null becomes ""',
'1.5' => 'string "1.5" stays a string',
];
var_dump(array_keys($keys));
$stock = [];
$stock['42'] = 'meter'; // stored under the integer 42
var_dump(isset($stock[42]), array_key_first($stock));
array(5) {
[0]=>
int(8)
[1]=>
string(2) "08"
[2]=>
int(1)
[3]=>
string(0) ""
[4]=>
string(3) "1.5"
}
bool(true)
int(42)
Two different-looking keys can therefore land on the same element:
in one array, '8' and 8 are the same key,
and so are true, 1 and 1.7.
The later assignment wins. In practice this matters when keys come
from outside, such as IDs from a form or a CSV file:
$stock['42'] and $stock[42] are the same
element, which is usually what you want. But meter numbers with
leading zeros ('00042') stay strings and never match
the integer 42.
Arrays are copied, not shared
Assigning an array to another variable, or passing it to a function, gives the receiver its own copy. Changing the copy does not change the original. PHP makes this cheap by copying lazily: the data is shared until one side writes to it (copy-on-write).
<?php
$original = ['Deventer', 'Zwolle'];
$copy = $original; // arrays are copied on assignment
$copy[] = 'Apeldoorn';
echo count($original), ' ', count($copy), "\n";
function addCity(array $list): void
{
$list[] = 'Enschede'; // changes the function's own copy
}
addCity($original);
echo count($original), "\n";
2 3 2
Adding, removing and searching
array_push() and array_pop() work at the
end of a list, array_unshift() and
array_shift() at the start (and renumber the integer
keys). unset($array[$key]) removes one element by key
and does not renumber: it leaves a gap.
<?php
$queue = ['INC-31877', 'INC-31880', 'INC-31902'];
array_push($queue, 'INC-31917'); // same as $queue[] = ...
$last = array_pop($queue); // remove from the end
$first = array_shift($queue); // remove from the start (reindexes)
array_unshift($queue, 'INC-31850'); // add at the start
echo "$first $last\n";
echo implode(' ', $queue), "\n";
unset($queue[1]); // remove by key: leaves a gap
print_r($queue);
print_r(array_values($queue)); // renumber 0, 1, 2 …
INC-31877 INC-31917
INC-31850 INC-31880 INC-31902
Array
(
[0] => INC-31850
[2] => INC-31902
)
Array
(
[0] => INC-31850
[1] => INC-31902
)
Searching
in_array() tells whether a value occurs, and
array_search() returns its key, or false.
Both compare loosely with == unless you pass
true as the third argument for a strict
=== comparison.
<?php
$meterIds = ['100231', '100232', '0'];
var_dump(in_array('100232', $meterIds)); // found
var_dump(in_array(100232, $meterIds, true)); // strict: int is not string
var_dump(array_search('100232', $meterIds)); // the key of the match
var_dump(array_search('999999', $meterIds)); // not found
var_dump(in_array(0, $meterIds)); // loose: 0 == '0'
bool(true) bool(false) int(1) bool(false) bool(true)
The last line shows why strict mode is safer: the integer 0 matches
the string '0'. Since array_search() can
return the key 0, test its result with
!== false, exactly as with
strpos().
Combining arrays
array_merge() joins arrays. String keys from later
arrays overwrite earlier ones; integer keys are renumbered, so lists
are appended. The + operator is different: it keeps
everything from the left array and only adds keys that are missing.
That makes $chosen + $defaults handy for filling in
default settings, and useless for joining two lists.
<?php
$defaults = ['tariff' => 'day', 'unit' => 'kWh', 'decimals' => 1];
$chosen = ['decimals' => 2, 'currency' => 'EUR'];
print_r(array_merge($defaults, $chosen)); // later values win
print_r($chosen + $defaults); // left side wins, missing keys added
$north = ['Zwolle', 'Assen'];
$east = ['Deventer', 'Enschede'];
print_r(array_merge($north, $east)); // lists: renumbered
print_r($north + $east); // keys 0 and 1 already exist!
Array
(
[tariff] => day
[unit] => kWh
[decimals] => 2
[currency] => EUR
)
Array
(
[decimals] => 2
[currency] => EUR
[tariff] => day
[unit] => kWh
)
Array
(
[0] => Zwolle
[1] => Assen
[2] => Deventer
[3] => Enschede
)
Array
(
[0] => Zwolle
[1] => Assen
)
Sorting
PHP has a family of sort functions. All of them sort the array
in place and return true, so
$sorted = sort($values) is a mistake:
$sorted becomes true. They differ in what
they sort by and whether they keep the keys:
| Function | Sorts by | Keys |
|---|---|---|
sort() / rsort() |
Value, ascending / descending | Discarded, renumbered 0, 1, 2 … |
asort() / arsort() |
Value, ascending / descending | Kept |
ksort() / krsort() |
Key, ascending / descending | Kept |
usort() |
Your comparison function | Discarded |
uasort() / uksort() |
Your function, on values / keys | Kept |
<?php
$usage = ['Zwolle' => 1210, 'Deventer' => 1542, 'Assen' => 980];
$values = $usage;
sort($values); // values sorted, keys thrown away
print_r($values);
asort($usage); // by value, keys kept
echo implode(', ', array_keys($usage)), "\n";
arsort($usage); // by value, descending
echo implode(', ', array_keys($usage)), "\n";
ksort($usage); // by key
echo implode(', ', array_keys($usage)), "\n";
$result = sort($values);
var_dump($result); // sort() returns true, not the array
Array
(
[0] => 980
[1] => 1210
[2] => 1542
)
Assen, Zwolle, Deventer
Deventer, Zwolle, Assen
Assen, Deventer, Zwolle
bool(true)
Sorting with your own rule
usort() takes a comparison function that receives two
elements and returns a negative number, 0 or a positive number. The
spaceship operator
<=> returns exactly that. Comparing two small
arrays with it compares them element by element, which gives a sort
on several fields in one line:
<?php
$tickets = [
['id' => 'INC-31902', 'priority' => 2, 'opened' => '2026-10-02 08:10'],
['id' => 'INC-31877', 'priority' => 3, 'opened' => '2026-10-01 14:30'],
['id' => 'INC-31917', 'priority' => 2, 'opened' => '2026-10-01 22:05'],
];
// Priority first (1 = most urgent), then oldest first
usort($tickets, function (array $a, array $b): int {
return [$a['priority'], $a['opened']] <=> [$b['priority'], $b['opened']];
});
foreach ($tickets as $t) {
echo "P{$t['priority']} {$t['opened']} {$t['id']}\n";
}
P2 2026-10-01 22:05 INC-31917 P2 2026-10-02 08:10 INC-31902 P3 2026-10-01 14:30 INC-31877
Since PHP 8.0 sorting is stable: elements that compare as equal keep their original order. On older versions their order was not guaranteed.
Numbers, strings and natural order
By default the sort functions compare values with PHP's normal
comparison rules, so numeric strings are compared as numbers. A flag
changes that: SORT_STRING compares as text, and
natsort() uses the natural order people expect for file
names.
<?php
$codes = ['10', '9', '2'];
sort($codes); // numeric strings compare as numbers
echo implode(' ', $codes), "\n";
sort($codes, SORT_STRING); // compare as text, character by character
echo implode(' ', $codes), "\n";
$files = ['meter10.csv', 'meter9.csv', 'meter1.csv'];
natsort($files); // natural order (keeps keys)
echo implode(' ', $files), "\n";
2 9 10 10 2 9 meter1.csv meter9.csv meter10.csv
array_map, array_filter and friends
Instead of writing a loop for every transformation, you can hand a
function to one of the array functions. The short
arrow functions (fn($v) => …) are made for this.
- array_map() calls a function on every element and returns a new array with the results. With one input array, the keys are kept.
-
array_filter() returns the
elements for which a function returns a truthy value. Without a
function, it drops every falsy element (
0,'','0',null,false,[]). -
array_reduce()combines all elements into one value, step by step; for the common casesarray_sum(),max()andmin()are shorter.
<?php
$kwh = ['Zwolle' => 1210, 'Deventer' => 1542, 'Assen' => 980];
$costs = array_map(fn($v) => round($v * 0.27, 2), $kwh);
print_r($costs); // keys kept
$high = array_filter($kwh, fn($v) => $v > 1000);
print_r($high);
$total = array_reduce($kwh, fn($carry, $v) => $carry + $v, 0);
echo $total, ' ', array_sum($kwh), ' ', max($kwh), "\n";
Array
(
[Zwolle] => 326.7
[Deventer] => 416.34
[Assen] => 264.6
)
Array
(
[Zwolle] => 1210
[Deventer] => 1542
)
3732 3732 1542
array_filter keeps the keys
Because a PHP array is a map, array_filter() keeps each
surviving element under its original key. Filtering a list therefore
leaves gaps, and code that expects keys 0, 1, 2 breaks quietly. A
list with gaps also turns into a JSON object instead of a JSON array
when you encode it. Wrap the result in
array_values() when you need a clean list. The flags
ARRAY_FILTER_USE_KEY and
ARRAY_FILTER_USE_BOTH pass the key, or the key and the
value, to your function.
<?php
$readings = [318.4, 0, 296.0, null, 301.5];
$valid = array_filter($readings); // no callback: drops falsy values
print_r($valid); // note the keys: 0, 2, 4
echo $valid[1] ?? 'no key 1', "\n";
print_r(array_values($valid)); // renumbered from 0
// Filter on keys instead of values
$tariffs = ['day' => 0.27, 'night' => 0.21, 'day_peak' => 0.34];
print_r(array_filter($tariffs, fn($k) => str_starts_with($k, 'day'), ARRAY_FILTER_USE_KEY));
Array
(
[0] => 318.4
[2] => 296
[4] => 301.5
)
no key 1
Array
(
[0] => 318.4
[1] => 296
[2] => 301.5
)
Array
(
[day] => 0.27
[day_peak] => 0.34
)
Tables of rows: array_column
Data from a database or a CSV file usually arrives as a list of
associative arrays, one per row. array_column() pulls
one column out of such a table, optionally keyed by another column.
Other useful helpers are array_keys(),
array_values(), array_combine() (keys from
one array, values from another), array_unique(),
array_slice() and array_count_values().
<?php
$customers = [
['id' => 501, 'name' => 'Lucía Romero', 'city' => 'Deventer'],
['id' => 502, 'name' => 'Joost Bakker', 'city' => 'Zwolle'],
['id' => 503, 'name' => 'Mei Chen', 'city' => 'Deventer'],
];
print_r(array_column($customers, 'name'));
print_r(array_column($customers, 'city', 'id')); // id => city
print_r(array_count_values(array_column($customers, 'city')));
Array
(
[0] => Lucía Romero
[1] => Joost Bakker
[2] => Mei Chen
)
Array
(
[501] => Deventer
[502] => Zwolle
[503] => Deventer
)
Array
(
[Deventer] => 2
[Zwolle] => 1
)
Destructuring and spread
Destructuring assignment
copies elements of an array into separate variables in one
statement. PHP writes it with square brackets on the left of
= (or with the older list(…)). You can
destructure by position, skip elements with empty slots, destructure
by key, swap two variables, and destructure in the head of a
foreach loop.
<?php
$reading = ['MTR-100231', '2026-10-01', 1542.7];
[$meter, $date, $kwh] = $reading; // by position
echo "$meter $date $kwh\n";
[, , $onlyKwh] = $reading; // skip elements
echo $onlyKwh, "\n";
['name' => $name, 'city' => $city] = ['id' => 502, 'name' => 'Joost Bakker', 'city' => 'Zwolle'];
echo "$name lives in $city\n"; // by key
$a = 'night'; $b = 'day';
[$a, $b] = [$b, $a]; // swap
echo "$a $b\n";
$rows = [['Zwolle', 1210], ['Assen', 980]];
foreach ($rows as [$place, $usage]) { // in a foreach
echo "$place: $usage\n";
}
MTR-100231 2026-10-01 1542.7 1542.7 Joost Bakker lives in Zwolle day night Zwolle: 1210 Assen: 980
The
spread syntax
... unpacks an array into a new array literal. Integer
keys are renumbered; since PHP 8.1, string keys are allowed too, and
a later key overwrites an earlier one, as with
array_merge(). The same ... also unpacks
an array into the arguments of a function call (see
Functions).
<?php
$north = ['Zwolle', 'Assen'];
$east = ['Deventer', 'Enschede'];
$all = ['Utrecht', ...$north, ...$east]; // spread into a new list
echo implode(', ', $all), "\n";
$defaults = ['unit' => 'kWh', 'decimals' => 1];
$settings = [...$defaults, 'decimals' => 2]; // string keys: later wins
print_r($settings);
Utrecht, Zwolle, Assen, Deventer, Enschede
Array
(
[unit] => kWh
[decimals] => 2
)