Validación de datos
1Introducción
Temma ofrece un sistema completo de validación de datos, tanto para la entrada (datos recibidos por el servidor) como para la salida (datos enviados al navegador).
Hay disponibles tres niveles de uso:
- Atributos PHP (enfoque declarativo): los atributos Check\* te permiten validar datos directamente en los controladores y las acciones, sin escribir código de validación. Este es el enfoque más simple y más habitual.
- Métodos del objeto Request (enfoque programático): para los casos en los que los atributos no son suficientes, los métodos validate*() del objeto Request ofrecen un control total desde el código del controlador.
- El objeto DataFilter (bajo nivel): el bloque fundamental sobre el que se construye todo el sistema. Se puede usar en cualquier parte de tu código para validar cualquier dato.
El hilo conductor de todo el sistema es el contrato de validación: una definición que describe el formato de datos esperado.
2Contratos de validación
2.1Formato del contrato
Un contrato de validación se puede escribir de dos formas:
Formato de cadena (los parámetros se separan con punto y coma):
// un entero entre 0 y 100
'int; min: 0; max: 100'
// una cadena de 2 a 50 caracteres
'string; minLen: 2; maxLen: 50'
// una dirección de correo electrónico que coincide con un patrón
'email; mask: @mydomain\.com$'
Formato de array (cada parámetro es una clave del array):
// un entero entre 0 y 100
['type' => 'int', 'min' => 0, 'max' => 100]
// un array asociativo con claves tipadas
[
'type' => 'assoc',
'keys' => [
'id' => 'int',
'name' => 'string; minLen: 2',
'email' => 'email',
]
]
Ambos formatos son intercambiables y se pueden usar en cualquier lugar donde se espere un contrato. Para más detalles, consulta la documentación de DataFilter.
2.2Modo estricto y no estricto
Por defecto, la validación es no estricta: los datos se convierten cuando es posible (una cadena "42" se acepta para un contrato int), los valores fuera de rango se ajustan (un número demasiado grande se recorta al máximo), y las cadenas demasiado largas se truncan.
En modo estricto, no se realiza ninguna conversión: los datos deben coincidir exactamente con el tipo esperado, y cualquier incumplimiento de los límites provoca un error.
El modo se puede controlar de varias formas:
- De forma global, mediante el parámetro $strict de los atributos o de los métodos de validación.
- Por contrato, anteponiendo un prefijo al tipo: = para forzar el modo estricto (por ejemplo, '=int'), ~ para forzar el modo no estricto (por ejemplo, '~string').
2.3Tipos disponibles
Temma ofrece una amplia gama de tipos de validación:
- Escalares: null, bool, true, false, int, float, string
- Texto e identificadores: email, url, slug, uuid, color, phone
- Fechas y horas: date, time, datetime
- Red: ip, ipv4, ipv6, mac, port
- Códigos: isbn, ean, hash (y los alias md5, sha1, sha256, sha512)
- Geografía: geo
- Estructuras: enum, list, assoc
- Datos binarios: json, binary, base64
Los tipos se pueden combinar con el operador pipe: 'null|int|string', y se pueden hacer nulables con el prefijo ?: '?bool'.
Para conocer todos los detalles de cada tipo y sus parámetros, consulta la documentación de DataFilter.
2.4Parámetros universales
Varios parámetros están disponibles para todos los tipos (o la mayoría):
- default: valor de reserva si el dato no es válido.
- min / max: valores mínimo y máximo (números, fechas, coordenadas).
- minLen / maxLen: longitud mínima y máxima (cadenas, listas). maxLen admite las unidades K, M, G.
- mask: expresión regular de validación (cadenas, correos electrónicos, URLs).
- values: valores aceptados (para enum).
- contract: contrato de validación para los elementos de un list o un json.
- keys: claves esperadas en un array assoc.
- mime: tipos MIME permitidos (binary, base64).
- charset: codificación de caracteres.
- format / inFormat / outFormat: formatos de fecha/hora.
2.5Claves opcionales y comodín
En los contratos de tipo assoc (y en los parámetros GET/POST), las claves se pueden marcar como opcionales con el sufijo ?:
[
'name' => 'string', // obligatorio
'firstname?' => 'string', // opcional
'age' => 'int; min: 0', // obligatorio
]
Una entrada con una clave numérica y un valor de tipo cadena es una forma abreviada para un nombre de clave sin contrato de validación: el dato debe estar presente, pero su contenido se acepta tal cual. Esta forma abreviada está disponible en los contratos GET/POST (validateInput(), Check\Get, Check\Post) y en los contratos de archivos (validateFiles(), Check\Files); no se aplica a los contratos list, donde las claves numéricas conservan su significado posicional.
// 'name' debe estar presente (contenido libre), 'age' debe ser un entero
['name', 'age' => 'int']
// equivalente a
['name' => null, 'age' => 'int']
El comodín ... permite aceptar datos adicionales no definidos en el contrato. Opcionalmente, puede imponer un tipo:
// acepta todas las claves adicionales tal cual
['name' => 'string', '...']
// acepta claves adicionales, pero deben ser enteros
['name' => 'string', '...' => 'int']
Sin el comodín, en modo no estricto las claves no definidas se eliminan; en modo estricto, provocan un error.
3Contratos con nombre (configuración)
Para evitar duplicar los contratos de validación, puedes definirlos una sola vez en el archivo de configuración etc/temma.php, bajo la clave validationTypes:
<?php
return [
'validationTypes' => [
// contrato simple (enumeración)
'sitecolor' => 'enum; values: orange, yellow, lime, green',
// contrato complejo (array asociativo)
'user' => [
'type' => 'assoc',
'keys' => [
'id?' => 'int',
'login' => 'string',
'email' => 'email',
],
],
// objeto de validación personalizado
'category' => '\App\Validations\CategoryValidator',
],
];
Estos contratos con nombre se pueden usar después en cualquier lugar donde se espere un contrato (atributos Check\*, métodos validate*(), objeto DataFilter), simplemente pasando su nombre como una cadena:
// en un atributo
#[TµCheckPost('user')]
// en un método de validación
$this->_request->validateInput('user');
// en DataFilter
\Temma\Utils\DataFilter::process($data, 'user');
4Validación de entrada (atributos Check)
4.1Los cinco atributos
Los atributos Check\* te permiten validar los datos entrantes de forma declarativa, directamente en los controladores y las acciones:
- \Temma\Attributes\Check\Params: parámetros de la URL.
- \Temma\Attributes\Check\Get: parámetros GET.
- \Temma\Attributes\Check\Post: parámetros POST.
- \Temma\Attributes\Check\Files: archivos subidos.
- \Temma\Attributes\Check\Payload: cuerpo de la petición (JSON, base64, binario).
Ejemplo: validar que un formulario POST contiene un correo electrónico y un nombre:
use \Temma\Attributes\Check\Post as TµCheckPost;
class User extends \Temma\Web\Controller {
// en caso de error, redirige al referer HTTP;
// los datos POST recibidos se copian en la variable
// flash por defecto '__form'
#[TµCheckPost([
'email' => 'email',
'name' => 'string; minLen: 2',
])]
public function create() {
// los datos son válidos, podemos usarlos
}
}
Ejemplo: validar los parámetros de URL de una acción:
use \Temma\Attributes\Check\Params as TµCheckParams;
class Article extends \Temma\Web\Controller {
// espera un entero positivo y una cadena (slug)
#[TµCheckParams(['int; min: 1', 'slug'])]
public function show(int $id, string $slug) {
// ...
}
}
Ejemplo: validar un payload JSON:
use \Temma\Attributes\Check\Payload as TµCheckPayload;
class Api extends \Temma\Web\Controller {
// espera un flujo JSON que contiene un array asociativo;
// en caso de error, redirige a '/api/error';
// la variable flash '__apiErr' se establece en true
#[TµCheckPayload(
[
'type' => 'json',
'contract' => [
'type' => 'assoc',
'keys' => [
'id' => 'int',
'role' => 'enum; values: user, admin',
],
],
],
redirect: '/api/error',
flashVar: 'apiErr',
)]
public function update() {
// ...
}
}
Ejemplo: recuperar los datos validados en una variable de template:
use \Temma\Attributes\Check\Payload as TµCheckPayload;
class Api extends \Temma\Web\Controller {
// espera un flujo JSON; los datos validados se almacenan
// en la variable de template 'jsonData'
#[TµCheckPayload(
['type' => 'json', 'contract' => ['id' => 'int', 'name' => 'string']],
dataVar: 'jsonData',
)]
public function import() {
// $this['jsonData'] contiene los datos JSON decodificados y validados
}
}
Para conocer todos los detalles y más ejemplos, consulta la documentación de los atributos Check.
4.2Parámetros comunes
Todos los atributos Check\* (excepto Output) aceptan parámetros comunes para controlar el comportamiento en caso de fallo de la validación:
- $strict: (bool) activa el modo de validación estricta (false por defecto).
- $redirect: (string) URL de redirección en caso de error.
- $redirectVar: (string) nombre de la variable de template que contiene la URL de redirección.
- $redirectReferer: (bool) true por defecto. Si true y $redirect y $redirectVar están vacíos, el encabezado HTTP REFERER se usa como URL de redirección.
- $flashVar: (string) nombre de la variable flash que contendrá los datos recibidos en la redirección ('form' por defecto, lo que hace que los datos sean accesibles en __form). Se puede poner a null para desactivarlo.
- $dataVar: (?string) nombre de la variable de template que contendrá los datos validados/filtrados por el DataFilter. Permite recuperar directamente los datos procesados, evitando operaciones redundantes. null por defecto. No disponible para Files.
4.3Prioridad de redirección
Cuando los datos no son válidos, la URL de redirección se determina en el siguiente orden:
- El parámetro $redirect, si está definido.
- El contenido de la variable de template indicada por $redirectVar, si existe y no está vacía.
- El encabezado HTTP Referer, si $redirectReferer es true y el encabezado está presente.
- La clave redirect de la configuración extendida x-security en etc/temma.php.
Si no se encuentra ninguna URL, se devuelve un error HTTP 403 (Forbidden).
5Validación de salida (atributo Output)
El atributo \Temma\Attributes\Check\Output define un contrato de validación para los datos de salida (variables de template). Este contrato puede ser utilizado por la vista para validar los datos antes de enviarlos al navegador.
use \Temma\Attributes\Check\Output as TµCheckOutput;
class User extends \Temma\Web\Controller {
// las variables de template 'name' y 'email' deben estar presentes
// y ser válidas; 'balance' es opcional
#[TµCheckOutput([
'name' => 'string',
'email' => 'email',
'balance?' => 'float',
])]
public function show() {
// ...
}
}
A diferencia de los demás atributos Check\*, Output no admite los parámetros de redirección ($redirect, $redirectVar, $flashVar).
6Validación programática (Request)
Para los casos en los que los atributos no son suficientes (lógica condicional, contratos dinámicos, etc.), el objeto Request expone cuatro métodos de validación. En caso de fallo, lanzan una excepción \Temma\Exceptions\Application.
6.1validateParams
Valida los parámetros recibidos en la URL. El contrato es una lista ordenada (un contrato por parámetro). Un parámetro opcional &$output permite recuperar por referencia los datos validados/filtrados:
// el primer parámetro debe ser un entero >= 1,
// el segundo una cadena slug
$this->_request->validateParams(['int; min: 1', 'slug']);
// el primero debe ser un entero,
// el resto se acepta tal cual
$this->_request->validateParams(['int', '...']);
// uso de un contrato con nombre
$this->_request->validateParams('deleteUserParameters');
// con recuperación de los datos filtrados
$output = null;
$this->_request->validateParams(['int; min: 1', 'slug'], output: $output);
6.2validateInput
Valida los parámetros GET y/o POST. El contrato es un array asociativo (clave = nombre del parámetro). Un parámetro opcional &$output permite recuperar por referencia los datos validados/filtrados:
// valida los parámetros 'name' y 'email' (GET o POST)
$this->_request->validateInput([
'name' => 'string; minLen: 2',
'email' => 'email',
]);
// 'comment' debe estar presente (contenido libre), 'age' debe ser un entero
$this->_request->validateInput(['comment', 'age' => 'int']);
// valida solo los parámetros POST, en modo estricto;
// 'firstname' es opcional, 'age' se fuerza a modo no estricto
$this->_request->validateInput(
[
'name' => 'string',
'firstname?' => 'string',
'age' => '~int',
],
'POST',
true
);
// uso de un contrato con nombre
$this->_request->validateInput('user');
// con recuperación de los datos filtrados
$output = null;
$this->_request->validateInput(
['name' => 'string', 'email' => 'email'],
'POST',
output: $output
);
6.3validatePayload
Valida el cuerpo de la petición (payload). Un parámetro opcional &$output permite recuperar por referencia los datos validados/filtrados:
// flujo JSON que contiene una lista de enteros
$this->_request->validatePayload([
'type' => 'json',
'contract' => 'list; contract: int',
]);
// imagen codificada en base64 (GIF o PNG)
$this->_request->validatePayload('base64; mime: image/gif, image/png');
// uso de un contrato con nombre
$this->_request->validatePayload('avatar');
// con recuperación de metadatos
$output = null;
$this->_request->validatePayload('binary; mime: image', output: $output);
// $output contiene ['binary' => ..., 'mime' => ..., 'charset' => ...]
6.4validateFiles
Valida los archivos subidos. El contrato es un array asociativo (clave = nombre del campo de archivo):
// un archivo JSON obligatorio y una imagen opcional
$this->_request->validateFiles([
'definition' => 'json',
'avatar?' => 'binary; mime: image',
]);
// un archivo "id_card" obligatorio, solo presencia (contenido no leído)
$this->_request->validateFiles(['id_card']);
// un archivo JSON y cualquier número de archivos PDF
$this->_request->validateFiles([
'count' => 'json; contract: int',
'...' => 'binary; mime: application/pdf',
]);
7Validación programática (Response)
El objeto Response te permite gestionar un contrato de validación para los datos de salida:
// define un contrato de validación de salida
$this->_response->setValidationContract([
'name' => 'string',
'email' => 'email',
]);
// usa un contrato con nombre
$this->_response->setValidationContract('userData');
// recupera el contrato definido
$contract = $this->_response->getValidationContract();
// elimina el contrato
$this->_response->setValidationContract(null);
El contrato definido podrá ser utilizado por la vista para validar las variables de template antes de enviarlas al navegador.