Тримайте дані під контролем

Не секрет, що користувальницьким даними довіряти не можна. Тому одного разу людина і придумав валідацію даних. Ну а я, заради цікавості і для користі, написав свою реалізацію валідатора на PHP.

Kontrolio — «чергова бібліотека валідації даних», спроектована незалежної від фреймворків, розширюваної і дружній контейнерів сервісів. Альтернативи: Respect, Sirius Validation, Valitron і багато інших.

В ідеалі передбачається, що ви використовуєте якусь реалізацію контейнера сервісів (напр., PHP-DI, PHP League Container та ін), тому для початку необхідно зареєструвати Kontrolio в ньому:

use Kontrolio\Factory;

// Реєструємо
$container->singleton('validation', function() {
return new Factory;
});

// Використовуємо
$validator = $container->get('validation')
->make($data, $rules, $messages)
->validate();


У тих ситуаціях, коли впровадити контейнер в проект важко, ви можете використовувати старий-добрий (насправді немає) сінглтон:

use Kontrolio\Factory;

$validator = Factory::getInstance()
->make($data, $rules, $messages)
->validate();


Можливо ви помітите, що процес валідації схожий на нім з Laravel. Дійсно, мені сподобалося те, як там це реалізовано, тому я вирішив використовувати подібне рішення. $data, $rules та $messages — асоціативні масиви, де $data — це просто масив пар ключ-значення (може бути багатовимірним), в якому ключ це ім'я атрибута, який необхідно провалидировать. Найцікавіше — в правилах валідації і повідомленнях про помилки.

Правила валідації та повідомлення про помилки
Правило валідації в Kontrolio може бути представлено об'єктом класу правила або замиканням. Замикання — найпростіший спосіб опису правила валідації:

$rules = [
'attribute' => function($value) {
return $value === 'foo';
}
];

$valid = $validator
->make(['attribute' => 'bar'], $rules)
->validate();

var_dump($valid); // false


Правила-замикання при обробці валідатором обертаються в об'єкт класу Kontrolio\Rules\CallbackRuleWrapper, тому вони розташовують всіма тими ж можливостями, що і класи-правила, ви можете написати замикання в такому вигляді:

'attribute' => function($value) {
return [
'valid' => $value === 'foo',
'name' => 'value_is_foo_rule',
'empty_allowed' => true,
'skip' => false,
'violations' => []
];
}


Замикання зручні для простих правил, які не призначені для багаторазового використання. Але якщо ви плануєте використовувати правило в різних місцях, то краще написати окремий клас правила і потім створити його об'єкт:

use Kontrolio\Rules\AbstractRule;

class FooRule extends AbstractRule
{
public function isValid($input = null)
{
return $input === 'foo';
}
}

$rules = ['attribute' => new FooRule];

На замітку: Kontrolio поставляється з безліччю правил «з коробки».

Опції правил валідації
У записі правил у вигляді замикань ви помітили кілька опцій, які підтримує будь правило. Трохи про кожної опції далі.

valid. Це безпосередньо умова. Еквівалент для класу правила — метод isValid, що приймає один аргумент, валидируемое значення атрибута. Щоб стало зрозуміліше, я покажу, як можна визначити правило валідації для якогось атрибути:

// найпростіший спосіб, де обчислене значення є умова правила.
'attribute' => function($value) {
return $value === 'foo';
}

// Якщо ви хочете використовувати опції, вам необхідно повертати масив з замикання,
// при цьому в масиві має бути обов'язково вказаний ключ 'valid',
// який визначає умову правила.
'attribute' => function($value) {
return [
'valid' => $value === 'foo'
// інші опції...
];
}

// При використанні окремого класу ви наслідуєте своє правило від базового класу
// і визначаєте метод isValid, в якому повертаєте умова.
use Kontrolio\Rules\AbstractRule;

class FooRule extends AbstractRule
{
public function isValid($input = null)
{
return $input === 'foo';
}
}


Це найпростіші способи задання правила до атрибуту.

name. Це ім'я, або ідентифікатор правила. Головним чином, використовується для формування повідомлень про помилки валідації:

$data = ['attribute' => 'invalid'];
$rules = ['attribute' => new Email];
$messages = ['attribute.email' => 'The attribute must be an email'];

$validator = $container->get('validation')
->make($data, $rules, $messages);
if ($validator->validate()) {
//
} else {
$messages = $validator->getErrors();
}


Якщо ви створюєте правило на основі класу, то вам немає необхідності вказувати назву/ідентифікатор правила вручну, тому що наследуясь від Kontrolio\Rules\AbstractRule ви отримуєте дану функціональність за замовчуванням в методі getName. Тим не менше ви можете вільно змінювати ім'я правила просто якщо перевизначити цей метод.

empty_allowed. Ця опція корисна в ситуаціях, коли вам необхідно застосувати правило валідації тільки в тому випадку, якщо значення атрибута присутній. З замиканнями це виглядає так:

'attribute' => function($value) {
return [
'valid' => $value === 'foo',
'empty_allowed' => true
];
}


Використовуючи клас-правило з даною опцією, ви можете застосувати його до атрибуту двома способами:

// Іменований конструктор
'attribute' => FooRule::allowingEmptyValue()

// Або не відповідний статичний метод
'attribute' => (new FooRule)->allowEmptyValue()


В даному випадку, валідатор відповість позитивно, якщо значення атрибута значення 'foo' або він буде порожній.

skip. Ця опція не схожа на попередню. Вона дозволяє вам вказати таку умову, що накаже валідатору пропустити перевірку атрибуту так, як ніби цього правила і не було зовсім.

'confirmed' => function($value) {
return [
'valid' => (bool)$value === true,
'skip' => is_admin()
];
}


Еквівалент для правила-класу — метод canSkipValidation, і працює він абсолютно так само:

class FooRule
{
public function isValid($input = null)
{
return (bool)$value === true;
}
public function canSkipValidation()
{
return is_admin();
}
}
$rules = ['confirmed' => new FooRule];


violations. Я люб'язно запозичив цей термін з Symfony. З використанням «порушень» користувач може отримати більш точне повідомлення про помилку (яке вам необхідно задати), хоча сам валідатор, так само як і раніше, просто поверне false:

$data = 'nonsense';
$rules = ['attribute' => new Email(true, true)];
$messages = [
'attribute' => [
'email' => "something's wrong with your email.",
'email.mx' => 'MX record is wrong.',
'email.host' => 'Unknown email host.'
]
];


Ви можете задавати стільки «порушень», скільки забажаєте, і кожне з них потім може бути використане для більш детального опису помилок валідації: від самого загального повідомлення до самого деталізованого. Подивіться, як приклад, клас Kontrolio\Rules\Core\Email.

Застосовуємо декілька правил до атрибутів
До цього всі приклади показували опис одного правила до одного атрибуту. Але, звичайно, ви можете додати скільки завгодно багато правил до як завгодно багатьох атрибутів :) Більш того, ви можете поєднувати використання замикань і класів:

$rules = [
'some' => function($value){
return $value === 'foo';
},
'another' => [
function($value) {
return $value !== 'foo';
},
new FooBarRule,
// і так далі...
]
];


Все круто, звичайно, але є ще один цікавий спосіб запису цілого набору правил — у вигляді рядка:

'attribute' => 'not_empty|length:5,15'


Тут кожне правило відділяється вертикальною рискою. Цю ідею я запозичив з Laravel, але різниця в тому, що будь-яка така рядок «розгортається» в звичайний масив правил, що ви вже не раз бачили в статті. Так що рядок вище в даному випадку — лише сахарок для ось такого масиву:

'attribute' => [
new NotEmpty,
new Length(5, 15)
]


Зверніть увагу, що все, що ви пишете після двокрапки, прямо потрапляє в аргументи конструктора правила-класу:

'length:5, 15' -> new Length(5, 15)


Так що тут треба бути обережним.

Пропускаємо валідацію атрибута цілком
Пропуску окремого правила або дозволу порожніх значень було б недостатньо, тому Kontrolio містить спеціальне правило, названий за аналогією з Laravel — 'sometimes' і представлена класом Kontrolio\Rules\Core\Sometimes. Коли ви додасте це правило до атрибуту, воно вкаже валідатору пропустити перевірку атрибуту, якщо він відсутній в масиві даних, переданих в валідатор, або якщо його значення порожньо. Дане правило необхідно завжди ставити першим у списку.

$data = [];
$rules = ['attribute' => 'sometimes|length:5,15'];

$valid = $container
->get('validator')
->make($data, $rules)
->validate();

var_dump($valid); // true


За аналогією з попередніми прикладами даний може бути записаний і так:

$data = [];
$rules = [
'attribute' => [
new Sometimes,
new Length(5, 15)
]
];

$valid = $container
->get('validator')
->make($data, $rules)
->validate();

var_dump($valid); // true


Висновок помилок валідації
Помилки валідації зберігаються у вигляді асоціативного масиву, де ключі назви атрибутів, а значення — масиви з самими повідомленнями:

$data = ['attribute' => "];
$rules = [
'attribute' => [
new NotBlank,
new Length(5, 15)
]
];

$messages = [
'attribute.not_blank' => 'The attr. is required.',
'attribute.length.min' => 'It must contain at lest 5 chars'
];

$valid = $container
->get('validator')
->make($data, $rules)
->validate();

$errors = $validator->getErrors();


Дамп помилок буде виглядати наступним чином:

[
'attribute' => [
0 => 'The attr. is required.',
1 => 'It must contain at lest 5 chars'
]
]


Тому якщо ви хочете просто вивести всі помилки поспіль, використовуйте метод валідатора getErrorsList. Він поверне плоский масив з повідомленнями:

$errors = $validator->getErrorsList();

<ul class="errors">
<?php foreach($errors as $error): ?>
<li class="errors__error"><?= $error; ?></li>
<?php endforeach; ?>
</ul>


Для більш складного виводу помилок можна використовувати метод getErrors. Він повертає повідомлення, згруповані за назвами атрибутів:

<ul class="errors">
<?php foreach ($errors as $attribute => $messages):
<li class="errors__attribute">
<b><?= $attribute; ?></b>
<ul>
<?php foreach ($messages as $message): ?>
<li><?= $message; ?></li>
<?php endforeach; ?>
</ul>
</li>
<?php endforeach; ?>
</ul>


Завершуючи цей розповідь
Ось так ви можете використовувати Kontrolio, ще одну бібліотеку валідації даних на PHP. Під час написання статті я задумався про те, що простого переказу документації буде недостатньо. Тому я планую написати статтю, де спробую порівняти свою бібліотеку з іншими рішеннями.

Дякую за прочитання!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.