Магический объект для хранения и передачи разнородных данных с проверкой типов и значений

в 6:06, , рубрики: data structures, php

В PHP для хранения и передачи разнородных данных (конфигурации компонентов, наборы параметров для функций, опции для виджетов и т.п.) обычно используют массивы — их универсальность и легкость использования весьма способствует этому, однако при этом возникают следующие проблемы:

  1. При разработке (даже в продвинутых IDE (системах разработки)), и при выполнении приложения отсутствует какой-либо контроль за структурой и типами данных в массиве.
  2. IDE ничем не может помочь при разработке, так что названия возможных ключей массивов придется вспоминать-печатать или где-то искать-копировать, что кроме неудобства и снижения производительности повышает возможность опечатки.
  3. Сложно контролировать где-как используются отдельные элементы этого массива и соответственно сложно рефакторить, даже несмотря на мощь современных 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 — сначала идет описание возможных значений, потом название свойства. Возможные значения указываются двумя способами

  1. Списком в квадратных скобках — содержимое скобок разбирается через json_decode(), поэтому должно соответствовать по формату, в первую очередь это означает использование двойных кавычек для обрамления строк.
  2. Названием класса в котором хранятся значения, этот класс должен поддерживать интерфейс 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

Источник

Поделиться