Как-то услышал один чудак, что можно уменьшить количество дублей при написании правил валидации с помощью великого знания - DRY, и решил он отправиться на поиски...
Его идеальное решение должно было учитывать несколько основных особенностей:
иметь более-менее лаконичный интерфейс;
давать возможность, при необходимости, редактировать или переписывать правила валидации для полей;
давать возможность на месте добавлять дополнительные поля;
поля в качестве имен могут иметь только строковые значения, но имя не может содержать в себе точки - они нужны для разделения уровней вложенности;
какие-то поля группировались по сущностям БД, какие-то были сами по себе.
Задача была поставлена, герою предстояло отправиться на поиски. Конечно, все хотелось сделать хорошо и быстро, поэтому первым делом он решился исследовать близлежащую деревеньку "Верхние интернеты", с целью раздобыть хоть какой-нибудь транспорт, но, ничего там не отыскав, он понял, что единственным решением для него остается собрать велосипед из подручных средств. Что же, взглянем что у нас имеется...
Чтобы не изобретать колеса, было решено использовать механизм хелперов, который уже присутствовал в Laravel. Не то чтобы очень сложно создать один файл в глобальной области видимости, но, все же, хорошо когда есть штатные средства, как-то на душе приятнее.
Следующим шагом было бы неплохо собрать раму. А так как это у нас заправский ду ит ёрселф, будем обходиться тем что есть - воображением и палками! Главное, чтобы ни то ни другое не попало в колеса.
Итак, первым делом сигнатура - это будет наш руль:
function get_validation_rules(array $fields, array $additionalRules = []): array
Здесь ничего сложного, берем массив с названиями полей и ассоциативный массив с дополнительными правилами для них. Пару слов о первом параметре: он может содержать как простое перечисления полей (индексированный массив), так и структуру типа название поля => правила валидации (ассоциативный массив). Сделано это для того, чтобы можно было перезаписывать правила валидации для отдельных полей.
Дальше, собственно, само тело, чтобы не мучить никого обрывками, вставляю всю функцию целиком:
<?php
if (!function_exists('get_validation_rules')) {
/**
* Возвращает список правил валидации для указанных полей
*
* @param array<int|string,string|array> $fields поля, для которых необходимо вернуть правила валидации
* @param array<string,array> $additionalRules дополнительные правила для полей указанных в $fields,
*
* @return array<string,array> сформированный ассоциативный массив: поле => правила
*/
function get_validation_rules(array $fields, array $additionalRules = []): array
{
$globalRules = config('validation');
foreach ($fields as $fieldKey => $field) {
if (!is_int($fieldKey)) {
$resultRules[$fieldKey] = $field;
continue;
}
$rulePath = explode('.', $field);
$rule = &$globalRules;
foreach ($rulePath as $rulePathItem) {
$rule = &$rule[$rulePathItem];
}
if (!empty($rule)) {
if (array_key_exists($field, $additionalRules)) {
$uniqueAdditionalRules = array_diff($additionalRules[$field], $rule);
$rule = array_merge($uniqueAdditionalRules, $rule);
}
$resultRules[$field] = $rule;
}
}
return $resultRules ?? [];
}
}
Вот она, настоящая рама с педалями! Вон, даже foreach крутится! Более того, тут еще и багажник красуется на 15 строке:
$globalRules = config('validation');
Да, в процессе возник закономерный вопрос: где же хранить этот набор правил для валидации? И, пожалуй, самым очевидным ответом на этот вопрос был - в файлах конфигураций. Выдерну лишь отрывок, просто чтобы показать как это выглядит:
<?php
return [
'user' => [
'phone_number' => ['required', 'string'],
'firstname' => ['required', 'string', 'max:255'],
'date_of_birth' => ['required', 'date', 'before:today'],
'citizenship' => ['required', 'max:512'],
'adress' => ['required', 'string', 'max:255'],
'affiliation' => ['required', 'string', 'max:255'],
'education' => ['required', 'string', 'max:500'],
],
'email' => ['required', 'string', 'max:255']
];
Собственно, правила могут быть на любом уровне вложенности, но за наименованиями, конечно, придется следить самому. Уровни вложенности, как и было написано в самом начале, разделяются точкой.
Что же, наконец-то можно протестировать это все в действии, для примера возьмем, собственно, класс request'та, в котором необходимо реализовать правила валидации:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CoolRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return get_validation_rules(
[
'user.firstname',
'user.phone_number' => ['string'], // Переписываем правила валидации полностью
'user.email' => ['required'] // Добавляем новое поле, которого нет в глобальных правилах
],
[
'user.firstname' => ['min:2'] // Добавляем дополнительное правило валидации к полю user.firstname
]
);
}
}
Вот и все, когда вопрос с транспортом решен, можно ехать на все четыре стороны, и несмотря ни на что, в этой истории все закончилось хорошо. Велосипед был собран, а чудак понял, что у него, в целом, и дома всяких DRY'ев хватает.
Вот такая вот идея, с нетерпением жду замечаний и предложений по улучшению! Всем спасибо, кто читал!