Respect Validation - Validando os dados de entrada do usuário

Uma das máximas do desenvolvimento é que não podemos confiar nos dados que o usuário envia, um amigo sempre dizia que se o label de um campo está escrito “Informe um número” em algum momento alguém iria escrever “doze” nele. Hoje em dia é bastante fácil realizar validações no frontend através das tags input corretas, mas como bem sabemos devemos validar os dados tanto no frontend como no backend, algo que pode parecer fazer o trabalho duas vezes é fundamental pois um usuário mal intencionado com conhecimentos médios pode facilmente burlar as validações do frontend e já no backend é um pouco mais difícil.

Normalmente utilizamos o mecanismo de validação que o framework em que estamos desenvolvendo nos fornece, mas em alguns casos o projeto é simples e decidimos não utilizar um framework mais “parrudo”, então utilizar uma biblioteca a parte para validação é interessante.

Uma biblioteca para a validação é Respect Validation que possui alguns recursos interessantes, para utilizá-la basta adicioná-la como dependência do composer:

composer require respect/validation

E para utilizá-lo é bastante simples.

<?php
declare(strict_types=1);
require('./vendor/autoload.php');

use Respect\Validation\Validator;

$inputNome = "rodrigo#botecodigital.dev.br";
$validator = Validator::email();

if($validator->validate($inputNome) ){
    echo "Válido!".PHP_EOL;
}else{
    echo "Inválido!".PHP_EOL;
}

Na linha 8 criamos um validador para a regra de e-mail e na linha 10 chamamos o método validate passando o valor que desejamos validar. Este método retorna true se o valor for válido ou false se for inválido.

Uma das vantagens é que podemos encadear várias regras de validação, veja um exemplo:

$inputLogin = "rod@12";
$validator = Validator::alnum()->noWhitespace()->length(4, null);

if($validator->validate($inputLogin) ){
    echo "Válido!".PHP_EOL;
}else{
    echo "Inválido!".PHP_EOL;
}

No exemplo acima vemos um validador criado para aceitar caracteres alfanuméricos(alnum), sem espações(noWhitespace) e com pelo menos 4 caracteres(length(4, null)).

O método validate apenas retorna verdadeiro ou falso se o valor é valido ou não, mas no caso de várias regras não sabemos qual regra falhou para informar para o usuário. Para isso podemos utilizar o método, o assert que lança uma exceção com as devidas mensagens.

$inputLogin = "rod 12@";
$validator = Validator::alnum()->noWhitespace()->length(4, null);

try{
    $validator->assert($inputLogin);
    echo "Válido!".PHP_EOL;
}catch(NestedValidationException $e){
    echo "Inválido!".PHP_EOL;
    foreach ($e->getMessages()  as $key => $value) {
        echo $key. ' -> '. $value . PHP_EOL;
    }
}

A saída deste código será:

Inválido!
alnum -> "rod 12@" must contain only letters (a-z) and digits (0-9)
noWhitespace -> "rod 12@" must not contain whitespace

Ao utilizar o método assert capturamos a exceção NestedValidationException que possui o método getMessages que retorna um array associativo onde a chave é a regra violada e o valor é a mensagem correspondente.

Regras de Validação do Respect Validation

Um dos benefícios do Respect Validation é que ele já fornece um grande conjunto de regras prontas, entre as mais comuns:

  • allOf: Valida se uma série de regras passadas como argumento são válidas. Semelhante a chamar as regras de forma encadeada.
  • alnum: Valida se a entrada é um alfanumérico ou não( a-z e 0-9). É possível passar como argumento um caractere que vai ser aceito. Ex.: almun(‘-‘)
  • alpha: Valida se uma entrada é alfabética(a-z) ou não.
  • number: Valida se uma entrada é um valor numérico.
  • email: Valida se uma entrada é um e-mail válido.
  • date: Valida se uma entrada é uma data válida. O formato padrão é “Y-m-d”, mas outro formato pode ser passado como argumento, por exemplo date(”d/m/Y”) valida a data “15/06/2020”. O formato segue o da função date.
  • datetime: Valida se a entrada é uma date/time válido. O formato segue o da função date.
  • length: Valida se tamanho da string de entrada está entre os limites passados como argumento. Ex. length(4, 8).
  • between: Valida se a entrada esta entre os dois valores passados. Ex. between(0,255).
  • min: Valida se a entrada é maior que o valor passado por argumento. Ex. min(18).
  • max: Valida se a entrada é menor que o valor passado por argumento. Ex. max(100).
  • domain: Valida se a entrada é um domínio(sem http://). Ex. domain( 'www.botecodigital.dev.br' ).
  • url: Valida se a entrada é uma Url válida.
  • slug: Valida se a entrada é um slug válido.
  • cpf: Valida se a entrada é um CPF válido.
  • cnpj: Valida se a entrada é um CNPJ válido.

Outras regras podem ser vista na documentação.

Validando Objetos

O Respect Validation permite a validação dos atributos de um objeto facilmente através do método attribute recebendo como argumentos o nome do campo a ser validado e um validador. Veja um exemplo:

class Pessoa{
    private $nome;
    private $idade;
    private $email; 

    public function setNome(string $nome): void{
        $this->nome = $nome;
    }
    public function getNome(): string{
        return $this->nome;
    }

    public function setIdade(int $idade): void{
        $this->idade = $idade;
    }
    public function getIdade(): int{
        return $this->idade;
    }

    public function setEmail(string $email): void{
        $this->email = $email;
    }
    public function getEmail(): string{
        return $this->email;
    }
}

$pessoa = new Pessoa();
$pessoa->setNome('Rodrigo@');
$pessoa->setIdade(36);
$pessoa->setEmail('rodrigobotecodigital.dev.br');

$validator = new Validator();
$validator->attribute('nome', Validator::alnum()->length(3,null));
$validator->attribute('idade', Validator::between(0, 120));
$validator->attribute('email', Validator::email());

try{
    $validator->assert($pessoa);
    echo "Válido!".PHP_EOL;
}catch(NestedValidationException $e){
    echo "Inválido!".PHP_EOL;

    foreach ($e->getMessages()  as $key => $value) {
        echo $key. ' -> '. $value . PHP_EOL;
    }
}

Para validar um array é feito de maneira semelhante, apenas utilizando o método key ao invés do attribute:

$pessoa = [
    'nome' => 'rodr@',
    'idade' => '-36',
    'email' =>'rodrigobotecodigital.dev.br'
];

$validator = new Validator();
$validator->key('nome', Validator::alnum()->length(3,null));
$validator->key('idade', Validator::between(0, 120));
$validator->key('email', Validator::email());

try{
    $validator->assert($pessoa);
    echo "Válido!".PHP_EOL;
}catch(NestedValidationException $e){
    echo "Inválido!".PHP_EOL;

    foreach ($e->getMessages()  as $key => $value) {
        echo $key. ' -> '. $value . PHP_EOL;
    }
}

Traduzindo as Mensagens de validação

Para traduzir as mensagens de erro de validação retornadas pelo método getMessages() basta passar um array associativo como argumento para o método, sendo a chave a regra de validação e valor a mensagem desejada. Veja um exemplo.

$messages = [
    'alnum'         => '{{name}} precisa conter somente caracteres alfanuméricos.',
    'noWhitespace'  => '{{name}} mão pode conter espaços em branco.',
    'length'        => '{{name}} o tamanho precisa ser entre {{minValue}} e {{maxValue}}.',
];


$inputLogin = "rod 12";
$validator = Validator::alnum()->noWhitespace()->length(4, null);

try {
    $validator->assert($inputLogin);
    echo "Válido!" . PHP_EOL;
} catch (NestedValidationException $e) {
    echo "Inválido!" . PHP_EOL;

    foreach ($e->getMessages($messages)  as $key => $value) {
        echo $key . ' -> ' . $value . PHP_EOL;
    }
}

Também é possível definir diretamente na regra a mensagem de erro através do método setTemplate.


$pessoa = new Pessoa();
$pessoa->setNome('o@');
$pessoa->setIdade(-36);
$pessoa->setEmail('rodrigobotecodigital.dev.br');

$validator = new Validator();
$validator->attribute('nome', Validator::allOf(
    Validator::alnum()->setTemplate('precisa conter somente caracteres alfanuméricos.'),
    Validator::length(3,null)->setTemplate('precisa ser maior que 3.')
));
$validator->attribute('idade', Validator::between(0, 120)->setTemplate('precisa ser entre 0 e 120'));
$validator->attribute('email', Validator::email())->setTemplate('precisa ser um email válido.');

try{
    $validator->assert($pessoa);
    echo "Válido!".PHP_EOL;
}catch(NestedValidationException $e){
    echo "Inválido!".PHP_EOL;

    foreach ($e->getMessages()  as $key => $value) {
        echo $key. ' -> '. $value . PHP_EOL;
    }
}

A saída será:

Inválido!
nome -> precisa ser maior que 3.
idade -> precisa ser entre 0 e 120
email -> precisa ser um email válido.

Regras Customizadas

Também é possível criar suas próprias regras, para isso precisamos criar uma classe de regra e uma de exceção, cada uma no seu devido pacote, neste exemplo colocaremos a regra em Botecodigital\Validation\Rules e a exceção em Botecodigital\Validation\Exceptions.

Primeiros devemos criar um classe filha de AbstractRule com o método validate retornando um booleano.

namespace Botecodigital\Validation\Rules;

use Respect\Validation\Rules\AbstractRule;

final class Par extends AbstractRule{
    public function validate($input): bool{
        return intval($input) % 2 == 0;
    }
}

Após criamos a classe de regra criamos a de exceção, ela deve ser nomeada com o nome da regra utilizada anteriormente seguido de Exception. Deve também possuir um atributo $defaultTemplates com a mensagem padrão de validação. Veja o exemplo:


namespace Botecodigital\Validation\Exceptions;

use Respect\Validation\Exceptions\ValidationException;

final class ParException extends ValidationException
{
    protected $defaultTemplates = [
        self::MODE_DEFAULT => [
            self::STANDARD => 'O valor não é par.',
        ],
    ];
}

Com as duas classe prontas devemos registrar os pacotes em que as colocamos no Respect Valiation:

Factory::setDefaultInstance(
    (new Factory())
        ->withRuleNamespace('Botecodigital\\Validation\\Rules')
        ->withExceptionNamespace('Botecodigital\\Validation\\Exceptions')
);

Agora basta utilizar a nova regra com o mesmo nome da classe de regra mas com o primeiro caractere em minúsculo, no nosso caso Validator::par():

Factory::setDefaultInstance(
    (new Factory())
        ->withRuleNamespace('Botecodigital\\Validation\\Rules')
        ->withExceptionNamespace('Botecodigital\\Validation\\Exceptions')
);

$inputNum = "3";
$validator = Validator::par();

try{
    $validator->assert($inputNum);
    echo "Válido!".PHP_EOL;
}catch(NestedValidationException $e){
    echo "Inválido!".PHP_EOL;

    foreach ($e->getMessages()  as $key => $value) {
        echo $key. ' -> '. $value . PHP_EOL;
    }
}

Bom pessoal essa foi uma apresentação curta da biblioteca Respect Validation. Espero que ajude T++.