🎯 Objetivo del tema
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.
La diferencia crucial entre datos en la URL y datos en el cuerpo de la petición.
Cómo PHP recibe los datos del formulario.
Evitar reenvíos accidentales al recargar la página.
filter_var, htmlspecialchars, y por qué NUNCA confiar en el usuario.
Los 3 ataques más comunes y cómo empezar a defenderte.
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.
| Aspecto | GET | POST |
|---|---|---|
| Los datos van en... | La URL: ?nombre=Ana&edad=25 | El cuerpo de la petición (invisible en URL) |
| Visible en la barra de direcciones | Sí, siempre. | No. |
| Se puede compartir como enlace | Sí: .../buscar?q=php | No. |
| Se guarda en el historial | Sí. | No. |
| Tiene límite de tamaño | Sí, ~2-8 KB según servidor. | No (prácticamente ilimitado). |
| Es seguro para contraseñas | NUNCA (queda en logs). | Sí (con HTTPS). |
| Es idempotente | Sí (repetir no cambia nada). | No (cada envío crea un nuevo recurso). |
| Uso típico | Búsquedas, filtros, navegación. | Login, registro, pagos, subir archivos. |
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
| Superglobal | Contiene | Cuándo se llena |
|---|---|---|
| $_GET | Parámetros de la URL (?clave=valor). | En cualquier petición, especialmente con method='get'. |
| $_POST | Datos del cuerpo de la petición. | Solo con method='post'. |
| $_REQUEST | Combina $_GET, $_POST y $_COOKIE (con prioridad POST > GET > COOKIE). | Cualquier petición. Úsalo con cuidado. |
| $_COOKIE | Cookies del navegador. | Si hay cookies enviadas por el usuario. |
| $_SESSION | Variables de sesión del usuario. | Si hay sesión iniciada. |
| $_SERVER | Información del servidor y de la petición. | Siempre disponible. |
| $_FILES | Archivos subidos vía upload. | Solo con enctype='multipart/form-data'. |
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
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ón | Para qué sirve | Ejemplo |
|---|---|---|
| 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
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.
| Ataque | Qué hace | Defensa 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 Injection | Inyectar código SQL malicioso en consultas a la base de datos. | SIEMPRE prepared statements con PDO. |
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
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
- Crea un formulario de contacto con: nombre, email, asunto, mensaje.
- Implementa validación: nombre no vacío, email válido con filter_var, mensaje mínimo 10 caracteres.
- Implementa sanitización: htmlspecialchars en TODA salida.
- Implementa el patrón PRG: redirige a gracias.php después del éxito.
- Ahora intenta 'hackear' tu propio formulario: envía <script>alert('xss')</script> en el nombre. ¿Se ejecuta?
- Si se ejecuta, sanitiza. Si no, perfecto: tu defensa funciona.
- Pídele a la IA: 'Tengo este formulario PHP. Audita la seguridad: XSS, CSRF, SQL injection. Dame 2 mejoras concretas con código.'
- Aplica al menos 2 mejoras. Vuelve a probar el 'hack'.
- 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
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.
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.
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.
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.
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
- Crea contacto.php con un formulario HTML (nombre, email, asunto, mensaje).
- El action del form apunta a sí mismo, method='post'.
- Si se envió por POST, valida: nombre no vacío, email válido, mensaje mínimo 10 caracteres.
- Si NO hay errores, 'guarda' (en un array por ahora) y redirige a gracias.php con el nombre en GET.
- Si HAY errores, muestra mensajes específicos cerca de cada campo.
- Implementa sanitización: htmlspecialchars en TODA salida al navegador.
- Crea gracias.php que muestre el nombre recibido por GET (sanitizado).
- Estiliza con CSS externo: errores en rojo, éxito en verde.
- Prueba el flujo completo: enviar válido, ver gracias.php, recargar gracias.php (no debe reenviar).
- Prueba el intento de XSS: escribe <script>alert('xss')</script> en el nombre. ¿Se ejecuta? Si sí, falta sanitizar.
- 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.