Arrays
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against PHP 8.5, checked on 2026-10-05. The array functions new in recent versions are marked:
array_find(), array_find_key(), array_any() and array_all() (8.4), array_first() and array_last() (8.5).
The short [] destructuring syntax is preferred over list(), which still works without a deprecation notice in 8.5
and in the upcoming 8.6 (checked against 8.6.0RC2).
The array model
A PHP array is an ordered map: a collection of key/value pairs that remembers insertion order. Keys are int or
string; values are anything, including other arrays. The single type serves as a list, a dictionary, a set (keys
only), a stack, a queue and a tuple.
<?php
declare(strict_types=1);
$titles = ['Solaris', 'Kindred', 'Dawn']; // a list: keys 0, 1, 2
$book = ['isbn' => '978-0-00-000001-1', 'title' => 'Kindred', 'year' => 1979]; // a map
$shelves = ['A' => ['Solaris'], 'B' => ['Kindred', 'Dawn']]; // nested
$titles[] = 'Neuromancer'; // append: next integer key (3)
$book['format'] = 'paperback'; // add a key
unset($titles[1]); // remove: keys are NOT renumbered
print_r($titles); // [0 => Solaris, 2 => Dawn, 3 => Neuromancer]
var_dump(array_is_list($titles)); // bool(false): keys are 0, 2, 3
var_dump(array_is_list(array_values($titles))); // bool(true)
echo count($book), ' keys, first key: ', array_key_first($book), "\n"; // 4 keys, first key: isbn
array_is_list() (8.1) is true when the keys are exactly 0, 1, 2, … in order — the arrays that json_encode()
writes as JSON arrays rather than objects. After unset() or array_filter(), call array_values() to get a list
again.
Source: Arrays and array_is_list
Key casting rules
Keys that are not int or string are converted, and some strings are converted too:
-
a string containing a valid decimal integer without leading zeros or sign padding becomes an
int:"8"is key8, but"08","8.0"and" 8"stay strings; -
a
floatis truncated toint(1.7becomes1), which has been deprecated since 8.1 when the float has a fractional part; -
truebecomes1,falsebecomes0; -
nullbecomes""— deprecated since 8.5; -
arrays and objects cannot be keys (
TypeError), and resources become their id with a warning.
<?php
declare(strict_types=1);
$a = [];
$a['8'] = 'string "8"';
$a[8] = 'int 8'; // same key as '8': overwrites
$a['08'] = 'string "08"'; // different key
$a[true] = 'true'; // key 1
$a[1] = 'int 1'; // overwrites the "true" entry
var_dump($a);
var_dump(array_keys(['1' => 'a', '01' => 'b'])); // [1, "01"]: the first key is an int
array(3) {
[8]=>
string(5) "int 8"
["08"]=>
string(11) "string "08""
[1]=>
string(5) "int 1"
}
array(2) {
[0]=>
int(1)
[1]=>
string(2) "01"
}
Because numeric-string keys become integers, array_keys() of a map whose keys look like numbers returns ints, and
foreach ($map as $key => …) gives you an int $key. Under strict_types=1, passing such a key to a string
parameter throws; cast it with (string) $key when needed.
Source: Key casting
Packed and hash arrays, and copy-on-write
Internally, the engine stores a list with keys 0..n-1 as a packed array (a plain vector, very compact), and
anything else as a hash table. Converting between the two happens automatically, but a list that stays a list is
faster and smaller.
Arrays are values: assigning or passing an array gives the receiver its own logical copy. The engine shares the underlying memory until one side writes to it (copy-on-write), so passing large arrays to functions is cheap as long as the function does not modify them. Writing into a copy duplicates the array once; see References and Memory Management.
<?php
declare(strict_types=1);
function countLong(array $titles): int // receives a shared copy: no duplication
{
return count(array_filter($titles, fn (string $t): bool => strlen($t) > 6));
}
$titles = array_fill(0, 100_000, 'Neuromancer');
$before = memory_get_usage();
countLong($titles);
echo memory_get_usage() - $before < 100_000 ? "no copy of the input\n" : "copied\n";
Source: Arrays (copying) and Reference Counting Basics
Creating arrays
<?php
declare(strict_types=1);
print_r(range(1, 5)); // [1, 2, 3, 4, 5]
print_r(range(0, 20, 5)); // [0, 5, 10, 15, 20]
print_r(range('a', 'e')); // ["a", "b", "c", "d", "e"]
print_r(array_fill(5, 3, 0)); // [5 => 0, 6 => 0, 7 => 0]
print_r(array_fill_keys(['sf', 'poetry'], [])); // ["sf" => [], "poetry" => []]
print_r(array_combine(['isbn', 'title'], ['978-0-00-000001-1', 'Kindred']));
print_r(array_flip(['sf', 'poetry'])); // ["sf" => 0, "poetry" => 1]: values become keys
$titles = ['Solaris'];
print_r(compact('titles')); // ["titles" => ["Solaris"]]: built from variable names
compact() and its inverse extract() work with variable names as strings, which hides data flow from readers
and static analysers. Prefer explicit arrays; never call extract() on user input, which can overwrite any local
variable.
Source: range, array_fill, array_fill_keys and array_combine
Destructuring and spread
Destructuring
The short list syntax […] = $array (7.1) assigns elements to variables. Elements can be skipped, keys can be named
(7.1), destructuring can be nested, and it works in foreach. Missing elements yield null with a warning.
<?php
declare(strict_types=1);
[$first, , $third] = ['Solaris', 'Kindred', 'Dawn']; // skip an element
['title' => $title, 'year' => $year] = ['year' => 1979, 'title' => 'Kindred']; // by key, any order
[[$a, $b], [$c, $d]] = [[1, 2], [3, 4]]; // nested
$x = 'left';
$y = 'right';
[$x, $y] = [$y, $x]; // swap without a temporary
echo "{$first} {$third} | {$title} {$year} | {$a}{$b}{$c}{$d} | {$x} {$y}\n";
// Solaris Dawn | Kindred 1979 | 1234 | right left
// By reference (7.3)
$book = ['title' => 'kindred'];
['title' => &$t] = $book;
$t = ucfirst($t);
echo $book['title'], "\n"; // Kindred
list($a, $b) = … is the older equivalent of [$a, $b] = …. It still works, with no deprecation notice in 8.5 or in
the upcoming 8.6 (8.6.0RC2), but new code uses the short syntax.
Source: Array destructuring
Spread
... unpacks an array or Traversable inside an array literal (7.4) or a function call. Since 8.1, arrays with
string keys can be unpacked too, with later keys overwriting earlier ones, like array_merge():
<?php
declare(strict_types=1);
$sf = ['Solaris', 'Kindred'];
$poetry = ['Ariel'];
print_r([...$sf, ...$poetry, 'Dawn']); // ["Solaris", "Kindred", "Ariel", "Dawn"]
$defaults = ['format' => 'paperback', 'copies' => 1];
print_r([...$defaults, 'copies' => 3]); // ["format" => "paperback", "copies" => 3] (8.1)
function gen(): Generator { yield 'x'; yield 'y'; }
print_r([...gen()]); // generators unpack too
Source: Array unpacking
The array API by family
Stack and queue
<?php
declare(strict_types=1);
$returns = ['Solaris'];
array_push($returns, 'Kindred', 'Dawn'); // same as $returns[] = ...
$last = array_pop($returns); // "Dawn" -- stack (LIFO)
array_unshift($returns, 'Ariel'); // prepend: renumbers integer keys
$first = array_shift($returns); // "Ariel" -- queue (FIFO), also renumbers
var_dump($last, $first, $returns);
array_shift() and array_unshift() reindex the whole array, so they are O(n). For large queues use SplQueue (see
SPL Data Structures).
Source: array_push, array_pop, array_shift and array_unshift
Searching
<?php
declare(strict_types=1);
$books = [
['isbn' => '978-0-00-000003-5', 'title' => 'Solaris', 'year' => 1961],
['isbn' => '978-0-00-000001-1', 'title' => 'Kindred', 'year' => 1979],
['isbn' => '978-0-00-000002-8', 'title' => 'Dawn', 'year' => 1987],
];
$titles = array_column($books, 'title');
var_dump(in_array('Dawn', $titles, true)); // bool(true) -- always pass true (strict)
var_dump(array_search('Kindred', $titles, true)); // int(1) -- key, or false
var_dump(array_key_exists(2, $books), isset($books[5])); // bool(true) bool(false)
// PHP 8.4: callback-based search
$firstModern = array_find($books, fn (array $b): bool => $b['year'] > 1970);
var_dump($firstModern['title']); // string(7) "Kindred"
var_dump(array_find_key($books, fn (array $b): bool => $b['title'] === 'Dawn')); // int(2)
var_dump(array_any($books, fn (array $b): bool => $b['year'] < 1965)); // bool(true)
var_dump(array_all($books, fn (array $b): bool => str_starts_with($b['isbn'], '978'))); // bool(true)
// PHP 8.5: first and last values (null for an empty array)
var_dump(array_first($titles), array_last($titles), array_first([])); // "Solaris" "Dawn" NULL
// PHP 7.3: first and last keys
var_dump(array_key_first($books), array_key_last($books)); // int(0) int(2)
array_find() returns null when nothing matches, which is indistinguishable from a matching null value; use
array_find_key() when values can be null. Before 8.4 these were written as foreach loops with break, and
before 8.5 the first value was obtained with reset() (which moves the internal pointer) or $a[array_key_first($a)].
Source: array_find, array_any, array_all, array_first and in_array
Transforming
<?php
declare(strict_types=1);
$books = [
['title' => 'Solaris', 'year' => 1961, 'copies' => 2],
['title' => 'Kindred', 'year' => 1979, 'copies' => 0],
['title' => 'Dawn', 'year' => 1987, 'copies' => 5],
];
// map: one output per input (keys preserved for a single array)
$labels = array_map(fn (array $b): string => "{$b['title']} ({$b['year']})", $books);
// filter: keep elements for which the callback is truthy (keys preserved!)
$available = array_filter($books, fn (array $b): bool => $b['copies'] > 0);
var_dump(array_keys($available)); // [0, 2]
// filter by key, or by key and value
$byKey = array_filter(['a' => 1, 'b' => 2], fn (string $k): bool => $k !== 'a', ARRAY_FILTER_USE_KEY);
$both = array_filter(['a' => 1, 'b' => 2], fn (int $v, string $k): bool => $v > 1 && $k === 'b', ARRAY_FILTER_USE_BOTH);
// filter without callback: removes falsy values (0, "", "0", null, false, [])
print_r(array_filter([1, 0, '', 'x', null, '0'])); // [0 => 1, 3 => "x"]
// reduce: fold to a single value
$totalCopies = array_reduce($books, fn (int $carry, array $b): int => $carry + $b['copies'], 0);
// column: extract one field, optionally keyed by another
$yearByTitle = array_column($books, 'year', 'title'); // ["Solaris" => 1961, ...]
// map over several arrays in parallel; null callback zips them
print_r(array_map(null, ['a', 'b'], [1, 2])); // [["a", 1], ["b", 2]]
echo implode('; ', $labels), " | copies: {$totalCopies} | ", json_encode($yearByTitle), "\n";
array_walk() calls a callback with each value by reference and its key, to modify an array in place;
array_walk_recursive() descends into nested arrays. array_count_values() counts occurrences, array_sum() and
array_product() aggregate numbers, and array_chunk(), array_pad() and array_reverse() reshape.
<?php
declare(strict_types=1);
$prices = ['Solaris' => 12.5, 'Kindred' => 9.99];
array_walk($prices, function (float &$price, string $title): void {
$price = round($price * 1.21, 2); // add VAT in place
});
print_r($prices);
print_r(array_count_values(['sf', 'poetry', 'sf', 'sf'])); // ["sf" => 3, "poetry" => 1]
print_r(array_chunk(['a', 'b', 'c', 'd', 'e'], 2)); // [["a","b"],["c","d"],["e"]]
var_dump(array_sum([1, 2, 3.5])); // float(6.5)
Source: array_map, array_filter, array_reduce, array_column and array_walk
Set operations: diff and intersect
The diff and intersect families compare arrays by value, by key, or both, with optional user callbacks (u…) for the
comparison:
| Function | Keeps the entries of the first array… |
|---|---|
|
whose values are in none of the others |
|
whose keys are in none of the others |
|
whose key/value pairs are in none of the others |
|
that are present in all of the others |
|
comparing values with a callback |
|
comparing keys with a callback |
|
combinations |
Values are compared as strings ((string) $a === (string) $b) unless a callback is given. Keys of the first array
are preserved.
<?php
declare(strict_types=1);
$catalogue = ['Solaris', 'Kindred', 'Dawn', 'Neuromancer'];
$onLoan = ['Kindred', 'Dawn'];
print_r(array_values(array_diff($catalogue, $onLoan))); // ["Solaris", "Neuromancer"]
print_r(array_intersect($catalogue, $onLoan)); // [1 => "Kindred", 2 => "Dawn"]
$book = ['title' => 'Kindred', 'year' => 1979, 'notes' => 'x', 'internal_id' => 7];
print_r(array_intersect_key($book, array_flip(['title', 'year']))); // whitelist keys
print_r(array_diff_key($book, ['internal_id' => true])); // blacklist keys
// Case-insensitive comparison with a callback
print_r(array_udiff(['Solaris', 'KINDRED'], ['kindred'], strcasecmp(...))); // ["Solaris"]
array_intersect_key($input, array_flip($allowed)) is the idiom for keeping only expected form fields (a
whitelist), used in Forms, Validation and Output
Escaping.
Source: array_diff, array_intersect_key and array_udiff
Merging: array_merge, + and array_replace
| Operation | Integer keys | String keys |
|---|---|---|
|
appended and renumbered |
later arrays overwrite |
|
kept; |
|
|
|
|
|
appended and renumbered |
later overwrite (8.1) |
|
appended |
same keys are merged into arrays (rarely what you want) |
|
replaced recursively |
replaced recursively (good for nested config) |
<?php
declare(strict_types=1);
$a = [0 => 'Solaris', 1 => 'Kindred', 'format' => 'paperback'];
$b = [0 => 'Dawn', 'format' => 'ebook'];
print_r(array_merge($a, $b)); // [0 => Solaris, 1 => Kindred, format => ebook, 2 => Dawn]
print_r($a + $b); // [0 => Solaris, 1 => Kindred, format => paperback]
print_r(array_replace($a, $b)); // [0 => Dawn, 1 => Kindred, format => ebook]
$config = ['db' => ['host' => 'localhost', 'port' => 5432]];
$local = ['db' => ['host' => 'db.internal']];
print_r(array_replace_recursive($config, $local)); // host replaced, port kept
Source: array_merge, array_replace and Array Operators
Slicing, splicing and uniqueness
<?php
declare(strict_types=1);
$titles = ['Solaris', 'Kindred', 'Dawn', 'Neuromancer', 'Ariel'];
print_r(array_slice($titles, 1, 2)); // ["Kindred", "Dawn"] -- copy, renumbered
print_r(array_slice($titles, -2, null, true)); // [3 => "Neuromancer", 4 => "Ariel"] -- keys preserved
$removed = array_splice($titles, 1, 2, ['Lilith']); // remove 2 at offset 1, insert 1 -- in place
print_r($removed); // ["Kindred", "Dawn"]
print_r($titles); // ["Solaris", "Lilith", "Neuromancer", "Ariel"]
print_r(array_unique(['sf', 'SF', 'sf', 'poetry'])); // [0 => "sf", 1 => "SF", 3 => "poetry"]
array_unique() compares values as strings by default (SORT_STRING) and keeps the first key of each value.
Source: array_slice, array_splice and array_unique
Sorting
All sort functions sort in place and return true. They differ in what they sort by, the direction, and whether the
key/value association is kept:
| Function | Sorts by | Keeps keys? | Order |
|---|---|---|---|
|
value |
no (renumbered) |
ascending / descending |
|
value |
yes |
ascending / descending |
|
key |
yes |
ascending / descending |
|
value, with a callback |
no |
callback |
|
value, with a callback |
yes |
callback |
|
key, with a callback |
yes |
callback |
|
value, natural order |
yes |
ascending |
|
several arrays or columns at once |
string keys kept |
per column |
The non-callback functions accept flags: SORT_REGULAR (default, standard comparison), SORT_NUMERIC,
SORT_STRING, SORT_LOCALE_STRING, SORT_NATURAL, and SORT_FLAG_CASE combined with SORT_STRING or
SORT_NATURAL. Since PHP 8.0 every sort is stable: elements that compare equal keep their original order, so
sorting by one key and then by another works as expected.
<?php
declare(strict_types=1);
$books = [
['title' => 'Kindred', 'year' => 1979],
['title' => 'Solaris', 'year' => 1961],
['title' => 'Dawn', 'year' => 1987],
['title' => 'Wild Seed', 'year' => 1980],
];
// usort with the spaceship operator: by year descending
usort($books, fn (array $a, array $b): int => $b['year'] <=> $a['year']);
echo implode(', ', array_column($books, 'title')), "\n"; // Dawn, Wild Seed, Kindred, Solaris
// Multiple criteria: compare arrays of keys
usort($books, fn (array $a, array $b): int => [strlen($a['title']), $a['title']] <=> [strlen($b['title']), $b['title']]);
echo implode(', ', array_column($books, 'title')), "\n"; // Dawn, Kindred, Solaris, Wild Seed
$years = ['Kindred' => 1979, 'Solaris' => 1961];
asort($years); // keeps title => year association
ksort($years); // by title
print_r($years);
$files = ['img12.png', 'img10.png', 'IMG9.png'];
natcasesort($files);
print_r($files); // IMG9, img10, img12 (keys kept)
sort($files, SORT_NATURAL | SORT_FLAG_CASE);
print_r($files); // same order, renumbered
Source: Sorting Arrays, usort and sort (flags)
array_multisort
array_multisort() sorts several arrays together, or a table by several columns, like SQL’s ORDER BY a DESC, b ASC:
<?php
declare(strict_types=1);
$books = [
['title' => 'Kindred', 'year' => 1979],
['title' => 'Dawn', 'year' => 1987],
['title' => 'Imago', 'year' => 1989],
['title' => 'Adulthood Rites', 'year' => 1987],
];
$years = array_column($books, 'year');
$titles = array_column($books, 'title');
array_multisort($years, SORT_DESC, $titles, SORT_ASC, SORT_STRING, $books);
foreach ($books as $b) {
echo "{$b['year']} {$b['title']}\n";
}
// 1989 Imago
// 1987 Adulthood Rites
// 1987 Dawn
// 1979 Kindred
Source: array_multisort
Iterating and modifying
foreach iterates over a snapshot of the array (thanks to copy-on-write): appending to the array inside the loop does
not extend the loop. To modify elements in place, write back by key, or iterate by reference and unset() the
reference afterwards (the pitfall is shown in Control
Structures).
<?php
declare(strict_types=1);
$stock = ['Solaris' => 2, 'Kindred' => 0];
foreach ($stock as $title => $copies) { // by value + write back by key
if ($copies === 0) {
$stock[$title] = 1; // reorder: one copy requested
}
}
foreach ($stock as &$copies) { // by reference
$copies *= 2;
}
unset($copies); // always break the reference
print_r($stock); // ["Solaris" => 4, "Kindred" => 2]
The old internal-pointer functions (current(), next(), reset(), end(), key(), prev()) still exist, and
reset()/end() were the classic way to get the first/last element; array_first()/array_last() (8.5) and
array_key_first()/array_key_last() (7.3) do this without side effects.
Source: foreach and array_key_first
Arrays versus objects
Arrays are flexible but untyped: nothing stops ['titel' => …]. For data with a fixed shape, readonly classes with
promoted properties give type checks, IDE completion and immutability at a similar cost; use arrays for
collections and for data on its way in and out (JSON, database rows). PHPDoc shapes (array{title: string, year:
int}, list<Book>) let static analysers check arrays that remain. ArrayObject and ArrayIterator wrap arrays in
objects when an API needs one; see SPL Data Structures.
Common pitfalls
-
array_filter()keeps keys: wrap inarray_values()beforejson_encode()or index access. -
in_array()/array_search()withouttrue: loose comparison (in_array('abc', [0])was true before 8.0;'1e1'still equals'10'). -
Numeric-string keys become ints: a map keyed by
'1979'has int keys. -
+versusarray_merge(): union by key versus append. -
array_search()returning0: compare with!== false. -
Sorting functions return
true, not the sorted array:$sorted = sort($a)is a bug. -
By-reference
foreachwithoutunset().
Where older books and tutorials are outdated
-
each()(Beak ch. 4, Nixon ch. 6) was removed in PHP 8.0: replacewhile (list($k, $v) = each($a))withforeach. -
list()still works and is not deprecated (not even in the upcoming 8.6), but[](7.1) is the modern syntax. -
Unstable sorting: before 8.0 equal elements could be reordered; books warn about it, but it no longer applies.
-
First/last element through
reset()/end()— preferarray_first()/array_last()(8.5). -
Hand-written loops for "find the first match", "any" and "all" are replaced by
array_find(),array_any()andarray_all()(8.4). -
Spread with string keys was a
TypeErrorbefore 8.1. -
array_key_exists()on objects was removed in 8.0; useproperty_exists()orisset().
References
-
PHP Manual — Arrays (syntax, key casting, destructuring, unpacking)
-
array_is_list, array_find, array_find_key, array_any, array_all, array_first, array_last, array_key_first
-
array_map, array_filter, array_reduce, array_walk, array_column, array_combine
-
array_diff, array_intersect_key, array_udiff, array_merge, array_replace, array_slice, array_splice, array_unique
-
PHP Manual — Reference Counting Basics (copy-on-write)