Skip to content
Theory · Arrays · lesson 4 of 8

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
<?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 08

Let's go through it line by line:

  1. $seats['8'] = 'Anna'; writes to the key 8: PHP turns the string '8' into a number.
  2. $seats[8] = 'Boris'; writes to the same key 8 and replaces Anna with Boris.
  3. $seats['08'] = 'Vera'; creates a new key: the string '08' stays a string.
  4. count gives 2, and $seats['8'] reads the key 8 and prints Boris.
  5. The array_keys function returns a list of all the array's keys, and echo prints it through implode: 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:

  • true becomes the key 1, and false the key 0;
  • a fractional number loses its fraction: $a[8.7] writes to the key 8 and gives Deprecated: Implicit conversion from float 8.7 to int loses precision;
  • null becomes the empty string '', and since PHP 8.5 such a write gives a Deprecated too.

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.

PHP
$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 0

Let's look at both cases:

  1. After unset($queue[2]) there is no key 2, but PHP remembers that it existed. So 40 gets the key 3, not 2.
  2. $codes has no integer key at all, and the first [] gets the key 0.

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.

PHP
$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 int or string, and '8' and 8 are one key;
  • the walking order is the order of insertion;
  • unset does 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:

PHP
$tasks = ['wash', 'cook', 'read'];
unset($tasks[0]);
for ($i = 0; $i < count($tasks); $i++) {
    echo $tasks[$i], "\n"; // ✗
}
TEXT
Warning: Undefined array key 0

After 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 int or a string: '8' becomes 8, true becomes 1, and '08' stays a string.
  • $a[] takes the largest integer key of all that have been in the array, plus one; unset does not free the number.
  • array_keys and array_values return the keys and the values as new lists, and array_is_list checks that the keys run in a row from 0.
  • PHP has one array type, an ordered hash map; only the keys 0..n-1 make an array a list.