Validação de dados
1Introdução
O Temma oferece um sistema abrangente de validação de dados, tanto para entrada (dados recebidos pelo servidor) quanto para saída (dados enviados ao navegador).
Três níveis de uso estão disponíveis:
- Atributos PHP (abordagem declarativa): os atributos Check\* permitem validar dados diretamente nos controladores e ações, sem escrever código de validação. Essa é a abordagem mais simples e mais comum.
- Métodos do objeto Request (abordagem programática): para os casos em que os atributos não são suficientes, os métodos validate*() do objeto Request oferecem controle total a partir do código do controlador.
- O objeto DataFilter (baixo nível): o bloco de construção fundamental sobre o qual todo o sistema é construído. Pode ser usado em qualquer lugar do seu código para validar qualquer dado.
O fio condutor de todo o sistema é o contrato de validação: uma definição que descreve o formato de dados esperado.
2Contratos de validação
2.1Formato do contrato
Um contrato de validação pode ser escrito de duas formas:
Formato string (os parâmetros são separados por ponto e vírgula):
// um número inteiro entre 0 e 100
'int; min: 0; max: 100'
// uma string de 2 a 50 caracteres
'string; minLen: 2; maxLen: 50'
// um endereço de e-mail correspondente a um padrão
'email; mask: @mydomain\.com$'
Formato array (cada parâmetro é uma chave do array):
// um número inteiro entre 0 e 100
['type' => 'int', 'min' => 0, 'max' => 100]
// um array associativo com chaves tipadas
[
'type' => 'assoc',
'keys' => [
'id' => 'int',
'name' => 'string; minLen: 2',
'email' => 'email',
]
]
Os dois formatos são intercambiáveis e podem ser usados em qualquer lugar onde um contrato seja esperado. Para mais detalhes, veja a documentação do DataFilter.
2.2Modo estrito e não estrito
Por padrão, a validação é não estrita: os dados são convertidos quando possível (uma string "42" é aceita para um contrato int), valores fora do intervalo são ajustados (um número grande demais é limitado ao máximo), e strings muito longas são truncadas.
No modo estrito, nenhuma conversão é realizada: os dados devem corresponder exatamente ao tipo esperado, e qualquer violação de limite causa um erro.
O modo pode ser controlado de várias formas:
- Globalmente, por meio do parâmetro $strict dos atributos ou métodos de validação.
- Por contrato, prefixando o tipo: = para forçar o modo estrito (ex.: '=int'), ~ para forçar o modo não estrito (ex.: '~string').
2.3Tipos disponíveis
O Temma oferece uma ampla gama de tipos de validação:
- Escalares: null, bool, true, false, int, float, string
- Texto e identificadores: email, url, slug, uuid, color, phone
- Datas e horários: date, time, datetime
- Rede: ip, ipv4, ipv6, mac, port
- Códigos: isbn, ean, hash (e os aliases md5, sha1, sha256, sha512)
- Geografia: geo
- Estruturas: enum, list, assoc
- Dados binários: json, binary, base64
Os tipos podem ser combinados com o operador pipe: 'null|int|string', e tornados anuláveis com o prefixo ?: '?bool'.
Para todos os detalhes de cada tipo e seus parâmetros, veja a documentação do DataFilter.
2.4Parâmetros universais
Vários parâmetros estão disponíveis para todos (ou a maioria) os tipos:
- default: valor padrão caso os dados sejam inválidos.
- min / max: valores mínimo e máximo (números, datas, coordenadas).
- minLen / maxLen: comprimento mínimo e máximo (strings, listas). maxLen aceita as unidades K, M, G.
- mask: expressão regular para validação (strings, e-mails, URLs).
- values: valores aceitos (para enum).
- contract: contrato de validação para os elementos de um list ou json.
- keys: chaves esperadas em um array assoc.
- mime: tipos MIME permitidos (binary, base64).
- charset: codificação de caracteres.
- format / inFormat / outFormat: formatos de data/hora.
2.5Chaves opcionais e curinga
Em contratos do tipo assoc (e em parâmetros GET/POST), as chaves podem ser marcadas como opcionais com o sufixo ?:
[
'name' => 'string', // obrigatório
'firstname?' => 'string', // opcional
'age' => 'int; min: 0', // obrigatório
]
O curinga ... permite aceitar dados adicionais não definidos no contrato. Ele pode, opcionalmente, impor um tipo:
// aceita todas as chaves adicionais como estão
['name' => 'string', '...']
// aceita chaves adicionais, mas elas devem ser inteiros
['name' => 'string', '...' => 'int']
Sem o curinga, no modo não estrito as chaves não definidas são removidas; no modo estrito, elas causam um erro.
3Contratos nomeados (configuração)
Para evitar a duplicação de contratos de validação, você pode defini-los uma única vez no arquivo de configuração etc/temma.php, na chave validationTypes:
<?php
return [
'validationTypes' => [
// contrato simples (enumeração)
'sitecolor' => 'enum; values: orange, yellow, lime, green',
// contrato complexo (array associativo)
'user' => [
'type' => 'assoc',
'keys' => [
'id?' => 'int',
'login' => 'string',
'email' => 'email',
],
],
// objeto de validação personalizado
'category' => '\App\Validations\CategoryValidator',
],
];
Esses contratos nomeados podem então ser usados em qualquer lugar onde um contrato seja esperado (atributos Check\*, métodos validate*(), objeto DataFilter), simplesmente passando seu nome como uma string:
// em um atributo
#[TµCheckPost('user')]
// em um método de validação
$this->_request->validateInput('user');
// no DataFilter
\Temma\Utils\DataFilter::process($data, 'user');
4Validação de entrada (atributos Check)
4.1Os cinco atributos
Os atributos Check\* permitem validar os dados recebidos de forma declarativa, diretamente nos controladores e ações:
- \Temma\Attributes\Check\Params: parâmetros da URL.
- \Temma\Attributes\Check\Get: parâmetros GET.
- \Temma\Attributes\Check\Post: parâmetros POST.
- \Temma\Attributes\Check\Files: arquivos enviados.
- \Temma\Attributes\Check\Payload: corpo da requisição (JSON, base64, binário).
Exemplo: validar que um formulário POST contém um e-mail e um nome:
use \Temma\Attributes\Check\Post as TµCheckPost;
class User extends \Temma\Web\Controller {
// em caso de erro, redireciona para o referer HTTP;
// os dados POST recebidos são copiados para a
// variável flash padrão '__form'
#[TµCheckPost([
'email' => 'email',
'name' => 'string; minLen: 2',
])]
public function create() {
// os dados são válidos, podemos usá-los
}
}
Exemplo: validar os parâmetros de URL de uma ação:
use \Temma\Attributes\Check\Params as TµCheckParams;
class Article extends \Temma\Web\Controller {
// espera um número inteiro positivo e uma string (slug)
#[TµCheckParams(['int; min: 1', 'slug'])]
public function show(int $id, string $slug) {
// ...
}
}
Exemplo: validar um payload JSON:
use \Temma\Attributes\Check\Payload as TµCheckPayload;
class Api extends \Temma\Web\Controller {
// espera um fluxo JSON contendo um array associativo;
// em caso de erro, redireciona para '/api/error';
// a variável flash '__apiErr' é definida como true
#[TµCheckPayload(
[
'type' => 'json',
'contract' => [
'type' => 'assoc',
'keys' => [
'id' => 'int',
'role' => 'enum; values: user, admin',
],
],
],
redirect: '/api/error',
flashVar: 'apiErr',
)]
public function update() {
// ...
}
}
Exemplo: recuperar os dados validados em uma variável de template:
use \Temma\Attributes\Check\Payload as TµCheckPayload;
class Api extends \Temma\Web\Controller {
// espera um fluxo JSON; os dados validados são armazenados
// na variável de template 'jsonData'
#[TµCheckPayload(
['type' => 'json', 'contract' => ['id' => 'int', 'name' => 'string']],
dataVar: 'jsonData',
)]
public function import() {
// $this['jsonData'] contém os dados JSON decodificados e validados
}
}
Para todos os detalhes e mais exemplos, veja a documentação dos atributos Check.
4.2Parâmetros comuns
Todos os atributos Check\* (exceto Output) aceitam parâmetros comuns para controlar o comportamento em caso de falha na validação:
- $strict: (bool) ativa o modo de validação estrita (false por padrão).
- $redirect: (string) URL de redirecionamento em caso de erro.
- $redirectVar: (string) nome da variável de template que contém a URL de redirecionamento.
- $redirectReferer: (bool) true por padrão. Se true e $redirect e $redirectVar estiverem vazios, o cabeçalho HTTP REFERER é usado como URL de redirecionamento.
- $flashVar: (string) nome da variável flash que conterá os dados recebidos no redirecionamento ('form' por padrão, tornando os dados acessíveis em __form). Defina como null para desativar.
- $dataVar: (?string) nome da variável de template que conterá os dados validados/filtrados pelo DataFilter. Permite recuperar diretamente os dados processados, evitando operações redundantes. null por padrão. Não disponível para Files.
4.3Prioridade de redirecionamento
Quando os dados são inválidos, a URL de redirecionamento é determinada na seguinte ordem:
- O parâmetro $redirect, se definido.
- O conteúdo da variável de template especificada por $redirectVar, se existir e não estiver vazia.
- O cabeçalho HTTP Referer, se $redirectReferer for true e o cabeçalho estiver presente.
- A chave redirect da configuração estendida x-security em etc/temma.php.
Se nenhuma URL for encontrada, um erro HTTP 403 (Forbidden) é retornado.
5Validação de saída (atributo Output)
O atributo \Temma\Attributes\Check\Output define um contrato de validação para os dados de saída (variáveis de template). Esse contrato pode ser usado pela view para validar os dados antes de enviá-los ao navegador.
use \Temma\Attributes\Check\Output as TµCheckOutput;
class User extends \Temma\Web\Controller {
// as variáveis de template 'name' e 'email' devem estar presentes
// e válidas; 'balance' é opcional
#[TµCheckOutput([
'name' => 'string',
'email' => 'email',
'balance?' => 'float',
])]
public function show() {
// ...
}
}
Diferente dos demais atributos Check\*, Output não suporta parâmetros de redirecionamento ($redirect, $redirectVar, $flashVar).
6Validação programática (Request)
Para os casos em que os atributos não são suficientes (lógica condicional, contratos dinâmicos etc.), o objeto Request expõe quatro métodos de validação. Em caso de falha, eles lançam uma exceção \Temma\Exceptions\Application.
6.1validateParams
Valida os parâmetros recebidos na URL. O contrato é uma lista ordenada (um contrato por parâmetro). Um parâmetro opcional &$output permite recuperar os dados validados/filtrados por referência:
// o primeiro parâmetro deve ser um inteiro >= 1,
// o segundo uma string slug
$this->_request->validateParams(['int; min: 1', 'slug']);
// o primeiro deve ser um inteiro,
// o restante é aceito como está
$this->_request->validateParams(['int', '...']);
// usando um contrato nomeado
$this->_request->validateParams('deleteUserParameters');
// com recuperação dos dados filtrados
$output = null;
$this->_request->validateParams(['int; min: 1', 'slug'], output: $output);
6.2validateInput
Valida os parâmetros GET e/ou POST. O contrato é um array associativo (chave = nome do parâmetro). Um parâmetro opcional &$output permite recuperar os dados validados/filtrados por referência:
// valida os parâmetros 'name' e 'email' (GET ou POST)
$this->_request->validateInput([
'name' => 'string; minLen: 2',
'email' => 'email',
]);
// valida apenas os parâmetros POST, em modo estrito;
// 'firstname' é opcional, 'age' é forçado ao modo não estrito
$this->_request->validateInput(
[
'name' => 'string',
'firstname?' => 'string',
'age' => '~int',
],
'POST',
true
);
// usando um contrato nomeado
$this->_request->validateInput('user');
// com recuperação dos dados filtrados
$output = null;
$this->_request->validateInput(
['name' => 'string', 'email' => 'email'],
'POST',
output: $output
);
6.3validatePayload
Valida o corpo da requisição (payload). Um parâmetro opcional &$output permite recuperar os dados validados/filtrados por referência:
// fluxo JSON contendo uma lista de inteiros
$this->_request->validatePayload([
'type' => 'json',
'contract' => 'list; contract: int',
]);
// imagem codificada em base64 (GIF ou PNG)
$this->_request->validatePayload('base64; mime: image/gif, image/png');
// usando um contrato nomeado
$this->_request->validatePayload('avatar');
// com recuperação de metadados
$output = null;
$this->_request->validatePayload('binary; mime: image', output: $output);
// $output contém ['binary' => ..., 'mime' => ..., 'charset' => ...]
6.4validateFiles
Valida os arquivos enviados. O contrato é um array associativo (chave = nome do campo de arquivo):
// um arquivo JSON obrigatório e uma imagem opcional
$this->_request->validateFiles([
'definition' => 'json',
'avatar?' => 'binary; mime: image',
]);
// um arquivo JSON e qualquer número de arquivos PDF
$this->_request->validateFiles([
'count' => 'json; contract: int',
'...' => 'binary; mime: application/pdf',
]);
7Validação programática (Response)
O objeto Response permite gerenciar um contrato de validação para os dados de saída:
// define um contrato de validação de saída
$this->_response->setValidationContract([
'name' => 'string',
'email' => 'email',
]);
// usa um contrato nomeado
$this->_response->setValidationContract('userData');
// recupera o contrato definido
$contract = $this->_response->getValidationContract();
// remove o contrato
$this->_response->setValidationContract(null);
O contrato definido pode ser usado pela view para validar as variáveis de template antes de enviá-las ao navegador.