Formularios en PHP: GET y POST
En esta lección
- Cómo viaja un formulario
- GET: datos en la URL
- POST: datos en el cuerpo de la petición
- Leer un dato sin errores: isset, empty y ??
- Un formulario que se envía a sí mismo
- Leer cada tipo de campo
- Mostrar los valores previos
- El patrón PRG: Post/Redirect/Get
- Ejemplo completo: apuntarse a una salida
- Errores frecuentes
- Resumen
Hasta ahora tus programas PHP generaban páginas con datos escritos en el propio código. Lo interesante empieza cuando el usuario puede enviarte datos: buscar un producto, apuntarse a una clase o dejar un comentario. En esta lección aprenderás cómo llegan los datos de un formulario HTML a PHP, la diferencia entre GET y POST, cómo leer cada tipo de campo sin errores y cómo evitar que al recargar la página se envíe todo otra vez.
Cómo viaja un formulario
Un formulario HTML es la parte que ve el usuario (si necesitas repasarlo, mira Formularios en HTML). Cuando pulsa el botón de enviar, el navegador hace una petición HTTP al servidor con los datos de los campos. Dos atributos de <form> deciden cómo:
action: la dirección del archivo PHP que recibe los datos. Si lo dejas vacío o no lo pones, el formulario se envía a la misma página.method: el método HTTP,getopost. Si no lo pones, se usaget.
<form action="buscar.php" method="get">
<label for="q">Buscar ruta</label>
<input type="search" id="q" name="q">
<button>Buscar</button>
</form>
Lo que PHP recibe es el atributo name de cada campo, no el id. Un campo sin name no se envía. Es el despiste más típico al empezar.
PHP coloca los datos recibidos en unos arrays especiales llamados superglobales, que existen en cualquier parte del script:
| Superglobal | Qué contiene |
|---|---|
$_GET | Datos que llegan en la URL (?q=picos&dificultad=media) |
$_POST | Datos que llegan en el cuerpo de una petición POST |
$_SERVER | Información de la petición y del servidor (método, URL, IP…) |
$_FILES | Archivos subidos (lo verás en Ficheros y subidas) |
$_REQUEST | Mezcla de GET, POST y cookies. Evítalo: no sabes de dónde viene cada dato |
GET: datos en la URL
Con method="get", el navegador pega los datos al final de la dirección, después de un ?, como pares nombre=valor separados por &. Si el usuario busca “picos de europa”, irá a:
buscar.php?q=picos+de+europa
Los espacios y los caracteres especiales se codifican (el espacio pasa a + o %20, la ñ a %C3%B1), pero PHP los descodifica por ti. En buscar.php los lees así:
<?php
$busqueda = $_GET['q'] ?? '';
echo "Has buscado: " . htmlspecialchars($busqueda);
// Has buscado: picos de europa
No necesitas un formulario para probar GET: escribe buscar.php?q=Somiedo en la barra del navegador o crea un enlace con esa dirección.
Nota:
htmlspecialchars()convierte caracteres como<y"en texto inofensivo antes de mostrarlo. Úsalo siempre que imprimas algo que viene del usuario. El porqué lo verás con detalle en Validar y sanear.
POST: datos en el cuerpo de la petición
Con method="post" los datos viajan dentro de la petición, no en la URL. No se ven en la barra de direcciones, no se guardan en el historial ni en los marcadores y no tienen el límite de longitud de una URL.
<form action="contacto.php" method="post">
<input type="text" name="nombre">
<textarea name="mensaje"></textarea>
<button>Enviar</button>
</form>
<?php
$nombre = $_POST['nombre'] ?? '';
$mensaje = $_POST['mensaje'] ?? '';
Cuándo usar cada uno
La regla es sencilla: GET para pedir, POST para cambiar.
| GET | POST | |
|---|---|---|
| Dónde van los datos | En la URL | En el cuerpo de la petición |
| Se puede guardar en marcadores o compartir | Sí | No |
| Límite de tamaño | Unos pocos miles de caracteres | Mucho mayor (configurable) |
| Archivos | No | Sí |
| Uso típico | Búsquedas, filtros, paginación | Registro, login, comentarios, borrar, pagar |
Una búsqueda es GET: no cambia nada y es útil compartir el enlace. Un registro es POST: crea algo y lleva una contraseña que no debe salir en la URL.
Cuidado: POST no es “seguro” por sí mismo. Los datos no se ven en la URL, pero viajan igual de visibles por la red si no usas HTTPS, y cualquiera puede ver lo que envía su navegador con las DevTools. POST sirve para el significado de la petición, no para cifrar.
Leer un dato sin errores: isset, empty y ??
Si intentas leer una clave que no existe, PHP muestra un aviso: Warning: Undefined array key "nombre". Eso ocurre, por ejemplo, la primera vez que alguien abre la página, antes de enviar nada. Tienes tres herramientas:
isset($x):truesi la variable o la clave existe y no esnull.empty($x):truesi no existe o si su valor es “vacío”:'','0',0,[],nullofalse.??(operador de fusión de null): devuelve el valor de la izquierda si existe y no esnull; si no, el de la derecha. No da aviso.
<?php
$datos = ['nombre' => 'Lucía', 'edad' => '0', 'ciudad' => ''];
var_dump(isset($datos['nombre'])); // bool(true)
var_dump(isset($datos['email'])); // bool(false)
var_dump(isset($datos['ciudad'])); // bool(true): existe, aunque esté vacío
var_dump(empty($datos['ciudad'])); // bool(true)
var_dump(empty($datos['edad'])); // bool(true): '0' cuenta como vacío
echo $datos['email'] ?? 'sin email'; // sin email
Fíjate en '0': si alguien escribe 0 en un campo de cantidad, empty() lo trata como vacío. Para texto, lo más claro es trim($_POST['campo'] ?? '') === ''.
| Quiero saber… | Usa |
|---|---|
| Si se ha enviado el formulario | $_SERVER['REQUEST_METHOD'] === 'POST' |
| El valor de un campo, o uno por defecto | $_POST['campo'] ?? '' |
| Si un campo existe (aunque esté vacío) | isset($_POST['campo']) |
| Si un campo de texto está vacío | trim($_POST['campo'] ?? '') === '' |
Un formulario que se envía a sí mismo
Lo más práctico es que la misma página muestre el formulario y procese los datos. Así, si hay un error, puedes volver a pintar el formulario con el mensaje sin cambiar de archivo. Para saber si llegas “de visita” o “enviando”, mira el método de la petición:
<?php
$saludo = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
$saludo = $nombre === '' ? 'Escribe tu nombre.' : "Hola, $nombre. Te esperamos en el rocódromo.";
}
?>
<!DOCTYPE html>
<html lang="es">
<head><meta charset="utf-8"><title>Reserva</title></head>
<body>
<form method="post">
<label for="nombre">Nombre</label>
<input type="text" id="nombre" name="nombre">
<button>Enviar</button>
</form>
<?php if ($saludo !== ''): ?>
<p><?= htmlspecialchars($saludo) ?></p>
<?php endif; ?>
</body>
</html>
Al no poner action, el formulario vuelve a la misma URL. En código antiguo verás action="<?php echo $_SERVER['PHP_SELF']; ?>": no lo copies, porque PHP_SELF se puede manipular desde la URL y, sin escapar, es un agujero de seguridad.
<?= ... ?> es la forma corta de <?php echo ... ?>, y la sintaxis if (...): ... endif; es la alternativa a las llaves que se lee mejor dentro de HTML. Las dos las viste en Sintaxis y echo.
Leer cada tipo de campo
Cada control de formulario llega a PHP de una forma distinta. Este formulario de inscripción a una actividad tiene uno de cada:
<form method="post">
<input type="text" name="nombre">
<input type="number" name="plazas" min="1" max="6">
<select name="nivel">
<option value="iniciacion">Iniciación</option>
<option value="medio">Medio</option>
</select>
<label><input type="radio" name="turno" value="manana"> Mañana</label>
<label><input type="radio" name="turno" value="tarde"> Tarde</label>
<label><input type="checkbox" name="extras[]" value="pies"> Pies de gato</label>
<label><input type="checkbox" name="extras[]" value="magnesio"> Magnesio</label>
<label><input type="checkbox" name="acepto" value="1"> Acepto las normas</label>
<button>Inscribirme</button>
</form>
Y así se lee cada campo:
<?php
$nombre = trim($_POST['nombre'] ?? '');
$plazas = (int) ($_POST['plazas'] ?? 1); // siempre llega como texto
$nivel = $_POST['nivel'] ?? 'iniciacion';
$turno = $_POST['turno'] ?? null; // null si no marcó ninguno
$extras = $_POST['extras'] ?? []; // array con los marcados
$acepto = isset($_POST['acepto']); // true o false
echo "Extras: " . implode(', ', $extras);
// Extras: pies, magnesio (si marcó esos dos)
Lo que tienes que saber de cada uno:
- Texto, número, email, fecha…: llegan siempre como cadena (
string), aunque el campo seatype="number". Convierte tú con(int)o(float), o mejor valida como verás en la siguiente lección. selectyradio: llega elvaluede la opción elegida. Un grupo de radio sin ninguno marcado no se envía, por eso el?? null.- Checkbox suelto: si está marcado llega su
value; si no, no llega nada. Por eso basta conisset(). - Checkbox múltiple: si el
nametermina en[], PHP junta todos los marcados en un array. Sin los corchetes solo te llegaría el último.
Consejo: para ver exactamente qué ha llegado, pon temporalmente
var_dump($_POST);oecho '<pre>' . print_r($_POST, true) . '</pre>';al principio del script. Quítalo antes de publicar.
Mostrar los valores previos
Si el usuario se equivoca en un campo y le devuelves el formulario vacío, tendrá que escribirlo todo otra vez. Lo correcto es rellenar cada campo con lo que envió, siempre escapado:
<input type="text" name="nombre" value="<?= htmlspecialchars($nombre) ?>">
<textarea name="mensaje"><?= htmlspecialchars($mensaje) ?></textarea>
Para un <select>, un grupo de radio o varios checkbox, la técnica es generar las opciones con un bucle a partir de un array y añadir selected o checked a la que coincida con lo enviado. Lo verás en el ejemplo completo del final.
El patrón PRG: Post/Redirect/Get
Imagina que alguien envía el formulario de contacto, ve “Mensaje enviado” y pulsa F5. El navegador pregunta “¿Reenviar el formulario?” y, si acepta, el mensaje se envía dos veces. En una tienda, eso podría ser un pedido duplicado.
La solución es el patrón PRG (Post/Redirect/Get):
- El formulario se envía por POST.
- PHP procesa los datos y, si todo va bien, responde con una redirección.
- El navegador pide la nueva página por GET. Si ahora se recarga, solo repite ese GET, que no cambia nada.
Navegador ──POST /contacto.php──▶ PHP guarda el mensaje
Navegador ◀──303 Location: gracias.php── PHP
Navegador ──GET /gracias.php──▶ PHP muestra "Gracias"
En PHP, la redirección se hace con la función header():
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
$mensaje = trim($_POST['mensaje'] ?? '');
if ($nombre !== '' && $mensaje !== '') {
// Guardar el mensaje (en un archivo, en la base de datos...)
header('Location: gracias.php', true, 303);
exit;
}
$error = 'Rellena el nombre y el mensaje.';
}
Dos detalles importantes:
exitdetiene el script tras la redirección. Sin él, PHP seguiría ejecutando el resto del archivo.- El código 303 (See Other) indica que la siguiente petición sea GET. Sin código, PHP envía 302, que en la práctica funciona igual.
Para mostrar “Mensaje enviado” en la misma página, redirige a contacto.php?enviado=1 y comprueba isset($_GET['enviado']). En Sesiones y cookies verás una forma más limpia: los mensajes flash.
Cuidado:
header()solo funciona si todavía no se ha enviado nada al navegador. Unecho, un espacio o una línea en blanco antes de<?phpprovocanWarning: Cannot modify header information - headers already sent. Procesa el formulario al principio del archivo, antes de cualquier HTML.
Ejemplo completo: apuntarse a una salida
Juntamos todo en un solo archivo, salida.php: formulario que se envía a sí mismo, lectura segura de cada campo, valores previos, errores sencillos y PRG.
<?php
$rutas = ['picos' => 'Picos de Europa', 'somiedo' => 'Somiedo', 'gredos' => 'Sierra de Gredos'];
$errores = [];
$nombre = '';
$ruta = '';
$material = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nombre = trim($_POST['nombre'] ?? '');
$ruta = $_POST['ruta'] ?? '';
$material = $_POST['material'] ?? [];
if ($nombre === '') {
$errores[] = 'El nombre es obligatorio.';
}
if (!array_key_exists($ruta, $rutas)) {
$errores[] = 'Elige una ruta de la lista.';
}
if (!$errores) {
$linea = date('Y-m-d H:i') . " | $nombre | $ruta | " . implode(',', $material) . "\n";
file_put_contents('inscripciones.txt', $linea, FILE_APPEND);
header('Location: salida.php?ok=1', true, 303);
exit;
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head><meta charset="utf-8"><title>Salida de senderismo</title></head>
<body>
<h1>Apúntate a la salida</h1>
<?php if (isset($_GET['ok'])): ?>
<p>Inscripción guardada. ¡Nos vemos en el monte!</p>
<?php endif; ?>
<?php foreach ($errores as $error): ?>
<p style="color: crimson"><?= htmlspecialchars($error) ?></p>
<?php endforeach; ?>
<form method="post">
<label>Nombre <input type="text" name="nombre" value="<?= htmlspecialchars($nombre) ?>"></label>
<label>Ruta
<select name="ruta">
<option value="">Elige una</option>
<?php foreach ($rutas as $valor => $texto): ?>
<option value="<?= $valor ?>" <?= $ruta === $valor ? 'selected' : '' ?>><?= $texto ?></option>
<?php endforeach; ?>
</select>
</label>
<fieldset>
<legend>Llevo mi propio...</legend>
<?php foreach (['bastones' => 'Bastones', 'frontal' => 'Frontal'] as $valor => $texto): ?>
<label>
<input type="checkbox" name="material[]" value="<?= $valor ?>"
<?= in_array($valor, $material, true) ? 'checked' : '' ?>> <?= $texto ?>
</label>
<?php endforeach; ?>
</fieldset>
<button>Apuntarme</button>
</form>
</body>
</html>
Comprobar que la ruta está en $rutas con array_key_exists() es importante: el usuario puede cambiar el HTML desde las DevTools y enviar un valor que no estaba en tu <select>. La escritura en el archivo con file_put_contents la verás a fondo en Ficheros y subidas.
Errores frecuentes
- Olvidar el atributo
name: el campo se ve en la página pero no llega a PHP. Revisa que todos los campos lo tengan. - Leer
$_POST['campo']sin comprobar: la primera vez que se carga la página no existe y apareceUndefined array key. Usa?? ''oisset(). - Checkbox múltiple sin
[]: conname="extras"solo llega el último marcado. Escribename="extras[]". header()después de imprimir algo: da headers already sent. Procesa primero, pinta después, y no dejes espacios antes de<?php.- Redirigir sin
exit: el script continúa y puede ejecutar código que no esperabas. - Confiar en las opciones del formulario: un
<select>no garantiza el valor que llega. Compruébalo en el servidor.
Resumen
| Concepto | Código |
|---|---|
| Destino y método | <form action="archivo.php" method="post"> |
| Leer un dato GET / POST | $_GET['q'] ?? '' / $_POST['nombre'] ?? '' |
| ¿Se ha enviado? | $_SERVER['REQUEST_METHOD'] === 'POST' |
| Existe y no es null | isset($x) |
Vacío ('', '0', 0, [], null) | empty($x) |
| Checkbox suelto | isset($_POST['acepto']) |
| Checkbox múltiple | name="extras[]" y $_POST['extras'] ?? [] |
| Repintar un valor | value="<?= htmlspecialchars($nombre) ?>" |
| Redirigir (PRG) | header('Location: gracias.php', true, 303); exit; |
Ya sabes recibir datos; ahora toca desconfiar de ellos. En la siguiente lección aprenderás a validar y sanear formularios.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.