certmundo.
es‑mx

6 min de lectura

¿Cómo procesar formularios HTML con PHP de forma segura?

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 maxlength en 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.

Puntos clave

  • Aplica siempre el sistema RECIBIR-VALIDAR-LIMPIAR en ese orden: primero `trim()` para limpiar espacios, luego verifica con `empty()`, `strlen()`, `filter_var()` o `preg_match()` según el tipo de dato.
  • Usa `filter_var($correo, FILTER_VALIDATE_EMAIL)` para correos, `preg_match('/^[0-9]{10}$/', $tel)` para teléfonos mexicanos y `in_array()` para validar opciones de listas desplegables.
  • Verifica que el método HTTP sea POST al inicio de `procesar.php` con `$_SERVER['REQUEST_METHOD']`. Esto evita errores cuando alguien accede directamente al archivo.
  • Nunca muestres el mensaje de error de PDOException al usuario en producción. Muestra un texto genérico y registra el error real en un log privado.
  • La validación en el HTML (atributo `maxlength`, `type='email'`) es solo una ayuda visual. La validación real y obligatoria siempre ocurre en PHP, del lado del servidor.

Comparte esta lección:

¿Cómo procesar formularios HTML con PHP de forma segura? | PHP y MySQL: Desarrollo Web con Base de Datos | Certmundo