В PHP для хранения и передачи разнородных данных (конфигурации компонентов, наборы параметров для функций, опции для виджетов и т.п.) обычно используют массивы — их универсальность и легкость использования весьма способствует этому, однако при этом возникают следующие проблемы:
- При разработке (даже в продвинутых IDE (системах разработки)), и при выполнении приложения отсутствует какой-либо контроль за структурой и типами данных в массиве.
- IDE ничем не может помочь при разработке, так что названия возможных ключей массивов придется вспоминать-печатать или где-то искать-копировать, что кроме неудобства и снижения производительности повышает возможность опечатки.
- Сложно контролировать где-как используются отдельные элементы этого массива и соответственно сложно рефакторить, даже несмотря на мощь современных IDE.
Для небольших проектов эти проблемы могут быть еще несущественны, там проще все проконтролировать, но с ростом объема кода они становятся все заметнее.
Один из известных подходов который позволяет решить проблемы 2 и 3 — создание объектов на основе этаких псевдоклассов расширяющих stdClass. Описанные в PHPDoc свойства класса и указание везде где используется объект его класса (в PHPDoc) позволяет работать автокомплиту и проверке возможных имен свойств, контролировать места применения свойств и при необходимости легко их рефакторить:
/**
* @property integer $integer
* @property integer[] $integers
*/
class DataStorage extend stdClass{
}
$ds=new DataStorage;
$ds->integer=13;
$ds->integers=[1,2,3];
Но проблема с контролем значений так не решается, данные пришедшие извне IDE точно не проконтролирует, не говоря уж о том, что предупреждение в IDE кто-то может и проигнорировать, в результате неверные данные могут привести к неожиданной ошибке в неожиданном месте. Кроме того, насколько мне известно, PHPDoc пока что не позволяет указывать список возможных значений свойств, что часто бывает важно.
Добавим немного PHP-магии: StrictDataStorage
Не найдя подходящего решения этих проблем разработал класс который позволяет выполнять проверку данных на основе PHPDoc, при этом как дополнительный бонус проверяет значения и по списку возможных. Работает класс аналогично решению приведенному выше с stdClass — создается класс отнаследованный от StrictDataStorage и у него в PHPDoc через @property
описываются типы свойств. При присвоение значений свойствам производится проверка значений на соответствие типа описанному и по результату проверки или происходит присвоение, или срабатывает обработчик ошибки — по умолчанию выбрасывается исключение. При проверке поддерживает все типы данных PHPDoc кроме callable, перечисление нескольких возможных типов через | и массивы описанные через [] в конце типа.
Кроме элемента @property
обрабатывается @enum
и @options
.
@enum
Позволяет указывать список возможных значений свойства. Исследование имеющейся в интернете информации привело к выводу, что никакого стандарта и даже просто соглашения по этому поводу на настоящий момент не существует, поэтому пока что придумал свой вариант не конфликтующий с @property
. Синтаксис @enum
похож на @property
— сначала идет описание возможных значений, потом название свойства. Возможные значения указываются двумя способами
- Списком в квадратных скобках — содержимое скобок разбирается через json_decode(), поэтому должно соответствовать по формату, в первую очередь это означает использование двойных кавычек для обрамления строк.
- Названием класса в котором хранятся значения, этот класс должен поддерживать интерфейс EnumArrayableInterface, который лежит в том же репозитории, можно в принципе сделать и поддержку SplEnum.
@options
Содержит настройки работы класса через запятую, в настоящий момент поддерживается два значения:
- PhpDocNotRequired — по умолчанию если свойство не описано элементом
@enum
или@property
, то ему невозможно присвоить значение, наличие PhpDocNotRequired в@options
включает менее строгий режим проверки — свойство м.б. присвоено даже если не описано. Этот режим позволяет описывать только те свойства для которых важна проверка значения. - StrictNumberTypeCheck — по умолчанию если свойство описано как integer или float, то строки содержащие числа также проходят проверку, как это применяется в PHP повсеместно, добавление StrictNumberTypeCheck в
@options
активирует более строгий режим проверки чисел — строки не пройдут!
Пример
Класс StrictDataStorage выложен здесь github.com/Yannn/php-strict-data, как он работает проиллюстрирую на примере приведенного там же наследника SampleDataStorage:
<?php
/**
* @property integer $integer
* @property Closure $callback
* @property mixed $mixed
*
* @property integer[] $integers
* @enum SampleEnum[] $integers
*
* @property float|string $enumFloat
* @enum ["1.8","7","9"] $enumFloat
*
* @property string[] $enumArray
* @enum ["one","two"][] $enumArray
*
* @options PhpDocNotRequired|StrictNumberTypeCheck
*/
class SampleDataStorage extends StrictDataStorage
{
}
Т.к. указано @options PhpDocNotRequired
у объекта класса SampleDataStorage можно присвоить значение любому свойству, но при присвоении свойств $integer, $callback, $mixed, $integers, $enumFloat, $enumArray значения будут проверятся по описанным в @property
и @enum
правилам:
- Значение $integer д.б. обязательно целым числом, если убрать из
@options
значение StrictNumberTypeCheck, то можно будет присвоить строку содержащую целое число. - Значение $callback д.б. обязательно замыканием, т.е. объектом типа Closure.
- Значение $mixed м.б. любым — тип не проверяется.
- Значение $integers д.б. массивом целых чисел, при том список возможных значений элементов массива будет получен методом getEnumValues() из SampleEnum.
- Значение $enumFloat д.б. дробным числом или строкой, при том возможные значения перечислены в списке
["1.8","7","9"]
. - Значение $enumArray д.б. массивом строк, при том возможные значения элементов массива перечислены в списке
["one","two"]
.
При несоответствии значения указанным правилам будет выкидываться исключение. Методы обрабатывающие ошибки реализованы как protected, так что поведение при ошибке легко изменить наследованием.
Буду рад замечаниям, предложениям и конечно же багрепортам.
Автор: annenkov