Зачем нужна типизация массивов в PHP
В принципе, в любой версии PHP, можно сделать что-то вроде:
function getNames(array $input): array {
  return array_map(function($item) {
    return $item->getName();
  }, $input)
}Однако это мало говорит о типах, с которыми имеем дело.
Базовые типы массивов
Указывая тип array или iterable, или что-то подобное, вы лишь указываете, что это массив. Но на самом деле хотите указать, что это массив, содержащий определённый тип. Если переписать предыдущий пример и сделать его более конкретным, можно получить следующее:
/**
 * @param array<User> $input
 *
 * @return array<string>
 */
function getNames(array $input): array {
  return array_map(function($item) {
    return $item->getName();
  }, $input)
}Если добавить типы, то станет понятно, передаётся массив, содержащий объекты User, а возвращаем массив string. В этом есть множество преимуществ.
Преимущества типизированных массивов
Подсказки IDE
Первое преимущество типизированных массивов заключается в том, что теперь IDE будет понимать, что это за массив, и, следовательно, сможет предоставить автодополнение внутри функции. Это также поможет при попытке найти использование getName() в классе User, поскольку теперь IDE знает, что вызывается именно этот метод.
Это также поможет в работе с инструментами рефакторинга, например, если переименовать метод getName, то такая IDE, как PHPStorm, сможет обновить и вызов этого метода. Кроме того, если передадите массив неправильного типа или попытаетесь сделать что-то, что невозможно с возвращаемым массивом строк, вы получите ошибку в IDE.
Инструменты статического анализа
Второе преимущество типизированных массивов связано с использованием инструментов статического анализа (SA) (например, PHPStan). SA-инструменты понимают, что именно вы делаете, и выдают ошибки, если вы делаете что-то не то. Чем больше типов используется в кодовой базе, тем лучше они понимают код, и тем лучше могут помочь обнаружить проблемы до того, как они попадут в продакшен.
В PHPStan добавление объявлений типов становится обязательным на уровне 6. Возможно, в устаревших кодовых базах будет сложно сделать это повсеместно, но настоятельно рекомендую перейти этот уровень и сгенерировать базовую линию. Это потребует добавления типов в любой новый код.
Понимание разработчиков
Скорее всего, вы не единственный, кто работает над вашей кодовой базой. Если другой разработчик увидит метод getNames, для начала ему придётся выяснить, что за параметры у функции, прежде чем понять, что нужно делать с этим методом.
Не сосчитать, сколько раз при взгляде на метод в коде думал: Понятия не имею, что здесь происходит
, потому что в методе либо не было типов, либо в качестве типа был только array. Сначала приходилось рыться в кодовой базе, в поисках, где вызывается метод, прежде чем становилось понятно, какие параметры и какие значения возвращаются.
Продвинутые типы массивов
Благодаря современным средствам PHP можно продвинуться гораздо дальше, чем просто задать @param array<Type> $value. Можно создавать списки, указывать ключи массива или быть уверенным, что он не пуст.
Тип list
Например, можно указать, что что-то является списком — list.
Список — массив, в котором ключи начинаются с 0 и каждый раз увеличиваются на единицу. Тип list может быть удобен, например, при конвертации в JSON, так как в этом случае он становится массивом, а не объектом. Если создать массив без ключей, он всегда будет списком, но при необходимости можно указать ключи.
$list = [1, 2, 3];
$alsoList = [
  0 => 1,
  1 => 2,
  2 => 3,
];
// Ключи начинаются с 1, а не с 0, поэтому это не список.
$notList = [
  1 => 1,
  2 => 2,
  3 => 3,
];
// Имеет строковый ключ, поэтому это не список.
$alsoNotList = [
  'foo' => 'bar',
  1,
  2
];Тип list не существует, но его можно использовать как показано в примере ниже, а такой инструмент, как PHPStan, поможет определить, если передаётся не список.
/**
 * @param list<User> $input
 *
 * @return list<string>
 */
function getNames(array $input): array {
  return array_map(function($item) {
    return $item->getName();
  }, $input)
}Определение ключей
Можно даже указать, что ожидается определённый набор ключей, или просто указать, какого типа должны быть ключи. С помощью следующей нотации указываются литеральные ключи: array{key: Type, key2: Type}. Это также включает автодополнение IDE для этих ключей и все другие ранее упомянутые преимущества. Если необходимо указать только общие типы, например, если ключи являются строками, то можно сделать следующим образом: array<string, Type>. В общем случае массивы обозначаются как array<KeyType, ValueType>. Если передаётся только один тип, то это тип значения.
/**
 * @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;
}Непустые типы
Можно указать, что массив не должен быть пустым. Для этого вместо array (или list) можно использовать non-empty-array (или non-empty-list). Если взять в качестве примера функцию concatKeys, можно определить её так, чтобы убедиться, что получаемый массив никогда не будет пустой. Также указываем, что возвращаемая строка тоже никогда не будет пустой.
/**
 * @param non-empty-array<string, mixed> $data
 *
 * @return non-empty-string
 */
function concatKeys(array $data): string {
  $output = '';
  foreach($data as $key => $v) {
  $output .= $key;
  }
  return $output;
}И опять, это обеспечивает лучшее понимание инструментария SA, IDE и других разработчиков.
Заключение
Добавление типов в массивы (и просто добавление типов в целом) поможет при использовании IDE или статического анализа, а также поможет другим разработчикам в команде понять ваш код. Также это поможет избежать ошибок и сделает код чище и читабельнее.
Типизация массивов помогает задокументировать замысел функции и то, как её следует использовать. А ещё лучше, когда современные инструменты показывают, если вы делаете что-то не так.