В этой статье я расскажу о некоторых новшествах, появившихся в проекте AMatch с момента написания первой статьи.
Напомню, что AMatch — класс, с помощью которого валидация входных параметров из большого набора if-ов превращается в удобную, лаконичную запись. К примеру:
Example: simple
В дальнейших примерах будем проверять массив в «хорошем» и «плохом» варианте:
Example: input data
Для проверки результатов тестов напишем функцию:
Example: result function
В первой версии ошибки отдавались строковыми константами. Этого вполне достаточно, чтобы сделать собственный маппинг, но это было некрасиво. На данный момент ошибки вынесены в отдельный класс AMatchStatus. Это позволило сделать следующие приятные вещи:
Составим простое условие валидации и отправим туда последовательно хороший и плохой массивы.
Example: bad and good policeman
Как видно из примера:
— matchResults() вернёт коды ошибок,
— matchComments() — комментарии,
— matchCommentsConditions() — условие валидации и дополнительная информация.
Попробуем написать функцию, которая сделает вывод ошибок «наружу» в соответствие с неким принятым стандартом внутри имеющегося кода.
Example: mapping
В результате выполнения данного кода будет получен массив со структурой ошибок, привычной для написанного ранее кода.
Для того, чтобы на любое условие получить собственную ошибку в комментариях (в случае непрохождения данного условия), достаточно передать текст третим параметром. Добавим в описанном выше примере («mapping») свой текст к ошибке:
Example: unique errors
Но заменять каждое условие — не всегда нужно. Иногда нужно заменить все комментарии.
Снова вернёмся к примеру «mapping». Для полноценной замены всех необходимых ошибок, напишем класс-наследник от AMatchStatus. Внутри необходимо перегрузить метод _fillComments(), не забывая вызвать родительский. Нужно создать объект данного класса и передать его в AMatch::runMatch(); третим параметром.
Example: russian
Ответ будет содержать переведённые конструкции наравне с не имеющими перевода.
Помимо отслеживания ошибок, несколько усовершенствована работа с пользовательскими функциями. Напомню, что ранее их можно было вызвать следующим образом:
На текущий момент поддержка callback расширена, и теперь можно вызывать по следующей схеме:
Example: callback
Вместе с библиотекой AMatch лежат примеры готовых классов с методами, которые можно использовать в качестве пользовательского вызова. Это два класса:
Рассмотрим примеры использования:
Example: plugins
Общая схема вызова AMatch на данный момент следующая:
Вы можете найти больше примеров (смотрите unittests) и скачать исходники AMatch на гитхабе, где вас ждёт готовый файл с примерами для этой статьи (examples2.php).
UPD:
Конвертировано для Composer.
Напомню, что AMatch — класс, с помощью которого валидация входных параметров из большого набора if-ов превращается в удобную, лаконичную запись. К примеру:
Example: simple
$match = AMatch::runMatch($params) ->doc_id(0, '<') // Левое значение меньше ->subject_id(0, '!=') // Не равен нулю ; $result = $match->stopMatch(); if (!$result) { die(var_export($match->matchComments(), true)); // для наглядности умрём }
Входные данные
В дальнейших примерах будем проверять массив в «хорошем» и «плохом» варианте:
Example: input data
$params = array( 'subject_id' => '64', 'parent_id' => -32, 'delimeter' => '-4.645E+32', 'title' => 'New document', 'links' => array(13, '-16', 24), 'email' => 'someuser@mail.dom', ); $params_bad = array( 'subject_id' => '64.43', 'parent_id' => array(), 'delimeter' => '-4.x6E.32', 'title' => new stdClass(), 'links' => array(0, array(0, array(0)), 0), 'email' => 'someuser!@mail.dom', );
Для проверки результатов тестов напишем функцию:
Example: result function
function result(AMatch $match) { echo PHP_EOL; echo $match->stopMatch() ? 'Dance!' : 'Cry!' ; echo PHP_EOL; var_export($match->matchResults()); echo PHP_EOL; var_export($match->matchComments()); echo PHP_EOL; var_export($match->matchCommentsConditions()); }
Работа над ошибками
В первой версии ошибки отдавались строковыми константами. Этого вполне достаточно, чтобы сделать собственный маппинг, но это было некрасиво. На данный момент ошибки вынесены в отдельный класс AMatchStatus. Это позволило сделать следующие приятные вещи:
Получение кодов ошибок
Составим простое условие валидации и отправим туда последовательно хороший и плохой массивы.
Example: bad and good policeman
$match = AMatch::runMatch($params, AMatch::FLAG_SHOW_GOOD_COMMENTS)->delimeter('', 'float'); // Существует с указанным типом result($match); $match = AMatch::runMatch($params_bad, AMatch::FLAG_SHOW_GOOD_COMMENTS)->delimeter('', 'float'); result($match);
В результате получим следующий ответ
// Хороший массив Dance! array ( 'delimeter' => 103, ) array ( 'delimeter' => 'OK. Expected parameter type is valid', ) array ( 'delimeter' => array ( 0 => '', 1 => 'float', ), ) // Плохой массив Cry! array ( 'delimeter' => 3, ) array ( 'delimeter' => 'Expected parameter type is not valid', ) array ( 'delimeter' => array ( 0 => 'float', 1 => 'float', ), )
Как видно из примера:
— matchResults() вернёт коды ошибок,
— matchComments() — комментарии,
— matchCommentsConditions() — условие валидации и дополнительная информация.
Дополнительная информация про float в AMatch
Обратите внимание, что float проверяется не через is_float
// Валидные значения float:
1, -1, 1.0, -1.0, '1', '-1', '1.0', '-1.0', '2.1', '0', 0, ' 0 ', ' 0.1 ', ' -0.0 ', -0.0, 3., '-3.', '.27', .27, '-0', '+4', '1e2', '+1353.0316547', '13213.032468e-13465', '-8E+3', '-1354.98879e+37436'
// Невалидные значения:
false, true, '', '-', '.a', '-1.a', '.a', '.', '-.', '1+', '1.3+', 'a1', 'e.e', '-e-4', 'e2', '8e', '3,25', '1.1.1'
// Валидные значения float:
1, -1, 1.0, -1.0, '1', '-1', '1.0', '-1.0', '2.1', '0', 0, ' 0 ', ' 0.1 ', ' -0.0 ', -0.0, 3., '-3.', '.27', .27, '-0', '+4', '1e2', '+1353.0316547', '13213.032468e-13465', '-8E+3', '-1354.98879e+37436'
// Невалидные значения:
false, true, '', '-', '.a', '-1.a', '.a', '.', '-.', '1+', '1.3+', 'a1', 'e.e', '-e-4', 'e2', '8e', '3,25', '1.1.1'
Маппинг ошибок
Попробуем написать функцию, которая сделает вывод ошибок «наружу» в соответствие с неким принятым стандартом внутри имеющегося кода.
Example: mapping
function mapping(AMatch $match) { // Карта ошибок $errors_mapping = array( AMatchStatus::KEY_TYPE_NOT_VALID => 'invalid_type', AMatchStatus::KEY_CONDITION_NOT_VALID => 'invalid_data', AMatchStatus::KEY_NOT_EXISTS => 'required', ); $results = $match->matchResults(); // Результат в кодах $comments = $match->matchComments(); // Комментарий к результату $comments_conditions = $match->matchCommentsConditions(); // Расшифровка результата $output = array(); foreach ($results as $param => $result) { $error = array_key_exists($result, $errors_mapping) ? $errors_mapping[$result] : 'other_errors'; // Ошибка, не имеющая аналогов в карте $comment = $param . ': ' . $comments[$param]; if (isset($comments_conditions[$param]) && !empty($comments_conditions[$param][0])) { $comment .= ' (' . $comments_conditions[$param][0] . ')'; // Дополнительная информация } $output[$error][] = $comment; } var_export($output); } $match = AMatch::runMatch($params_bad, AMatch::FLAG_SHOW_GOOD_COMMENTS | AMatch::FLAG_DONT_STOP_MATCHING) ->title('', 'string') // Существует с типом string ->parent_id('', 'int') // Существует с типом string ->ineedkey() // Ключ должен существовать ->subject_id(1, '>') // "1" больше имеющегося значения ->delimeter('', 'blabla') // Ошибка в условии ; mapping($match);
В результате выполнения данного кода будет получен массив со структурой ошибок, привычной для написанного ранее кода.
Результат примера «mapping»
array ( 'invalid_type' => array ( 0 => 'title: Expected parameter type is not valid (string)', 1 => 'parent_id: Expected parameter type is not valid (int)', ), 'required' => array ( 0 => 'ineedkey: Expected parameter does not exist in the array of parameters', ), 'invalid_data' => array ( 0 => 'subject_id: Condition is not valid (1)', ), 'other_errors' => array ( 0 => 'delimeter: Condition is unknown', ), )
Собственная расшифровка ошибок для любого условия
Для того, чтобы на любое условие получить собственную ошибку в комментариях (в случае непрохождения данного условия), достаточно передать текст третим параметром. Добавим в описанном выше примере («mapping») свой текст к ошибке:
Example: unique errors
... ->title('', 'string', 'Incorrect document title. Please, read FAQ.') ...
И выполним его заново
array ( 'other_errors' => array ( 0 => 'title: Incorrect document title. Please, read FAQ. (string)', 1 => 'delimeter: Condition is unknown', ),
Но заменять каждое условие — не всегда нужно. Иногда нужно заменить все комментарии.
Подмена класса с ошибками (в т.ч. i18n)
Снова вернёмся к примеру «mapping». Для полноценной замены всех необходимых ошибок, напишем класс-наследник от AMatchStatus. Внутри необходимо перегрузить метод _fillComments(), не забывая вызвать родительский. Нужно создать объект данного класса и передать его в AMatch::runMatch(); третим параметром.
Example: russian
class AMatchRussian extends AMatchStatus { protected function _fillComments() { parent::_fillComments(); // Если не вызвать родительский метод, то отсутствующие строки будут отданы в виде кодов $this->_result_comments[self::KEY_NOT_EXISTS] = 'Искал, вот честно. Не нашел'; $this->_result_comments[self::KEY_CONDITION_NOT_VALID] = 'Параметр не соответствует требованиям, попробуйте поиграть шрифтами'; $this->_result_comments[self::CONDITION_IS_UNKNOWN] = 'Нипаняятна'; } } $match = AMatch::runMatch($params_bad, AMatch::FLAG_SHOW_GOOD_COMMENTS | AMatch::FLAG_DONT_STOP_MATCHING, new AMatchRussian()) // ... дальше те жепроверки, что и в примере mapping ; mapping($match);
Ответ будет содержать переведённые конструкции наравне с не имеющими перевода.
Результат примера «russian»
array ( 'other_errors' => array ( 0 => 'title: Incorrect document title. Please, read FAQ. (string)', 1 => 'delimeter: Нипаняятна', ), 'invalid_type' => array ( 0 => 'parent_id: Expected parameter type is not valid (int)', ), 'required' => array ( 0 => 'ineedkey: Искал, вот честно. Не нашел', ), 'invalid_data' => array ( 0 => 'subject_id: Попробуйте поиграть шрифтами (1)', ), )
Новые возможности callback
Помимо отслеживания ошибок, несколько усовершенствована работа с пользовательскими функциями. Напомню, что ранее их можно было вызвать следующим образом:
$match->data(array($this, 'callbackMethod'), 'callback');
На текущий момент поддержка callback расширена, и теперь можно вызывать по следующей схеме:
Example: callback
// param([mixed $callback_argument], [callable|callable $callback]) // или // param([callable|string $callback], 'callback') // Примеры: ->param($callback_property, 'MyClass::myFunc') ->param($callback_property, 'MyClass->myFunc') ->param($callback_property, array($my_obj, 'myFunc')) ->param($callback_property, array('MyClass', 'myFunc'))
Встроенные callback-плагины
Вместе с библиотекой AMatch лежат примеры готовых классов с методами, которые можно использовать в качестве пользовательского вызова. Это два класса:
- class AMatchArray
- class AMatchString
Рассмотрим примеры использования:
Example: plugins
function matchCallbacks($params) { $match = AMatch::runMatch($params, AMatch::FLAG_DONT_STOP_MATCHING) ->parent_id('/^-?\d+$/', 'AMatchString::pregMatch') // проверка значения регулярным выражением ->title(12, 'AMatchString::length') // длина строки равна ->email('([\w-]+@([\w-]+\.)+[\w-]+)', 'AMatchString::isEmail') // проверка email собственной регуляркой (игнорируя встроенный алгоритм) ->links(AMatchArray::FLAG_EMPTY_SOME_ELEMENT, 'AMatchArray::isNotEmpty') // проверка на пустоту по алгоритму: хотя бы один элемен массива или его вложенных массивов должен быть не-пустым ; result($match); } matchCallbacks($params); matchCallbacks($params_bad);
Результат примера «plugins»
// Хороший массив Dance! array ( ) array ( ) array ( ) // Плохой массив Cry! array ( 'parent_id' => 'str3', 'title' => 'str5', 'email' => 'str4', 'links' => 'arr8', ) array ( 'parent_id' => 'The string does not match the regular expression', 'title' => 'String required', 'email' => 'Incorrect email', 'links' => 'At least one element of the array must be non-empty', ) array ( 'parent_id' => array ( 0 => '/^-?\\d+$/', 1 => 'AMatchString::pregMatch', ), 'title' => array ( 0 => 12, 1 => 'AMatchString::length', ), 'email' => array ( 0 => 'someuser!@mail.dom', 1 => 'AMatchString::isEmail', ), 'links' => array ( 0 => NULL, 1 => 'AMatchArray::isNotEmpty', ), )
Послебуквие
Общая схема вызова AMatch на данный момент следующая:
$match = AMatch::runMatch(array $associative_array, bitmask $flags, AMatchStatus $obj) ->имя_ключа([ожидаемое_или_сравниваемое_значение], [условие]) ->…; $match->stopMatch(); // получить общий результат $match->matchResults(); // Получить коды статусов (ошибки или успешные статусы, если предусмотрены) $match->matchComments(); // Получить комментарии $match->matchCommentsConditions(); // Получить дополнительную информацию
Вы можете найти больше примеров (смотрите unittests) и скачать исходники AMatch на гитхабе, где вас ждёт готовый файл с примерами для этой статьи (examples2.php).
UPD:
Конвертировано для Composer.
