Un formulario PHP se envía al mismo script que lo imprimió: escribe <form method="post"> con campos con nombre y, cuando $_SERVER['REQUEST_METHOD'] sea 'POST', lee cada campo como $_POST['name']. Escapa con htmlspecialchars() todo lo que vuelvas a imprimir.
Ejecuta este bloque, escribe un nombre en el formulario y pulsa Send: el mismo script se ejecuta otra vez, ahora con $_POST lleno.
Aquí pasan tres cosas que necesita cualquier manejador de formularios:
- La comprobación del método. En la primera visita la petición es un GET y no hay nada que procesar.
?? 'GET'solo importa cuando el archivo se ejecuta desde la línea de comandos, dondeREQUEST_METHODno está definido; en un servidor web siempre lo está. ?? ''en cada campo. Un campo puede faltar (un input renombrado, una petición enviada por un script), y leer una clave que falta imprime un warning.- Escapar en la salida. El nombre vuelve a la página dos veces, y
htmlspecialchars()garantiza que un valor como<b>Ada</b>se muestre como texto en lugar de convertirse en marcado. Los detalles están en la página de htmlspecialchars.
Validar un formulario y mostrar los errores junto a los campos
Reúne los errores en un array cuyas claves sean los nombres de los campos. Si el array está vacío, el formulario es válido; si no, vuelve a imprimir el formulario con cada mensaje bajo su campo y lo que escribió el visitante todavía en su sitio. Envíalo vacío, luego con un email incorrecto y luego con valores válidos.
novalidate desactiva las comprobaciones del propio navegador para que veas funcionar las de PHP. En un formulario real, mantén las dos: la validación del navegador es una comodidad para el visitante, la de PHP es la que cuenta, porque cualquiera puede enviar una petición sin tu formulario. filter_var() comprueba el email y el rango del entero con una llamada cada uno; la página de filter_var explica sus otros filtros.
Casillas, botones de radio y select
Un campo de texto siempre envía un valor, aunque sea vacío. Los demás controles no:
- Una casilla sin marcar no envía nada, así que su clave falta en
$_POST. - Las casillas llamadas
topics[]llegan como un array:$_POST['topics']es['php', 'sql']. - Un grupo de radio o un
<select>envía elvaluede la opción elegida.
Para conservar la elección tras el envío, imprime checked o selected en la opción que coincida. Marca unas cuantas casillas, elige un nivel y pulsa Save.
Las dos comprobaciones tras leer la entrada importan más de lo que parece. array_intersect() descarta cualquier tema que no estuviera en tu lista, y la comprobación con is_string() más array_key_exists() devuelve el nivel a un valor por defecto si alguien envía un valor que nunca ofreciste (o un array, que haría que $levels[$level] lanzara un error). Un select limita lo que puede enviar un navegador, no lo que puede contener una petición.
Formularios de varios pasos con campos ocultos
Un input oculto lleva un valor de un paso al siguiente sin mostrarlo. Aquí el paso 1 pide un nombre, el paso 2 pide un idioma y envía el nombre en un campo oculto, y el paso 3 muestra los dos. Recorre los tres pasos.
Los campos ocultos se ven en el código fuente de la página y se pueden editar como cualquier otro campo, así que sirven para llevar las propias respuestas del visitante entre pasos, pero nunca para un precio, un id de usuario o un rol. Guarda eso en el servidor, en una sesión; consulta sesiones y cookies.
Post/Redirect/Get: evitar el reenvío al recargar
Después de un POST correcto, si el script imprime directamente una página de "gracias", pulsar recargar hace que el navegador vuelva a enviar el POST, y el pedido o el comentario se guarda dos veces. La solución es guardar, redirigir y mostrar el resultado en un GET:
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = validate($_POST);
if (!$errors) {
save_comment($_POST);
$_SESSION['flash'] = 'Comment posted.';
header('Location: /comments.php', true, 303);
exit;
}
// On errors, fall through and print the form with the messages.
}
$flash = $_SESSION['flash'] ?? null;
unset($_SESSION['flash']);
303 See Other le dice al navegador que siga la redirección con un GET. El exit después de header() importa: sin él, el resto del script sigue ejecutándose. La redirección además tiene que ocurrir antes de cualquier salida, o PHP se niega con "headers already sent".
Los formularios reales añaden también un token CSRF: un valor aleatorio guardado en la sesión y en un campo oculto, comparado con hash_equals() al enviar, para que otro sitio no pueda enviar tu formulario en nombre de un visitante que ha iniciado sesión.
Probar la validación sin navegador
Pon las reglas en una función que reciba un array y devuelva los errores. La página la llama con $_POST, y tú puedes llamarla con cualquier array, lo que facilita probar los casos límite. Añade tus propios casos a la lista.
El cuarto caso es el que la gente olvida. Una petición puede enviar name[]=x, lo que convierte $_POST['name'] en un array, y pasar un array a trim() lanza un TypeError y detiene el script. Comprobar antes is_string() convierte una entrada hostil en un error de validación normal. El cast (string) de los bloques anteriores también evita el TypeError, pero convierte el array en la palabra Array e imprime un warning, así que is_string() es la comprobación más limpia.
Error frecuente: leer $_POST antes de que se envíe el formulario
El primer script de formulario más común lee los campos al principio del archivo:
<?php
$name = $_POST['name'];
echo "Hello, $name";
En la primera visita no se ha enviado nada, así que PHP imprime:
Warning: Undefined array key "name" in /var/www/form.php on line 2
La solución es el patrón del primer bloque: procesa el formulario solo cuando la petición sea un POST, y lee cada campo con un valor por defecto, $_POST['name'] ?? ''. Si el warning aparece después de enviar, compara el atributo name del HTML con la clave de PHP: name="Name" y $_POST['name'] son claves distintas.
Preguntas frecuentes
¿Cómo envío un formulario PHP a la misma página?
Escribe <form method="post"> sin action (o con action=""). El navegador envía a la URL actual, así que el mismo script muestra el formulario y lo procesa: comprueba $_SERVER['REQUEST_METHOD'] === 'POST' y lee los campos de $_POST.
¿Cómo conservo los valores del formulario después de enviarlo en PHP?
Imprime el valor enviado de vuelta en el campo, escapado: <input name="email" value="<?= htmlspecialchars($_POST['email'] ?? '') ?>">. Para una casilla imprime checked, y para una opción de un select imprime selected, cuando el valor enviado coincida.
¿Cómo obtengo los valores de varias casillas en PHP?
Dale a las casillas el mismo nombre terminado en corchetes, name="topics[]". PHP pone entonces los valores marcados en un array, $_POST['topics']. Las casillas sin marcar no envían nada, así que usa $_POST['topics'] ?? [] y comprueba cada valor contra la lista que ofreciste.
¿Por qué al recargar la página se vuelve a enviar mi formulario PHP?
La última petición fue un POST, así que el navegador lo repite al recargar. Tras un envío correcto, redirige con header('Location: /thanks.php'); exit; (el patrón Post/Redirect/Get). Así la recarga repite un GET inofensivo.
¿Es seguro usar $_POST en PHP?
No, cada valor de $_POST viene del visitante y puede ser cualquier cosa, incluido HTML, SQL o un array donde esperabas una cadena. Valídalo a la entrada (filter_var, comprobaciones de longitud y de lista permitida) y escápalo a la salida (htmlspecialchars para HTML, sentencias preparadas para SQL).