Why you should be typing your arrays in PHP

Jul 17, 2024·
Gert de Pagter
Gert de Pagter
· 6 min read

We have been able to natively type parameters of methods and functions in PHP for quite some time. In basically any version of PHP you should be running we can do something like the following:

function getNames(array $input): array {
  return array_map(function($item) {
    return $item->getName();
  }, $input)
}

Basic array types

This however tells us very little about the types that we are dealing with. When specifying a type as just array or iterable, or something alike, all you are saying is that it is an array, but you really want to specify that it is an array containing a specific type. If we rewrite the previous example to be more specific, we can do the following:

/**
 * @param array<User> $input
 *
 * @return array<string> 
 */
function getNames(array $input): array {
  return array_map(function($item) {
    return $item->getName();
  }, $input)
}

If we add types like this we now understand we are getting an array which contains User objects, and we are returning an array of string. There are multiple benefits to doing so.

Benefits of typed arrays

IDE hints

The first benefit is the fact that your IDE will now understand what kind of array this is, and thus be able to give you autocompletion inside the function. It will also help you when trying to find the usages of getName() on the User class, as now your IDE, knows it is that method that is being called.

This will also help you with refactor tools, for example if you are renaming the getName method, an IDE like PHPStorm will be able to also update this method call. You’ll also get errors in your IDE if you pass in the wrong type of array, or if you try to do something that’s not possible with the returned array of strings.

Static analysis tooling

The second benefit comes from the usage of static analysis (SA) tooling (like PHPStan). SA tools will understand what you are doing, and give you error in your pipelines if you are doing something that shouldn’t be possible. The more types you use in your codebase, the better understanding it has of your code, the better it can help you detect issues before they hit production.

In PHPStan adding type declarations becomes a requirement on level 6. This may be hard to add everywhere in legacy code bases, but I would highly recommend upgrading to at least this level and generating a baseline. This will require you to at least add these types to any new code you are writing

Developer understanding

Most likely you aren’t the only one working on your code base. If the next developer comes along and sees the getNames method, they first have to figure out what the input is, before they can figure out what they are supposed to do with the method.

I can’t count the amount of times I looked at a method and thought “I have no idea what’s going on here”, because there were either no types, or just array as a type. I would first have to search through the code base to see where the method is called, before being able to understand what the parameters where and what the return values where.

Advanced array types

With modern PHP tooling, we can however, go much further than just doing @param array<Type> $value. We can do lists, specify array keys, or make sure it is not empty

The list type

We can for example specify something is a list.

A list is an array, where the keys start at 0, and each time they increment by one. The list type can be especially useful when converting to JSON for example, as that will make it an array, instead of an object. If you create an array without keys, it is always a list, but you can also specify the keys if needed

$list = [1, 2, 3];
$alsoList = [
  0 => 1,
  1 => 2, 
  2 => 3,
];
// Keys start at 1, not at 0, so not a list.
$notList = [
  1 => 1,
  2 => 2, 
  3 => 3,
];
// Has a string key, so not a list.
$alsoNotList = [
  'foo' => 'bar',
  1,
  2
];

The list type doesn’t exist natively, but you can use it like so, and a tool like PHPStan will help tell you if you aren’t passing a list

/**
 * @param list<User> $input
 *
 * @return list<string> 
 */
function getNames(array $input): array {
  return array_map(function($item) {
    return $item->getName();
  }, $input)
}

Specifying keys

Nowadays, we can even specify that we expect a specific set of keys, or just what type the keys should be. With the following notation we specify literal keys: array{key: Type, key2: Type}. This also comes with IDE autocompletion for those keys, and all other previously mentioned advantages. If we want to specify just the general types, for example if the keys are strings, we can do that as follows: array<string, Type>. Generally the notation for arrays is array<KeyType, ValueType>. If we only pass one type, it is the type of the value.

/**
 * @param array{begin: int, interval: int} $data
 * 
 * @return int 
 */
function transform(array $data): int {
  return $data['begin'] + $data['interval'];
}

/**
 * @param array<string, mixed> $data
 * 
 * @return string
 */
function concatKeys(array $data): string {
  $output = '';
  foreach($data as $key => $v) {
  $output .= $key;
  }
  return $output;
}

Non-empty types

We can even go as far as to specify the array should not be empty. you can do that by using non-empty-array (or non-empty-list), instead of just array (or list). If we take the concatKeys function as an example, we could define it like so, to make sure it is never empty. And we also specify the returned string will never be empty.

/**
 * @param non-emptyarray<string, mixed> $data
 * 
 * @return non-empty-string
 */
function concatKeys(array $data): string {
  $output = '';
  foreach($data as $key => $v) {
  $output .= $key;
  }
  return $output;
}

And once again, this provides better understanding for SA tooling, your IDE, and other developers.

Conclusion

Adding types to your arrays (and just adding types in general) helps you when using an IDE, or static analysis, and helps other developers in your team to understand your code. It should help you produce less bugs, and make your code cleaner and more readable.

These types help you document the intent of a function and how it should be used. And even better, with modern day tooling, it even shows you if you are doing something wrong.

Are you using (array) types in your PHP codebase, and do you think they are usefull? Are you already using more advanced types like the list and non-empty-array. Let me know in the comments!

If you want to get notified of the next blog post, join the newsletter.