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++.