What keys can be
Tags in PHP's cloakroom come in only two kinds: an integer or a string. The ticket 8 and the name tag 'anna' are ordinary keys. But what happens if you hang an item under the tag '8'—a string of a single digit?
Let's see which tags PHP actually hangs:
<?php
$seats = [];
$seats['8'] = 'Anna';
$seats[8] = 'Boris';
$seats['08'] = 'Vera';
echo count($seats), ' ', $seats['8'], "\n"; // 2 Boris
echo implode(' ', array_keys($seats)), "\n"; // 8 08Let's go through it line by line:
$seats['8'] = 'Anna';writes to the key8: PHP turns the string'8'into a number.$seats[8] = 'Boris';writes to the same key8and replaces Anna with Boris.$seats['08'] = 'Vera';creates a new key: the string'08'stays a string.countgives2, and$seats['8']reads the key8and printsBoris.- The
array_keysfunction returns a list of all the array's keys, andechoprints it throughimplode:8 08.
What a key turns into
A key can only be an integer or a string. PHP converts a value of another type to one of them, and some strings to a number.
A string holding an integer
If a string looks exactly the way PHP writes an integer, it becomes a number: '8' turns into 8, '-3' into -3. That is why $a['8'] and $a[8] are one and the same key.
Other strings
All other strings stay strings, even when they look like a number:
'08'—with a leading zero;'+8'—with a plus sign;'8.5'—with a fractional part;' 8'—with a leading space.
true, false, a fraction and null
PHP converts values of other types like this:
truebecomes the key1, andfalsethe key0;- a fractional number loses its fraction:
$a[8.7]writes to the key8and givesDeprecated: Implicit conversion from float 8.7 to int loses precision; nullbecomes the empty string'', and since PHP 8.5 such a write gives aDeprecatedtoo.
Deprecated is a notice that you should not write code this way and a future PHP version may forbid it: for now the script keeps running. Fractional and null keys are best not used at all.
The next index
As you already know, $a[] = … hangs an element on the hook after the largest number. Let's make the rule precise: PHP takes the largest integer key of all that have been in the array and adds one to it.
$queue = [10, 20, 30];
unset($queue[2]);
$queue[] = 40;
echo implode(' ', array_keys($queue)), "\n"; // 0 1 3
$codes = ['x' => 5];
$codes[] = 6;
echo implode(' ', array_keys($codes)), "\n"; // x 0Let's look at both cases:
- After
unset($queue[2])there is no key2, but PHP remembers that it existed. So40gets the key3, not2. $codeshas no integer key at all, and the first[]gets the key0.
Here the cloakroom from the picture and PHP part ways. A real attendant would hand the freed ticket 2 to the next guest, while PHP never gives numbers back and keeps counting.
If the largest integer key is negative, say -5, the next one gets -4. PHP counts this way starting with version 8.3.
Keys and values separately
array_keys
array_keys($queue) returns a new list of the array's keys: [0, 1, 3].
array_values
array_values($queue) returns a new list of the values, numbered afresh from 0: [10, 20, 40]. This is how the gaps in the numbers are closed after unset.
array_is_list
array_is_list($queue) checks whether the array is a list: the keys must run in a row, 0, 1, 2 and so on.
$values = array_values($queue);
echo implode(' ', array_keys($values)), "\n"; // 0 1 2
var_dump(array_is_list($queue)); // bool(false)
var_dump(array_is_list($values)); // bool(true)The keys in $queue are 0, 1 and 3, so it is no longer a list. After array_values the keys run in a row again.
One type under the hood
Interviewers like to ask how an indexed array differs from an associative one under the hood. The answer: as far as your code can tell, it doesn't—PHP has one array type. Its structure is called an ordered hash map: a table that finds a value by its key quickly and remembers the order of insertion.
Only its keys 0..n-1, that is, from 0 to the number of elements minus one, make an array a list, and array_is_list checks exactly them. Everything you have seen in this theme follows from this:
- keys are only
intorstring, and'8'and8are one key; - the walking order is the order of insertion;
unsetdoes not shift the numbers: after a removal from the start or the middle, a gap stays in the numbers.
Common mistake
An element was removed from a list, and then the list was walked with a for loop by index:
$tasks = ['wash', 'cook', 'read'];
unset($tasks[0]);
for ($i = 0; $i < count($tasks); $i++) {
echo $tasks[$i], "\n"; // ✗
}Warning: Undefined array key 0After unset the list has the keys 1 and 2, and count is 2. The loop runs $i from 0 to 1: on $tasks[0] PHP gives a warning and prints an empty line, and then cook. The task read under the key 2 is never printed. Walk such an array with foreach or renumber it: $tasks = array_values($tasks);.
Key points
- An array key is an
intor astring:'8'becomes8,truebecomes1, and'08'stays a string. $a[]takes the largest integer key of all that have been in the array, plus one;unsetdoes not free the number.array_keysandarray_valuesreturn the keys and the values as new lists, andarray_is_listchecks that the keys run in a row from0.- PHP has one array type, an ordered hash map; only the keys
0..n-1make an array a list.