PHP 7 оснащён расширенной системой контроля типов аргументов, включающей не только классы, но и скаляры. Однако в том, что касается сложных структур данных, ничего не изменилось — для них существует единственный тип array, который в PHP может содержать всё, что угодно.
Я надеюсь, что новые версии PHP исправят ситуацию. А на данный момент я хочу поделиться с сообществом некоторыми своими наработками в этой области:

alexeymaximov/fulltype
alexeymaximov/containum
UPD 02.12.2018 — Публикация была обновлена для соответствия приведённой документации текущим версиям библиотек.
Fulltype
GitHub: alexeymaximov/fulltype
Эта библиотека предназначена непосредственно для работы с типами. Вы можете определить собственный тип данных с помощью функции ArsMnemonica\Fulltype\fulltype::define:
function define(string $aName, TypeInterface $aType): TypeInterface;
Вы можете как создать и инстанцировать собственный класс, реализующий интерфейс ArsMnemonica\Fulltype\TypeInterface, так и использовать встроенные.
Для обращения к типу предназначена функция ArsMnemonica\Fulltype\fulltype::type:
function type(string $aName): TypeInterface;
Она принимает в качестве аргумента имя типа (аргумент aName функции define), и возвращает соответствующий объект.
Чтобы проверить значение на соответствие типу, воспользуйтесь функцией ArsMnemonica\Fulltype\fulltype::is:
function is($aValue, TypeInterface $aType): bool;
или методом validate самого объекта типа:
function TypeInterface::validate($aValue): bool;
Чтобы проверить значение на соответствие типу, вы также можете воспользоваться функцией ArsMnemonica\Fulltype\fulltype::assert:
function assert($aValue, TypeInterface $aType): mixed;
Эта функция возвращает переданное ей значение $aValue в случае успешной проверки, или выбрасывает исключение, унаследованное от TypeError, сообщение которого содержит описание переданного значения и место вызова функции assert.
Определены следующие встроенные типы (пространство имён ArsMnemonica\Fulltype и класс fulltype):
function bool(): BooleanType;
Логическое значение true/false.
function number(float $aMin = null, float $aMax = null): NumericType; function int(int $aMin = null, int $aMax = null): IntegerType; function cardinal(int $aMax = null): CardinalType;
Числовые типы.
Тип NumericType соответствует PHP-типам int и float.
Являющийся его наследником тип IntegerType соответствует только PHP-типу int.
Оба типа могут быть ограничены минимальным и максимальным значениями.
Тип CardinalType, являющийся наследником IntType, соответствует количественным числам (целым числам без знака) — его минимальным значением является 0, а максимальное может быть определено.
function string(int $aLength = null): StringType;
Строковый тип, может быть ограничен по максимальной длине.
function regexp(string $aRegularExpression): RegularExpressionType;
Регулярное выражение.
function enum(...$aValues): EnumerableType;
Перечислимый тип.
Ограничивает множество допустимых значений заданным набором.
function object(string $aBase = null): ObjectType;
Объектный тип.
Значение может быть только объектом заданного класса (интерфейсы так же допустимы).
function null(TypeInterface $aType): NullableType;
Nullable-тип.
Дополняет множество допустимых значений дочернего типа значением null.
function array(int $aLength = null): ArrayType;
Массивный тип (ключевое слово array не допустимо в качестве имени функции), может быть ограничен по максимальной длине.
Значение может быть:
— массивом;
— объектом, реализующим интерфейсы ArrayAccess, Countable и Traversable (должен быть вызван метод objective).
Чтобы задать допустимый тип значений массива, используйте метод of(TypeInterface), а для ключей используйте метод by(TypeInterface). Если вы зададите тип ключей, отличный от PHP-типов int и string, массивный тип будет иметь смысл только в отношении объектов, поскольку у массивов PHP не может быть ключей других типов.
function any(TypeInterface ...$aTypes): MultipleType;
Структурный тип, требующий соответствия переданного для валидации значения хотя бы одному из дочерних типов.
function union(TypeInterface ...$aTypes): ExclusiveType;
Структурный тип, требующий соответствия переданного для валидации значения строго одному из дочерних типов.
function struct(TypeInterface ...$aTypes): ComplexType;
Структурный тип, требующий соответствия переданного для валидации значения всем дочерним типам.
Для данного типа имеет смысл задавать следующие дочерние типы:
function key(string $aName, TypeInterface $aType = null): PropertyType
Тип-ключ, требующее от переданного для валидации значения быть массивом, содержащим строковый ключ указанного типа.
function offset(string $aName, TypeInterface $aType = null): PropertyType
Тип-смещение, требующее от переданного для валидации значения быть массивом или объектом, реализующим интерфейс ArrayAccess, содержащим строковое смещение указанного типа.
function property(string $aName, TypeInterface $aType = null): PropertyType
Тип-свойство, требующее от переданного для валидации значения быть объектом, содержащим свойство указанного типа.
Если для указанных выше типов вызван метод optional, то соответствующая проверка допускает отсутствие такого ключа/смещения/свойства.
Для наглядной демонстрации работы библиотеки рассмотрим следующий пример:
use ArsMnemonica\Fulltype\fulltype as t; const input_t = 'input'; t::define(input_t, t::struct( t::key('name', t::string()), t::key('authors', t::any( t::string(), t::array()->of(t::string()) )), t::union( t::key('text', t::string()), t::key('content', t::struct( t::key('title', t::string(255)), t::key✱('annotation', t::string(65535)), t::key('text', t::string()), t::key✱('pages', t::cardinal?(5000)) )) ), t::key('status', t::enum('WILL_READ', 'READ', 'FAVORITE_BOOK')) )); echo "Processing input...\n"; if (PHP_SAPI === 'cli') { $input = []; parse_str(implode('&', array_slice($argv, 1)), $input); } else { $input = $_GET; } foreach ($input as $key => $value) { echo "$key: " . json_encode($value) . "\n"; } echo "Validation: " . (t::is($input, t::type(input_t)) ? 'success' : 'failed') . PHP_EOL;
Этот код проверяет корректность переданного описания элемента книжной серии:
- Обязательный параметр name должен быть строкой произвольной длины.
- Обязательный параметр authors должен быть строкой произвольной длины или массивом таких строк.
- Может быть передан параметр text, являющийся строкой произвольной длины, либо составной параметр content.
- Обязательный параметр status должен иметь одно из указанных значений.
Такой набор параметров будет валидным:
name="The Lord of the Rings" authors[]="J. R. R. Tolkien" content[title]="The Return of the King" content[text]=... status=READ
А такой не пройдёт проверку:
name="The Lord of the Rings" authors[]="J. R. R. Tolkien" text=... content[title]="The Return of the King" content[text]=... status=READ
Вместо заключения
Хотя всё это — в некоторой степени набор велосипедов, но я надеюсь, что он может кому-то пригодиться в работе. typedef может быть удобен для проверки параметров скрипта вместе с их преобразованием с помощью json_decode. А контейнеры могут пригодиться для ограничения типов массивов в аргументах с помощью уже готовых инструментов.
Можно было бы добавить типизированные свойства объектов, оформить библиотеки в виде расширений для улучшения производительности или сделать ещё что-нибудь необдуманное, но пока я не вижу в этом острой необходимости.

Так же я буду рад выслушать конструктивную критику и что-то улучшить в этих несложных инструментах или узнать про какой-нибудь silver-bullet, просвистевший мимо меня.
Благодарю за ваше внимание!

