Validando entradas no PHP com Respect Validation

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çãodate
.datetime
: Valida se a entrada é uma date/time válido. O formato segue o da funçãodate
.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++.