Procesar formularios HTML con PHP de forma segura significa validar, limpiar y verificar cada dato antes de usarlo o guardarlo en tu base de datos.
El formulario que nadie revisó
Imagina que lanzas un sistema de registro para una tienda en línea. Los primeros días todo funciona bien. Luego alguien escribe <script>alert('hackeado')</script> en el campo "nombre" y tu página empieza a mostrar ventanas emergentes. Otro usuario envía el formulario vacío y MySQL lanza un error que expone el nombre de tu tabla. Esto le ha pasado a docenas de proyectos mexicanos por no revisar los datos antes de procesarlos.
La buena noticia es que PHP tiene todas las herramientas para evitarlo. Solo necesitas aplicarlas en el orden correcto.
El sistema RECIBIR-VALIDAR-LIMPIAR
Cada formulario que proceses en PHP debe pasar por tres fases obligatorias antes de tocar la base de datos.
Fase 1 — RECIBIR: Obtén el dato desde $_POST o $_GET.
Fase 2 — VALIDAR: Verifica que el dato cumple las reglas de tu negocio (no vacío, formato correcto, longitud aceptable).
Fase 3 — LIMPIAR: Elimina espacios extra y caracteres peligrosos.
Si alguna fase falla, detienes el proceso y le explicas al usuario qué corregir. Solo cuando las tres fases pasan sin errores, procedes al INSERT o UPDATE.
El formulario HTML de base
Empecemos con un formulario de contacto simple, como el que usaría una empresa como Liverpool para recibir solicitudes de empleo:
<form method="POST" action="procesar.php">
<label>Nombre completo</label>
<input type="text" name="nombre" maxlength="100">
<label>Correo electrónico</label>
<input type="email" name="correo" maxlength="150">
<label>Mensaje</label>
<textarea name="mensaje" maxlength="500"></textarea>
<button type="submit">Enviar</button>
</form>
El atributo method="POST" envía los datos de forma oculta. El action="procesar.php" indica a qué archivo PHP llegan esos datos.
Nota importante: El
maxlengthen HTML ayuda, pero no es suficiente. Cualquier persona puede desactivarlo desde el navegador. La validación real siempre ocurre en PHP.
El archivo procesar.php paso a paso
Aquí está el código completo con las tres fases aplicadas:
<?php
// Fase 1: RECIBIR
$nombre = $_POST['nombre'] ?? '';
$correo = $_POST['correo'] ?? '';
$mensaje = $_POST['mensaje'] ?? '';
// Fase 2: LIMPIAR (primero limpia, luego valida)
$nombre = trim($nombre);
$correo = trim($correo);
$mensaje = trim($mensaje);
// Fase 3: VALIDAR
$errores = [];
if (empty($nombre)) {
$errores[] = 'El nombre es obligatorio.';
} elseif (strlen($nombre) > 100) {
$errores[] = 'El nombre no puede tener más de 100 caracteres.';
}
if (empty($correo)) {
$errores[] = 'El correo es obligatorio.';
} elseif (!filter_var($correo, FILTER_VALIDATE_EMAIL)) {
$errores[] = 'El formato del correo no es válido.';
}
if (empty($mensaje)) {
$errores[] = 'El mensaje es obligatorio.';
} elseif (strlen($mensaje) > 500) {
$errores[] = 'El mensaje no puede tener más de 500 caracteres.';
}
// Si hay errores, detenemos todo
if (!empty($errores)) {
foreach ($errores as $e) {
echo "<p style='color:red'>" . htmlspecialchars($e) . "</p>";
}
exit;
}
// Solo aquí conectamos y guardamos
echo "<p>Datos válidos. Listo para guardar.</p>";
?>
¿Por qué limpiamos antes de validar?
Si el usuario escribe " juan " con espacios, empty() lo detecta como no vacío. Pero en tu base de datos quedaría " juan " con espacios inútiles. Con trim() primero, limpias el dato y luego validas lo que realmente importa.
Validaciones más comunes en proyectos mexicanos
Validar un número de teléfono
En México los celulares tienen 10 dígitos. Puedes validarlo así:
$telefono = trim($_POST['telefono'] ?? '');
if (!preg_match('/^[0-9]{10}$/', $telefono)) {
$errores[] = 'El teléfono debe tener exactamente 10 dígitos.';
}
preg_match revisa el patrón: solo números del 0 al 9, exactamente 10 veces.
Validar un monto en pesos
Si tienes un formulario de cotización, como el que usaría FEMSA para registrar proveedores, necesitas validar que el monto sea un número positivo:
$monto = trim($_POST['monto'] ?? '');
if (!is_numeric($monto) || $monto <= 0) {
$errores[] = 'El monto debe ser un número mayor a cero.';
}
Si el usuario ingresa "18500", PHP lo procesa correctamente. En pantalla lo mostrarías formateado:
echo "Monto recibido: $" . number_format($monto, 0) ;
// Resultado: Monto recibido: $18,500
Validar una opción de lista (select)
Cuando el usuario elige una opción de un <select>, no basta con recibirla. Debes verificar que el valor enviado sea uno de los valores permitidos:
$estados_validos = ['CDMX', 'Jalisco', 'Nuevo León', 'Puebla'];
$estado = trim($_POST['estado'] ?? '');
if (!in_array($estado, $estados_validos)) {
$errores[] = 'Selecciona un estado válido.';
}
Esto evita que alguien envíe un estado inventado o un valor malicioso en el campo.
Conectar validación con la base de datos
Una vez que pasas las tres fases sin errores, conectas y guardas. Aquí combinas lo aprendido en lecciones anteriores:
<?php
// (Código de validación anterior aquí...)
// Solo si $errores está vacío llegamos aquí
try {
$pdo = new PDO(
'mysql:host=localhost;dbname=mi_base;charset=utf8',
'root',
''
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'INSERT INTO contactos (nombre, correo, mensaje) VALUES (?, ?, ?)';
$stmt = $pdo->prepare($sql);
$stmt->execute([$nombre, $correo, $mensaje]);
if ($stmt->rowCount() === 1) {
echo "<p>Tu mensaje fue enviado correctamente.</p>";
}
} catch (PDOException $e) {
echo "<p>Ocurrió un error al guardar. Intenta más tarde.</p>";
}
?>
Nota que nunca concatenas $nombre directamente en el SQL. Usas ? y pasas los valores en el arreglo de execute(). Eso previene la inyección SQL.
Errores comunes que abren vulnerabilidades
Error 1: Confiar en el maxlength del HTML
Un atacante puede abrir las herramientas del navegador, borrar el atributo maxlength y enviar mil caracteres. Si no validas en PHP, esos datos llegan completos a tu base de datos.
Error 2: Imprimir errores de MySQL en pantalla
Nunca hagas esto en producción:
// MAL — no hagas esto
echo $e->getMessage();
Eso puede revelar el nombre de tu base de datos, tabla o estructura. Muestra siempre un mensaje genérico al usuario y guarda el error real en un log privado.
Error 3: Olvidar htmlspecialchars() al mostrar datos del usuario
Si alguien escribió <script> en el campo nombre y tú lo imprimes sin filtrar, ese script se ejecuta en el navegador del siguiente visitante. Siempre usa:
echo htmlspecialchars($nombre, ENT_QUOTES, 'UTF-8');
Error 4: Procesar el formulario sin verificar el método HTTP
Si alguien accede directamente a procesar.php sin enviar el formulario, $_POST estará vacío y PHP puede generar errores inesperados. Protégete así:
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: formulario.php');
exit;
}
Esta verificación va al inicio del archivo, antes de cualquier otra cosa.
Error 5: No sanitizar campos numéricos
Si esperas un número entero, conviértelo explícitamente:
$edad = (int) ($_POST['edad'] ?? 0);
Así, aunque el usuario escriba "25abc", PHP guarda solo 25.
Una regla para recordar siempre
Cada campo del formulario es una puerta de entrada a tu sistema. Tú decides si esa puerta tiene cerradura o no.
La seguridad de un formulario no depende del HTML que el usuario ve, sino del PHP que el usuario nunca ve.