PHP: Never type hint on arrays

in #php6 years ago (edited)

Let's be controversial: In modern PHP, you should never type-hint an array.

Before you start throwing tomatoes, hear me out.

PHP allows you to specify the type of a function/method parameter or return value. These return values can be any legal PHP type, which includes any class or interface type, various scalars, and some fancy pseudo-types like callable and iterable.

PHP has a data type that it calls array, although it's not really an array as any other language would define it. It combines what most languages would call a vector (a sequence of variable length) with a dictionary (a list of key/value pairs). As a result, it often gets used as a cheap anonymous struct for complex data that the developer feels isn't worth defining a class for.

And you should almost never use array as a type hint. Why? Because there's always a better, more generic option.

Let's consider three cases:

A single complex value

Sometimes PHP arrays are used as an anonymous struct. That is, I would argue, always wrong. The better approach is to go ahead and define a class. Really, in PHP 7 there is no good reason to not define a class.

  • A class is twice as memory efficient as an array.
  • A class lets you define up-front what the expected properties of the complex value are, which makes it more self-documenting.
  • An IDE can provide auto-completion for the properties you've defined.
  • If you want to restrict access to certain properties you can make them protected or private and only expose methods to access them.
  • If you want to add additional behavior for that complex data object you can simply add more methods to the object.

The only advantage an associative array has over an object is that you don't need to define its structure, which is a problem because then you don't get any of the self-documentation benefits we mentioned. Even a fully public-property class is superior to an associative array in every way.

If you're defining a class, you'll want to type hint on the class for both parameters and return types. Not on an array.

A complex set of complex values

The other use for arrays is an (ordered) sequence of values. Arrays can work for that, but often times you want to have a more structured collection. It's fairly easy in PHP to write an object that is a collection of other objects; the collection only needs to implement Iterator or IteratorAggregate and it can be foreach()ed just like an array.

A formal collection object can also enforce, through type hinting, that it contains only a single type of data (some other object, or a string, or whatever), as well as offer other methods that make sense for that data type. It can also offer cleaner map(), each(), first(), last(), and various other collection-oriented methods.

Of course, if you're using a formal collection object you'll want to type hint against that. Not on an array.

A simple set of values

In practice, of course, most cases don't need a formal collection object. They have their uses but they're not always necessary. However, if you're passing a simple ordered set of values to a routine, or returning a simple ordered set of values from a routine... you still shouldn't type hint on an array.

That's not to say you shouldn't use an array. Using an array is fine. However, when you're dealing with a set you shouldn't limit other users of your code to interacting via arrays. PHP actually has several foreach()-able types of data:

  • Arrays
  • Objects that implement Traversable (via either Iterator or IteratorAggregate, or their subtypes)
  • Generators

From the point of view of code that's reading that data and iterating over it, it doesn't really matter which it is. They're all equivalent, but an array type hint will only permit the first. A Traversable type hint will only permit the second and third. If you want to allow all three as options (and you do), you need to type hint as iterable. An iterable will allow any of the foreach()-able values.

That allows your caller (for a parameter) or you or someone implementing an interface (for a return type) a lot of flexibility. Sometimes code is just cleaner and easier to read if implemented with a generator. Sometimes you really do need the lazy-generation capability of an iterator. If you type hint on array you force other users to use array-centric code structures always, rather than whatever structure makes for the cleanest code for them. That could very well be an array, and that's totally fine, but you shouldn't force that decision on them.

So, if you want an informal collection of values type hint with iterable. Not on an array.

Array operations

One unfortunate caveat is that PHP has a huge number of useful functions that operate on arrays... and not on other iterables. Operations like array_map(), array_filter(), or array_reduce() conceptually make sense on any iterable but because they predate non-array iterables in the language only work on array structures. If we're not doing just a simple foreach(), don't we need to specify an array explicitly?

Not really. Iterable-friendly versions of those operations are simple to write. There's several existing PHP libraries that offer those and many others on collection objects or arbitrary iterables that you can download, but they're trivial to write yourself. Here's some simple versions:

function iterable_map(iterable $list, callable $operation) : iterable
{
  foreach ($list as $k => $v) {
    yield $operation($k, $v);
  }
}

function iterable_filter(iterable $list, callable $filter) : iterable 
{
  foreach ($list as $k => $v) {
    if ($filter($v)) {
      yield $k => $v;
    }
  }
}

function iterable_reduce(iterable $list, callable $reducer, $acc)
{
  foreach ($list as $v) {
   $acc = $reducer($acc, $v);
  }
  return $acc;
}

Tweak as needed.

So arrays?

Arrays are fine. Arrays are useful. Arrays are no longer something you ever need to type hint. If you feel the need to type hint an array, consider it a code smell and ask yourself which use case you're dealing with. Do you want a collection or a struct? Type hint to what you actually mean. Your users will thank you.

Sort:  

very well article. And I disagree about arrays are fine. arrays are useful.
Nope, they are not. :)

They have their uses, totally. If you have a low-cardinality list of things an array is absolutely a reasonable tool to use for it.

It's type hinting against that array that is rarely useful.

I just wanted to mention that iterable is only available in PHP 7.1.0 and above. While everyone should be running a new enough version, not everyone is 😞.

Coin Marketplace

STEEM 0.27
TRX 0.11
JST 0.032
BTC 64579.45
ETH 3101.05
USDT 1.00
SBD 3.83