В этой статье я расскажу о некоторых новшествах, появившихся в проекте AMatch с момента написания первой статьи.
Напомню, что 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:
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);
В результате выполнения данного кода будет получен массив со структурой ошибок, привычной для написанного ранее кода.
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);
Ответ будет содержать переведённые конструкции наравне с не имеющими перевода.
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);
// Хороший массив
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).
Автор: KIVagant