📝 2.3 · PHP + HTML: formularios, GET, POST y superglobales

⏱ 6h 00min ⚡ 100 XP 🏅 Practicante 📖 Backend FullStack
"Un formulario sin backend es un bonito adorno. Con PHP, es la puerta de entrada a tus datos."

🎯 Objetivo del tema

Al terminar este tema serás capaz de: Al terminar este tema serás capaz de crear formularios HTML que envíen datos a un script PHP, diferenciar GET de POST, acceder a los datos del usuario con las superglobales $_GET, $_POST, $_REQUEST, validar entradas con filter_var, y proteger tu app contra inyecciones y XSS. Tu frontend y backend finalmente se HABLAN.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

🗺️ Mapa del tema

En el Tema 2.2 aprendiste PHP básico. Ahora viene la parte divertida: conectarlo con HTML. Los formularios son el punto de contacto entre el usuario y tu servidor. Cada campo que llenas, cada botón que presionas, es un dato que viaja del navegador al PHP, y una respuesta que vuelve. Hoy aprenderás cómo funciona ese viaje y cómo hacerlo seguro.

1. GET vs POST: cuándo usar cada uno

La diferencia crucial entre datos en la URL y datos en el cuerpo de la petición.

2. Los 3 superglobales: $_GET, $_POST, $_REQUEST

Cómo PHP recibe los datos del formulario.

3. El patrón POST-Redirect-GET

Evitar reenvíos accidentales al recargar la página.

4. Validación y sanitización de datos

filter_var, htmlspecialchars, y por qué NUNCA confiar en el usuario.

5. Seguridad: XSS, CSRF, SQL injection (introducción)

Los 3 ataques más comunes y cómo empezar a defenderte.

6. Sintaxis alternativa: mezclar PHP y HTML limpio

Cómo escribir PHP embebido en HTML sin morir en el intento.

2.3.1 GET vs POST: cuándo usar cada uno

Hay dos formas principales de enviar datos de un formulario al servidor: GET (datos en la URL) y POST (datos en el cuerpo de la petición). Cada uno tiene casos de uso. Equivocarse aquí puede causar problemas de seguridad o de experiencia de usuario.

📷 Imagen referencial: Comparativa: GET envía datos en la URL (visibles, cacheables, limitados). POST envía en el cuerpo (ocultos, no cacheables, sin límite).
AspectoGETPOST
Los datos van en...La URL: ?nombre=Ana&edad=25El cuerpo de la petición (invisible en URL)
Visible en la barra de direccionesSí, siempre.No.
Se puede compartir como enlaceSí: .../buscar?q=phpNo.
Se guarda en el historialSí.No.
Tiene límite de tamañoSí, ~2-8 KB según servidor.No (prácticamente ilimitado).
Es seguro para contraseñasNUNCA (queda en logs).Sí (con HTTPS).
Es idempotenteSí (repetir no cambia nada).No (cada envío crea un nuevo recurso).
Uso típicoBúsquedas, filtros, navegación.Login, registro, pagos, subir archivos.
⭐ GET = leer, POST = escribir: Regla simple: GET para LEER (no cambia nada en el servidor). POST para ESCRIBIR (crear, modificar, eliminar). Si el usuario hace clic en 'Ver perfil', es GET. Si hace clic en 'Guardar cambios', es POST. Memorízalo.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

2.3.2 Los 3 superglobales: $_GET, $_POST, $_REQUEST

PHP tiene variables 'superglobales' que están disponibles en TODAS partes, sin necesidad de 'global'. Las más importantes para formularios son $_GET, $_POST y $_REQUEST. Cada una es un array asociativo con los datos recibidos del cliente.

<?php
// index.php - mismo archivo muestra formulario y procesa

// SIEMPRE verifica si se envió el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  // $_POST: array con los datos del formulario enviado por POST
  $nombre = $_POST['nombre'] ?? '';
  $email = $_POST['email'] ?? '';
  echo "Recibido: $nombre, $email";
}

// O por URL: http://misitio.com/buscar?q=php
// $_GET: array con los parámetros de la URL
$query = $_GET['q'] ?? '';
echo "Buscando: $query";

// $_REQUEST: combina $_GET, $_POST y $_COOKIE (úsalo con cuidado)
$dato = $_REQUEST['nombre'] ?? '';

// Sanitizar SIEMPRE antes de usar
$nombre_limpio = htmlspecialchars($_POST['nombre'] ?? '', ENT_QUOTES, 'UTF-8');
echo "<p>Bienvenido, $nombre_limpio</p>";
?>

Acceso a datos del formulario en PHP

SuperglobalContieneCuándo se llena
$_GETParámetros de la URL (?clave=valor).En cualquier petición, especialmente con method='get'.
$_POSTDatos del cuerpo de la petición.Solo con method='post'.
$_REQUESTCombina $_GET, $_POST y $_COOKIE (con prioridad POST > GET > COOKIE).Cualquier petición. Úsalo con cuidado.
$_COOKIECookies del navegador.Si hay cookies enviadas por el usuario.
$_SESSIONVariables de sesión del usuario.Si hay sesión iniciada.
$_SERVERInformación del servidor y de la petición.Siempre disponible.
$_FILESArchivos subidos vía upload.Solo con enctype='multipart/form-data'.
⭐ $_REQUEST es peligroso: NUNCA uses $_REQUEST para datos críticos. Si tu formulario espera POST pero alguien hace la petición con GET, $_REQUEST no distingue y puedes tener comportamientos inesperados. Usa $_POST o $_GET explícitamente según el método.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

2.3.3 El patrón POST-Redirect-GET

Si después de procesar un POST muestras el resultado directamente, al usuario le aparece un mensaje de 'confirmación de reenvío del formulario' cada vez que recarga la página. La solución profesional: redirigir a una URL GET después del POST. Es el patrón PRG (Post-Redirect-Get).

<?php
// MAL: muestra el resultado y la URL sigue siendo procesar.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $nombre = $_POST['nombre'];
  // guardar en BD
  echo 'Registro exitoso. Bienvenido, ' . $nombre;
  // Problema: si el usuario recarga, ve '¿Reenviar formulario?'
}

// BIEN: redirige a una URL de éxito (patrón PRG)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $nombre = $_POST['nombre'];
  // guardar en BD
  header('Location: exito.php?nombre=' . urlencode($nombre));
  exit;  // SIEMPRE exit después de un header Location
}
?>

<!-- exito.php (URL limpia, GET) -->
<?php
$nombre = htmlspecialchars($_GET['nombre'] ?? 'usuario');
echo "<h1>¡Bienvenido, $nombre!</h1>";
echo "<p>Tu registro fue exitoso. Esta página se puede recargar sin problemas.</p>";
?>

Patrón PRG: POST-Redirect-GET

⭐ PRG: profesional vs amateur: El patrón PRG tiene 3 beneficios: (1) Evita reenvíos accidentales al recargar, (2) La URL final es 'compartible' (es GET, cualquiera puede visitarla), (3) El botón 'Atrás' del navegador funciona correctamente. Es la diferencia entre código amateur y profesional.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

2.3.4 Validación y sanitización de datos

REGLA DE ORO en PHP: nunca confíes en los datos del usuario. El usuario puede enviar letras donde pides números, un email mal formado, un script malicioso, o 10 MB de datos donde esperabas 10 bytes. Validar y sanitizar NO es opcional: es la base de la seguridad de tu app.

FunciónPara qué sirveEjemplo
filter_var($email, FILTER_VALIDATE_EMAIL)Valida formato de email.Retorna el email o false.
filter_var($edad, FILTER_VALIDATE_INT)Valida que sea entero.Retorna el int o false.
htmlspecialchars($texto, ENT_QUOTES, 'UTF-8')Convierte < > " ' en entidades HTML.Previene XSS al imprimir.
trim($texto)Quita espacios al inicio y final.Útil antes de validar.
strlen($texto)Longitud del string.Validar contraseñas de 8+ caracteres.
empty($variable)True si está vacía, null, '0', etc.Validar campos requeridos.
<?php
// El ejemplo completo: validación robusta de un formulario
$errores = [];
$nombre = $email = $edad = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  // 1. LEER (con operador null coalescing para evitar warnings)
  $nombre = trim($_POST['nombre'] ?? '');
  $email = trim($_POST['email'] ?? '');
  $edad = trim($_POST['edad'] ?? '');

  // 2. VALIDAR
  if (empty($nombre)) $errores[] = 'El nombre es obligatorio.';
  elseif (strlen($nombre) < 2) $errores[] = 'El nombre debe tener al menos 2 caracteres.';

  if (empty($email)) $errores[] = 'El email es obligatorio.';
  elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errores[] = 'El email no es válido.';

  if (empty($edad)) $errores[] = 'La edad es obligatoria.';
  elseif (!filter_var($edad, FILTER_VALIDATE_INT, ['options' => ['min_range' => 18, 'max_range' => 120]])) {
    $errores[] = 'La edad debe ser un número entre 18 y 120.';
  }

  // 3. Si NO hay errores, procesar
  if (empty($errores)) {
    // Aquí guardarías en la base de datos (lo verás en el Tema 2.4)
    header('Location: exito.php');
    exit;
  }
}

// 4. Si hay errores, mostrarlos Y repoblar el formulario
// El HTML va aquí abajo, con los valores en 'value="<?= htmlspecialchars($nombre) ?>"'
?>

Validación robusta de formulario en PHP

⭐ XSS: la regla #1: NUNCA imprimas datos del usuario sin sanitizar: echo $_POST['nombre']. Si el usuario escribe <script>alert('hack')</script>, tu página ejecutará el script. SIEMPRE htmlspecialchars() antes de imprimir. Es la defensa #1 contra XSS.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

2.3.5 Seguridad: XSS, CSRF, SQL injection (introducción)

Hay 3 ataques que TODO desarrollador web junior debe conocer. No vamos a profundizar (eso es tema de un curso de seguridad), pero sí vas a entender QUÉ son, CÓMO te pueden afectar, y la防御 BÁSICA contra cada uno.

AtaqueQué haceDefensa básica
XSS (Cross-Site Scripting)Inyecta JavaScript en tu página que se ejecuta en el navegador de otros usuarios.SIEMPRE htmlspecialchars() antes de imprimir datos del usuario.
CSRF (Cross-Site Request Forgery)Hacer que el usuario ejecute acciones en tu sitio sin saberlo, desde otro sitio.Tokens CSRF únicos por formulario.
SQL InjectionInyectar código SQL malicioso en consultas a la base de datos.SIEMPRE prepared statements con PDO.
⭐ htmlspecialchars = lavarse las manos: El 90% de los ataques web a juniors son XSS por no sanitizar. La defensa es UNA función: htmlspecialchars(). Memorízala y aplícala SIEMPRE antes de imprimir datos del usuario. Es el equivalente a lavarse las manos en medicina: básico pero salva vidas.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

2.3.6 Sintaxis alternativa: mezclar PHP y HTML limpio

Hay dos formas de escribir PHP: la forma tradicional con echo, y la forma moderna con sintaxis corta. La segunda es mucho más legible cuando mezclas PHP con HTML: en vez de echo '<p>$nombre</p>';, usas <p><?= $nombre ?></p>.

<?php
// FORMA TRADICIONAL (funciona pero es fea)
echo '<div class="usuario">';
echo '<h2>' . htmlspecialchars($nombre) . '</h2>';
echo '<p>Email: ' . htmlspecialchars($email) . '</p>';
echo '</div>';

// FORMA MODERNA CON SINTAXIS CORTA (recomendada)
// <?= expresion ?> es equivalente a <?php echo expresion; ?>
?>

<div class="usuario">
  <h2><?= htmlspecialchars($nombre) ?></h2>
  <p>Email: <?= htmlspecialchars($email) ?></p>
  <p>Edad: <?= htmlspecialchars($edad) ?></p>
</div>

<?php if ($usuario['activo']): ?>
  <span class="badge">Activo</span>
<?php else: ?>
  <span class="badge inactivo">Inactivo</span>
<?php endif; ?>

<!-- Sintaxis corta para foreach -->
<?php foreach ($productos as $producto): ?>
  <div class="producto">
    <h3><?= htmlspecialchars($producto['nombre']) ?></h3>
    <p>$<?= number_format($producto['precio'], 2) ?></p>
  </div>
<?php endforeach; ?>

Sintaxis corta PHP + HTML: la forma moderna

⭐ Sintaxis corta siempre: La sintaxis corta (<?= ?>, <?php if: ?>, <?php foreach: ?>) hace que tu código sea LEGIBLE. En vez de echo '<p>';, escribes <p>. En vez de cerrar PHP y abrir para cada if, usas if: ... endif;. Es lo que verás en Laravel, Symfony, y todos los frameworks modernos. Si tienes que elegir entre las dos formas, usa la moderna SIEMPRE.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

📚 Contenido ampliado

Material adicional, referencias externas verificadas y ejemplos extendidos.

🤖 AI Mission

La seguridad se construye pensando como atacante, no como desarrollador.

Misión: Crea un formulario de contacto completo con validación robusta y protección contra XSS. Después, intenta 'hackearlo' tú mismo: envía datos maliciosos (etiquetas <script>, SQL, email inválido) y verifica que tu código los rechace o los muestre de forma segura. Pídele a la IA que revise tu código y te sugiera 2 mejoras de seguridad. Anota en tu cuaderno: ¿qué tipo de ataque te sorprendió más? ¿cuál pensaste que no era importante?

Pasos sugeridos

  1. Crea un formulario de contacto con: nombre, email, asunto, mensaje.
  2. Implementa validación: nombre no vacío, email válido con filter_var, mensaje mínimo 10 caracteres.
  3. Implementa sanitización: htmlspecialchars en TODA salida.
  4. Implementa el patrón PRG: redirige a gracias.php después del éxito.
  5. Ahora intenta 'hackear' tu propio formulario: envía <script>alert('xss')</script> en el nombre. ¿Se ejecuta?
  6. Si se ejecuta, sanitiza. Si no, perfecto: tu defensa funciona.
  7. Pídele a la IA: 'Tengo este formulario PHP. Audita la seguridad: XSS, CSRF, SQL injection. Dame 2 mejoras concretas con código.'
  8. Aplica al menos 2 mejoras. Vuelve a probar el 'hack'.
  9. Anota: qué ataque te sorprendió más, qué aprendiste sobre defensa.

📓 Entregable: Capturas del formulario funcionando, intento de XSS bloqueado, código del PHP con sanitización, y media página de cuaderno con tu reflexión sobre seguridad.

🚫 Errores típicos de razonamiento

Error 1: Imprimir datos del usuario con echo sin htmlspecialchars.
Por qué: echo $_POST['nombre'] es la receta del XSS. Si el usuario escribe <script>alert('hack')</script>, el script se ejecuta en el navegador de cualquier persona que vea la página. Solución: SIEMPRE htmlspecialchars($variable, ENT_QUOTES, 'UTF-8') antes de imprimir. Es la regla #1 de seguridad en PHP.
Error 2: Usar GET para enviar contraseñas o datos sensibles.
Por qué: Con GET, la contraseña va en la URL: misitio.com/login?pass=secreto. Queda en: el historial del navegador, los logs del servidor, el referrer header si el usuario hace clic en otro enlace, y el bookmark si guarda la página. NUNCA uses GET para contraseñas o datos sensibles. SIEMPRE POST + HTTPS.
Error 3: Olvidar el 'exit' después de header('Location: ...').
Por qué: header() solo envía la cabecera al navegador, pero el script SIGUE EJECUTÁNDOSE. Sin exit, el resto del código se ejecuta antes de que el navegador procese la redirección. Esto puede causar bugs difíciles de detectar (como enviar emails duplicados). SIEMPRE exit después de un header Location.
Error 4: Confiar en los datos del cliente sin validar.
Por qué: El usuario PUEDE enviar lo que quiera: letras donde pides números, emails inválidos, números negativos, scripts, archivos maliciosos. Tu código DEBE validar TODO. Validar no es opcional, es la base de la seguridad. Si no validas, tu app será vulnerable a bugs y ataques desde el día 1.
Error 5: Usar $_REQUEST en producción.
Por qué: $_REQUEST combina $_GET, $_POST y $_COOKIE sin que sepas cuál vino de dónde. Si tu formulario espera POST y un atacante envía la misma URL con GET, $_REQUEST te da el valor de GET sin que te enteres. Es un riesgo de seguridad. Usa $_POST o $_GET explícitamente según el método esperado.

🧪 Laboratorio práctico

Un formulario sin validación es una puerta abierta a los hackers.

Laboratorio: Formulario de contacto seguro

Objetivo: Crear un formulario de contacto PHP profesional con validación, sanitización, y patrón PRG. Practicarás: GET vs POST, $_POST, filter_var, htmlspecialchars, y redirección.

Pasos

  1. Crea contacto.php con un formulario HTML (nombre, email, asunto, mensaje).
  2. El action del form apunta a sí mismo, method='post'.
  3. Si se envió por POST, valida: nombre no vacío, email válido, mensaje mínimo 10 caracteres.
  4. Si NO hay errores, 'guarda' (en un array por ahora) y redirige a gracias.php con el nombre en GET.
  5. Si HAY errores, muestra mensajes específicos cerca de cada campo.
  6. Implementa sanitización: htmlspecialchars en TODA salida al navegador.
  7. Crea gracias.php que muestre el nombre recibido por GET (sanitizado).
  8. Estiliza con CSS externo: errores en rojo, éxito en verde.
  9. Prueba el flujo completo: enviar válido, ver gracias.php, recargar gracias.php (no debe reenviar).
  10. Prueba el intento de XSS: escribe <script>alert('xss')</script> en el nombre. ¿Se ejecuta? Si sí, falta sanitizar.
  11. Haz commit: 'feat: formulario de contacto seguro con PRG'.

📓 Entregable: Capturas del formulario (vacío, con errores, con éxito), captura del intento XSS bloqueado, código de contacto.php y gracias.php, y commit en Git.

📓 Tu cuaderno: Anota pseudocódigo, diagramas, errores que encontraste y respuestas a "explica sin código". La escritura manual refuerza tu razonamiento. Tu profesor puede pedirte que subas fotos de páginas específicas.