array is a shrug
array tells PHP you've got an array. It tells the next developer nothing. A list of SKUs, best seller first? Stock levels keyed by SKU? A database row? They all have the same type, so every caller reads the method body to find out.
World 2 is about types that say what you mean. It starts here, because from level 6 PHPStan stops accepting the shrug:
Method BestSellers::available() return type has no value type specified in iterable type array.
Two kinds of collection
Most arrays in application code are one of two things:
| You have | Type | For example |
|---|---|---|
| An ordered sequence | list<T> |
list<string>: SKUs, best seller first |
| A lookup by key | array<K, V> |
array<string, int>: quantity by SKU |
list<T> is the stronger promise: integer keys from 0, in order, with no gaps. That's what json_encode() turns into a JSON array, and what $skus[0] and [...$a, ...$b] quietly rely on. array<int, T> has integer keys too, but it doesn't promise they start at zero or have no gaps.
PHP has no syntax for any of this, so the native type stays array and the docblock says what's inside:
/** @var array<string, int> price in pence by SKU */ private array $prices = []; /** * @param list<string> $skus * @return list<int> */ public function pricesFor(array $skus): array
The type is a promise, so keep it
Writing list<string> is easy. Keeping it is where the bugs are, because some of the most common array functions keep the original keys:
array_filter(['MUG-01', 'MUG-02', 'TEA-01'], $inStock); // [1 => 'MUG-02', 2 => 'TEA-01']
PHPStan can catch that lie, but not yet. From level 7, returning array_filter()'s result from a method that promises list<string> is an error. Level 6 asks you to write the types. Level 7, the next level up, checks you meant them.
Power-up
<?php final class RecentlyViewed { private array $skus = []; public function forget(string $sku): array { // A list goes in. A list with a gap in it comes out. $this->skus = array_filter($this->skus, fn ($s) => $s !== $sku); return $this->skus; } }
<?php final class RecentlyViewed { /** @var list<string> SKUs, most recent first */ private array $skus = []; /** @return list<string> */ public function forget(string $sku): array { $this->skus = array_values(array_filter($this->skus, fn (string $s): bool => $s !== $sku)); return $this->skus; } }