Typed arrays in PHP

An alternative to the missing feature in PHP: Generics

Jose Maria Valera Reales
3 min readOct 13, 2020

The perfect combination

We will use this snipped for our examples

Having a class, `Customer`:

/** 
* @psalm-immutable
*/
final class Customer
{
// Using PHP 8 constructor property promotion
public function __construct(
public string $name,
) {}
}
// We create a list of 6 customers
$customers = array_map(
fn(int $i): Customer => new Customer("name-{$i}"),
range(1, 6)
);

Whenever we want to manipulate a list of Customers, we can pass as an argument: `…$customers`.

How we used to do it

We define the array type using the PHPDoc param comment block above. But we cannot define the real type of the item. The code will still run without any problem passing any type on that argument `array $customers`:

/** 
* @param Customer[]
*/
function createInvoiceForCustomers(array $customers): void
{
foreach ($customers as $customer) {
// ... some irrelevant logic for this example
}
}

The code below would work at “compile-time”. But it might fail at “runtime”.

createInvoiceForCustomers($customers);
createInvoiceForCustomers([new Customer('any name')]);
createInvoiceForCustomers([new AnyOtherType()]);

An alternative (recommended!) might be to extract that logic and ask for the particular type in order to “check it” at runtime in that particular moment, failing if one of the items wasn’t really a Customer:

/** 
* @param Customer[]
*/
function createInvoiceForCustomers(array $customers): void
{
foreach ($customers as $customer) {
createInvoice($customer);
}
}
function createInvoice(Customer $customer): void
{
// ... some irrelevant logic for this example
}

Everything here below would work at “compile-time”. It will for sure break during “runtime” if the `createInvoice(Customer $customer)` receives something different than a Customer.

createInvoiceForCustomers($customers);
createInvoiceForCustomers([new Customer('any name')]);
createInvoiceForCustomers([new AnyOtherType()]); // won't work

By doing that `createInvoice(Customer $customer)` we are ensuring the type of the argument, which is good! But, what about going one step further. Could we check the types of the elements when calling the function `createInvoiceForCustomers(array $customers)`, even making the IDE complain when the types are not right?

Well, that’s actually what Generics are for, but sadly, they are not yet in PHP. Not even in the upcoming PHP 8. Hopefully in a near future, but we cannot predict that for now.

Luckily, we have currently an alternative nowadays, but it’s not that popular. It has its own “pros” and “cons”, so let’s take a look at an example first:

function createInvoiceForCustomers(Customer ...$customers): void
{
foreach ($customers as $customer) {
createInvoice($customer);
}
}

Everything here below would work at “compile-time”. It will for sure break during “runtime” if the `createInvoice()` receives something different than a Customer.

createInvoiceForCustomers(...$customers); // OK
createInvoiceForCustomers(
new Customer('any name'),
new Customer('any name'),
); // OK
// This is not even possible to write. The IDE will yeld at you.
// It's expecting a `Customer`, but `AnyOtherType` is given:
createInvoiceForCustomers(new AnyOtherType());

PROS

  • We can easily type a list of any concrete type.

CONS

  • We better define our functions with one or two arguments max. Otherwise, it would be too complicated to read.
  • You can not have more than one ...variadic argument per function.

Important remarks

  • It needs to be the last taken argument of a function.
  • It helps to minimize the number of arguments that we use in a function.

Conclusions

Argument unpacking is a great feature that, in combination with variadic functions, can help us to simulate typed arrays. With great power comes great responsibility, and this is no exception.

We need to learn about our toolbox in order to use it wisely.

--

--

Jose Maria Valera Reales

Aka: Chema. I love writing about stuff that I find interesting and bring some value to my life, so I can share them with you. https://chemaclass.com