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

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

Комментарии (0)