Laravel — проверка ‘true’ или ‘false’ как булевых значений

Эта статья была первоначально опубликована в моем личном блоге 30 декабря 2021 года.

Время от времени вы сталкиваетесь с ситуацией, когда вам нужно подтвердить ввод запроса как булево значение, а входное значение 'true' или 'false' (обратите внимание, что я заключил значения в одинарные кавычки, чтобы показать, что на самом деле это строки). Вы, как и я, ожидаете, что это будет работать из коробки, и валидатор Laravel будет рассматривать их как булевы значения. Но это не так, вместо этого вы получите красивое сообщение об ошибке 'The inputName field must be true or false.'.

Чтобы понять, почему это происходит, давайте рассмотрим пример. Допустим, у нас есть запрос формы, который мы называем PostRequest.

Запросы формы — это классы пользовательских запросов, которые инкапсулируют собственную логику валидации и авторизации.

— Laravel docs.

<?php

namespace AppHttpRequests;

use IlluminateFoundationHttpFormRequest;

class PostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'is_published' => 'required|boolean',
        ];
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

PostRequest, для простоты, имеет единственный вход запроса, который, во-первых, является обязательным, а во-вторых, булевым, и этот вход называется is_published. Предположим также, что значение is_published, приходящее к нам от клиента, равно 'true' или 'false'.

На этом этапе мы уже знаем, что валидация не пройдет. Итак, как мы можем справиться с этим?

Предварительно

Прежде чем погрузиться в эту тему, я хочу показать вам логику, которую Laravel использует для внутренней валидации булевых значений. Вы можете увидеть это в методе validateBoolean(), расположенном в Illuminate/Validation/Concerns/ValidatesAttributes.

<?php

namespace IlluminateValidationConcerns;

/**
 * Validate that an attribute is a boolean.
 *
 * @param  string  $attribute
 * @param  mixed  $value
 * @return bool
 */
public function validateBoolean($attribute, $value)
{
    $acceptable = [true, false, 0, 1, '0', '1'];

    return in_array($value, $acceptable, true);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Мы также можем видеть это в данном модульном тесте.

<?php

use IlluminateValidationValidator;

// ...

public function testValidateBoolean()
{
    $trans = $this->getIlluminateArrayTranslator();

    // ...

    $v = new Validator($trans, ['foo' => 'false'], ['foo' => 'Boolean']);

    $this->assertFalse($v->passes());

    $v = new Validator($trans, ['foo' => 'true'], ['foo' => 'Boolean']);

    $this->assertFalse($v->passes());

    // ...
}
Вход в полноэкранный режим Выход из полноэкранного режима

Источник : https://github.com/laravel/framework/blob/8.x/tests/Validation/ValidationValidatorTest.php

Решения

Первый подход — использование метода prepareForValidation

Давайте начнем с более простого, на мой взгляд, подхода, который Laravel называет «подготовкой ввода к проверке»,
и делается это с помощью метода prepareForValidation(). Как видно из названия, этот метод позволяет нам добавлять новые или обновлять существующие входы запроса перед прохождением правил валидации.

Поэтому в нашем небольшом примере мы попытаемся преобразовать значение is_published в действительное булево значение и объединить его с исходным запросом.

<?php

namespace AppHttpRequests;

use IlluminateFoundationHttpFormRequest;

class PostRequest extends FormRequest
{
    // ...

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'is_published' => 'required|boolean',
        ];
    }

    /**
     * Prepare inputs for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $this->merge([
            'is_published' => $this->toBoolean($this->is_published),
        ]);
    }

    /**
     * Convert to boolean
     *
     * @param $booleable
     * @return boolean
     */
    private function toBoolean($booleable)
    {
        return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
    }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

FILTER_VALIDATE_BOOLEAN пытается быть умным, распознавая такие слова как 'Yes', 'No', 'Off', 'On', 'true' и 'false', и не чувствителен к регистру при проверке строк.

FILTER_VALIDATE_BOOLEAN возвращает true для '1', 'true', 'on' и 'yes'. В противном случае возвращает false.

Когда установлен флаг FILTER_NULL_ON_FAILURE, false возвращается ТОЛЬКО для значений '0', 'false', 'off', 'no' и '', а null возвращается для всех небулевых значений.

Понимание того, как флаг FILTER_NULL_ON_FAILURE влияет на функцию filter_var, очень важно, особенно при рассмотрении второго подхода, как мы увидим позже.

Поэтому позвольте мне привести несколько примеров, демонстрирующих поведение метода toBoolean в различных ситуациях.

$this->toBoolean('1');                // true
$this->toBoolean('true');             // true
$this->toBoolean('on');               // true
$this->toBoolean('yes');              // true

$this->toBoolean('0');                // false
$this->toBoolean('false');            // false
$this->toBoolean('off');              // false
$this->toBoolean('no');               // false

$this->toBoolean('not a boolean');    // null
Вход в полноэкранный режим Выход из полноэкранного режима

До этого момента все логично: «истинные» булевы — это true, «ложные» — false, остальные — просто null. Отлично!

$this->toBoolean('');                 // false
Вход в полноэкранный режим Выход из полноэкранного режима

Вот здесь становится интересно, этот последний случай использования может действительно запутать, я сам ожидал null в качестве возвращаемого значения, но вместо этого мы получаем boolean. (false в данном случае).

Это приведет к ложной проверке, потому что пустая строка будет оценена как boolean, что сделает проверку пройденной.

Обратите внимание, что в нашем примере этого не произойдет, потому что у нас есть правило required, если входной запрос (is_published) является пустой строкой, валидация будет провалена еще до того, как будет выполнено правило boolean.

Я посчитал важным упомянуть об этом.

С учетом сказанного, давайте перейдем непосредственно ко второму подходу.

Второй подход — использование пользовательского правила валидации

Хотя первый подход отлично работает, существует «стильный» способ проверки ввода как boolean, и это создание пользовательских правил валидации с помощью объектов правил.

Laravel предоставляет множество полезных правил валидации, однако вы можете захотеть задать несколько своих собственных. Одним из методов регистрации пользовательских правил валидации является использование объектов правил. Чтобы создать новый объект правила, вы можете использовать команду make:rule Artisan.

— Laravel docs.

Давайте воспользуемся этой командой для создания правила, которое проверяет строковое значение true и false как булево.

php artisan make:rule Boolean
Войти в полноэкранный режим Выход из полноэкранного режима

Laravel поместит новое правило в каталог app/Rules. Если этого каталога не существует, Laravel создаст его, когда вы выполните команду Artisan для создания правила.

— Laravel docs.

Как и было обещано, класс Boolean создан в пространстве имен app/Rules, и вот как он выглядит:

<?php

namespace AppRules;

use IlluminateContractsValidationRule;

class Boolean implements Rule
{
    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        //
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The validation error message.';
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

После создания правила Boolean мы готовы определить его поведение.

Объект правила содержит два метода: passes и message. Метод passes получает значение и имя атрибута и должен возвращать true или false в зависимости от того, является ли значение атрибута действительным или нет. Метод message должен возвращать сообщение об ошибке валидации, которое должно использоваться при неудачной валидации.

Небольшой поворот — сделать глобальные вспомогательные функции

Но, прежде чем сделать это, может быть полезно извлечь toBoolean из предыдущей функции в свою собственную функцию и сделать ее доступной глобально.

Простой и эффективный способ создания глобальных функций в Laravel — это автозагрузка непосредственно из Composer. Секция autoload в composer принимает массив files, который автоматически загружается.

  1. Создайте файл helpers.php в любом удобном для вас месте. Я обычно держу свои глобальные помощники в app/Support/helpers.php.
  2. Добавьте свои функции-помощники, не нужно указывать пространство имен (поэтому нам не нужно использовать use function для их вызова).

    if (!function_exists('to_boolean')) {
    
        /**
         * Convert to boolean
         *
         * @param $booleable
         * @return boolean
         */
        function to_boolean($booleable)
        {
            return filter_var($booleable, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
        }
    }
    
  3. В composer.json внутри секции autoload добавьте следующую строку "files": ["app/Support/helpers.php"].

    "autoload": {
        "psr-4": {
            "App\": "app/",
            "Database\Factories\": "database/factories/",
            "Database\Seeders\": "database/seeders/"
        },
        "files": ["app/Support/helpers.php"]
    }
    
  4. Запустите composer dump-autoload.

Теперь наша функция to_boolean может быть вызвана в любом месте нашего проекта.

Вернемся к нашему правилу Boolean.

<?php

namespace AppRules;

use IlluminateContractsValidationRule;

class Boolean implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        return is_bool(to_boolean($value));
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return __('validation.boolean');
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Для нашего случая мы можем смело удалить конструктор.

Функция is_bool — это встроенная функция php, она выясняет, является ли переменная булевой.

Функция __ — это помощник Laravel strings, она переводит заданную строку перевода или ключ перевода, используя ваши файлы локализации.

Все почти готово, теперь нам осталось обновить наш PostRequest, чтобы реализовать объект пользовательского правила Boolean следующим образом:

<?php

use AppRulesBoolean;

    // ...

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'is_published' => ['required', new Boolean],
        ];
    }
Войти в полноэкранный режим Выйти из полноэкранного режима

Заключение

Наконец, наш пост подошел к концу. Вкратце напомним, что мы описали два способа, если хотите, подхода к проверке 'true' и 'false' как булевых значений с помощью Laravel validator.

Первый подход заключается в подготовке ввода к валидации с помощью метода prepareForValidation, предоставляемого нам FormRequest.

Второй подход заключается в использовании пользовательских правил валидации, точнее, объектов правил, для чего мы создали собственный объект Boolean для выполнения этой работы.

Я знаю, что говорил, что первый подход проще в реализации, но теперь, когда я чаще использую объект rule, я нахожу его более простым и чистым, абстракция в объекте rule более «дружелюбна к разработчику», так сказать, а первый подход, возможно, более многословен. В любом случае, полезно знать их оба, используйте тот, который подходит для вашего случая использования или ваших личных предпочтений.

Ссылки

  • php.net — is_bool
  • php.net — валидация фильтров
  • php.net — filter_var — примечания, внесенные пользователем
  • stackoverflow.com — Как сделать глобальные вспомогательные функции в laravel
  • stackoverflow.com — Входное значение false для FILTER_VALIDATE_BOOLEAN
  • github.com — Валидация булевых значений не принимает «true» и «false», но принимает «1», «0»

Первоначально опубликовано на https://echebaby.com 30 декабря 2021 года.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *