Путь к DRY валидации в Laravel

от автора

Как-то услышал один чудак, что можно уменьшить количество дублей при написании правил валидации с помощью великого знания — 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’ев хватает.

Вот такая вот идея, с нетерпением жду замечаний и предложений по улучшению! Всем спасибо, кто читал!


ссылка на оригинал статьи https://habr.com/ru/articles/818309/