В отношении данных, которые программа получает извне, принято следовать правилу trustno1. Это справедливо не только в отношении данных, получаемых непосредственно от пользователя, но и в отношении данных, которые передаёт в подпрограммы клиентский код.
PHP 7 оснащён расширенной системой контроля типов аргументов, включающей не только классы, но и скаляры. Однако в том, что касается сложных структур данных, ничего не изменилось — для них существует единственный тип array, который в PHP может содержать всё, что угодно.
Я надеюсь, что новые версии PHP исправят ситуацию. А на данный момент я хочу поделиться с сообществом некоторыми своими наработками в этой области:
alexeymaximov/fulltype
alexeymaximov/containum
UPD 02.12.2018 — Публикация была обновлена для соответствия приведённой документации текущим версиям библиотек.
GitHub: alexeymaximov/fulltype
Эта библиотека предназначена непосредственно для работы с типами. Вы можете определить собственный тип данных с помощью функции ArsMnemonica\Fulltype\fulltype::define:
Вы можете как создать и инстанцировать собственный класс, реализующий интерфейс ArsMnemonica\Fulltype\TypeInterface, так и использовать встроенные.
Для обращения к типу предназначена функция ArsMnemonica\Fulltype\fulltype::type:
Она принимает в качестве аргумента имя типа (аргумент aName функции define), и возвращает соответствующий объект.
Чтобы проверить значение на соответствие типу, воспользуйтесь функцией ArsMnemonica\Fulltype\fulltype::is:
или методом validate самого объекта типа:
Чтобы проверить значение на соответствие типу, вы также можете воспользоваться функцией ArsMnemonica\Fulltype\fulltype::assert:
Эта функция возвращает переданное ей значение $aValue в случае успешной проверки, или выбрасывает исключение, унаследованное от TypeError, сообщение которого содержит описание переданного значения и место вызова функции assert.
Определены следующие встроенные типы (пространство имён ArsMnemonica\Fulltype и класс fulltype):
Логическое значение true/false.
Числовые типы.
Тип NumericType соответствует PHP-типам int и float.
Являющийся его наследником тип IntegerType соответствует только PHP-типу int.
Оба типа могут быть ограничены минимальным и максимальным значениями.
Тип CardinalType, являющийся наследником IntType, соответствует количественным числам (целым числам без знака) — его минимальным значением является 0, а максимальное может быть определено.
Строковый тип, может быть ограничен по максимальной длине.
Регулярное выражение.
Перечислимый тип.
Ограничивает множество допустимых значений заданным набором.
Объектный тип.
Значение может быть только объектом заданного класса (интерфейсы так же допустимы).
Nullable-тип.
Дополняет множество допустимых значений дочернего типа значением null.
Массивный тип (ключевое слово array не допустимо в качестве имени функции), может быть ограничен по максимальной длине.
Значение может быть:
— массивом;
— объектом, реализующим интерфейсы ArrayAccess, Countable и Traversable (должен быть вызван метод objective).
Чтобы задать допустимый тип значений массива, используйте метод of(TypeInterface), а для ключей используйте метод by(TypeInterface). Если вы зададите тип ключей, отличный от PHP-типов int и string, массивный тип будет иметь смысл только в отношении объектов, поскольку у массивов PHP не может быть ключей других типов.
Структурный тип, требующий соответствия переданного для валидации значения хотя бы одному из дочерних типов.
Структурный тип, требующий соответствия переданного для валидации значения строго одному из дочерних типов.
Структурный тип, требующий соответствия переданного для валидации значения всем дочерним типам.
Для данного типа имеет смысл задавать следующие дочерние типы:
Тип-ключ, требующее от переданного для валидации значения быть массивом, содержащим строковый ключ указанного типа.
Тип-смещение, требующее от переданного для валидации значения быть массивом или объектом, реализующим интерфейс ArrayAccess, содержащим строковое смещение указанного типа.
Тип-свойство, требующее от переданного для валидации значения быть объектом, содержащим свойство указанного типа.
Если для указанных выше типов вызван метод optional, то соответствующая проверка допускает отсутствие такого ключа/смещения/свойства.
Для наглядной демонстрации работы библиотеки рассмотрим следующий пример:
Этот код проверяет корректность переданного описания элемента книжной серии:
Такой набор параметров будет валидным:
А такой не пройдёт проверку:
Хотя всё это — в некоторой степени набор велосипедов, но я надеюсь, что он может кому-то пригодиться в работе. typedef может быть удобен для проверки параметров скрипта вместе с их преобразованием с помощью json_decode. А контейнеры могут пригодиться для ограничения типов массивов в аргументах с помощью уже готовых инструментов.
Можно было бы добавить типизированные свойства объектов, оформить библиотеки в виде расширений для улучшения производительности или сделать ещё что-нибудь необдуманное, но пока я не вижу в этом острой необходимости.
Так же я буду рад выслушать конструктивную критику и что-то улучшить в этих несложных инструментах или узнать про какой-нибудь silver-bullet, просвистевший мимо меня.
Благодарю за ваше внимание!
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, просвистевший мимо меня.
Благодарю за ваше внимание!