De vez em quando, você se depara com uma situação em que precisa validar uma entrada de solicitação como booleana e o valor de entrada é 'true'ou 'false'(observe que coloquei os valores entre aspas simples para indicar que na verdade são strings).
Você esperaria, como eu fiz , que isso funcionaria imediatamente e o validador do Laravel os veria apenas como booleanos. Mas, esse não é o caso, em vez disso, você será atingido por esta bela mensagem de erro'The inputName field must be true or false.'

Para entender profundamente por que isso está acontecendo, vamos passar por um exemplo, digamos que temos uma solicitação de formulário que chamamos PostRequest.

Solicitações de formulário são classes de solicitação personalizadas que encapsulam sua própria lógica de validação e autorização.

– Documentos Laravel.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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, para fins de simplicidade, possui uma única entrada de solicitação que, primeiro, é obrigatória e, em segundo lugar, é boolean , e essa entrada é denominada is_publishedDigamos também que o is_publishedvalor que nos vem do cliente é 'true'ou 'false'.

Neste ponto já sabemos que a validação não passaria. Então, como podemos lidar com isso?

Antecipadamente

Antes de mergulhar, quero mostrar a lógica que o Laravel usa para validar booleanos internamente, você pode ver isso no validateBoolean()método localizado em Illuminate/Validation/Concerns/ValidatesAttributes.

<?php

namespace Illuminate\Validation\Concerns;

/**
 * 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);
}

Também podemos ver isso nos testes de unidade.

<?php

use Illuminate\Validation\Validator;

// ...

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());

    // ...
}

Fonte: https://github.com/laravel/framework/blob/8.x/tests/Validation/ValidationValidatorTest.php

Soluções

A primeira abordagem - use o método prepareForValidation

Vamos começar com o que eu acho que é uma abordagem mais fácil de implementar, é o que o Laravel chama de “Preparando Entrada para Validação”, e isso é feito usando o prepareForValidation()método. Como o próprio nome revela, esse método nos permite adicionar novas entradas de solicitação ou atualizar entradas de solicitação existentes antes de passar pelas regras de validação.

Portanto, em nosso pequeno exemplo aqui, tentaremos converter o is_publishedvalor em um booleano real e mesclá-lo de volta à solicitação original.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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_BOOLEANtenta ser inteligente, reconhecendo palavras como 'Yes''No''Off''On'e não diferencia maiúsculas de minúsculas ao validar strings.'true''false'

FILTER_VALIDATE_BOOLEANretorna verdadeiro para '1''true'Retorna false caso contrário.'on''yes'

Quando FILTER_NULL_ON_FAILUREsinalizador é definido, false é retornado SOMENTE para '0''false''off''no''', e null é retornado para todos os valores não booleanos.

Compreender como o FILTER_NULL_ON_FAILUREsinalizador afeta a filter_varfunção é essencial, especialmente ao abordar a segunda abordagem, como veremos mais adiante.
Por esse motivo, deixe-me fornecer alguns exemplos para demonstrar como o toBooleanmétodo se comporta em diferentes casos de uso.

$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

Até aqui faz todo o sentido, booleanos true verdadeiros” são , booleanos “falsos” são false, outros são apenas nullPerfeito!

$this->toBoolean('');                 // false

Aqui, é onde se interessa, este último caso de uso pode realmente ser confuso, eu mesmo estava esperando nullcomo valor de retorno, mas recebemos um booleanem vez disso falseneste caso) .

Isso causará uma validação falsa , pois a string vazia será avaliada como a boolean, o que fará a validação passar.

Observe que isso nunca será o caso em nosso exemplo, pois temos uma requiredregra, se a entrada da solicitação is_published) for uma string vazia, a validação falhará antes mesmo de atingir a booleanregra.

Achei importante trazer isso à tona.

Com isso dito, vamos pular direto para a segunda abordagem.

A segunda abordagem - use uma regra de validação personalizada

Embora a primeira abordagem funcione perfeitamente, existe uma maneira “elegante” de validar a entrada como booleana, e isso é criar regras de validação personalizadas usando objetos de regra.

O Laravel fornece uma variedade de regras de validação úteis; no entanto, você pode querer especificar algumas de sua preferência. Um método de registro de regras de validação personalizadas é usar objetos de regra. Para gerar um novo objeto de regra, você pode usar o comando make:rule Artisan.

– Documentos Laravel.

Vamos usar este comando para gerar uma regra que valida um valor de string de truefalsecomo booleano.

php artisan make:rule Boolean

 

O Laravel colocará a nova regra no app/Rulesdiretório. Se este diretório não existir, o Laravel irá criá-lo quando você executar o comando Artisan para criar sua regra.

– Documentos Laravel.

Como prometido, uma Booleanclasse é criada no app/Rulesnamespace, e aqui está o que parece:

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

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.';
    }
}

Uma vez que a Booleanregra foi criada, estamos prontos para definir seu comportamento.
Um objeto de regra contém dois métodos: passesmessageO método passes recebe o valor do atributo e o nome e deve retornar trueou falsedependendo se o valor do atributo é válido ou não. O método de mensagem deve retornar a mensagem de erro de validação que deve ser usada quando a validação falha.

Pequeno turno - faça funções auxiliares globais

Mas, antes de fazer isso, pode ser útil extrair toBooleande antes para sua própria função e disponibilizá-la globalmente.

Uma maneira fácil e eficiente de criar funções globais no Laravel é carregá-las automaticamente diretamente do Composer . A seção de autoload do composer aceita um filesarray que é carregado automaticamente.

  1. Crie um helpers.phparquivo onde quiser. Eu costumo manter meus ajudantes globais em app/Support/helpers.php.
  2. Adicione suas funções auxiliares, não há necessidade de especificar nenhum namespace (para que não precisemos usá use function-las para chamá-las) .

     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. Dentro composer.jsonda autoloadseção adicione a seguinte linha "files": ["app/Support/helpers.php"].

     "autoload": {
         "psr-4": {
             "App\\": "app/",
             "Database\\Factories\\": "database/factories/",
             "Database\\Seeders\\": "database/seeders/"
         },
         "files": ["app/Support/helpers.php"]
     }
    
  4. Correcomposer dump-autoload

Agora nossa to_booleanfunção pode ser chamada em qualquer lugar em nosso projeto.

De volta à nossa Booleanregra.

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

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');
    }
}

Para o nosso caso, podemos remover com segurança o construtor.

is_boolfunção é uma função nativa do php, ela descobre se uma variável é booleana.

__função é um auxiliar de strings do Laravel, ele traduz a string de tradução ou chave de tradução fornecida usando seus arquivos de localização.

Está quase pronto, tudo o que precisamos fazer agora é atualizar nosso PostRequestpara implementar o objeto de regra personalizado Booleanassim:

<?php

use App\Rules\Boolean;
    
    // ...
    
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'is_published' => ['required', new Boolean],
        ];
    }

Conclusão

Finalmente, nosso post chegou ao fim. Então, uma rápida recapitulação, descrevemos duas maneiras, abordagens, se você quiser, para validar 'true''false'como booleano com o validador Laravel.

A primeira abordagem é preparar a entrada para validação ao longo do uso do prepareForValidationmétodo fornecido a nós por FormRequest.

A segunda abordagem é usar regras de validação personalizadas, mais precisamente objetos de regras, para isso criamos nosso próprio Booleanobjeto para fazer o trabalho.

Eu sei que eu disse que a primeira abordagem é mais fácil de implementar, mas agora que eu uso o objeto de regra com mais frequência, acho que é mais simples e limpo, a abstração no objeto de regra é mais “amigável ao desenvolvedor” por assim dizer, a primeira abordagem é, sem dúvida, mais detalhada. De qualquer forma, é bom conhecer os dois, use o que for mais adequado ao seu caso de uso ou à sua preferência pessoal.

Referências