Apuntes PHP – Gestión de formularios

Etiqueta <form>

Para la gestión de formularios, es imprescindible conocer el funcionamiento de la etiqueta <form> de html.

Esta etiqueta puede contener un conjunto de controles, tales como botones, cajas de texto, casillas de verificación, botones radio, listas desplegables, etc, que permiten al usuario introducir datos y enviarlos al servidor para su procesamiento.

Atributos principales de <form>

La etiqueta <form> puede tener diferentes atributos, los principales son action y method, aunque también admite otros genéricos como id y target.

action

Este atributo contiene el nombre del fichero que procesará los datos remitidos al servidor, por ejemplo “procesa_formulario.php”.

method

Con este atributo, indicamos el modo de enviar los datos al servidor, estos pueden ser get o post.

GET:
Los valores enviados se añaden a la dirección indicada en el atributo action, es decir, los datos se envían directamente en la misma url del navegador, por tanto es un método muy carente a nivel de seguridad. Además también encuentra una limitación de caracteres (unos 2000).

POST:
Los valores se envían de forma separada, no aparecen en la url y son tratados en el fichero de destino mediante la variable $_POST.

POST soporta funciones avanzadas como la posibilidad de subir archivos al servidor.

Si la etiqueta <form> no cuenta con el atributo method definido, de forma predeterminada actuará como si el método fuera get.

Ejemplo:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="manolohidalgo_"/>
    <title>Manolo Hidalgo - 2º DAW</title>
</head>
<body>
    <div id="info">
        <form id="ejemplo" action="procesa_formulario.php" target="_blank" method="post">
            <h1>Información personal</h1>
             <p>Nombre: <input name='nombre'></p>
             <p>Apellidos: <input name='apellidos'></p>
           </form>
    </div>  
</body>
</html>

Etiquetas y contenido del elemento <form>

En la etiqueta <form> es un elemento de bloque, y en su interior puede contar con cualquier elemento clásico de una página web, párrafos, imágenes, listas, tablas… además de los elementos que crean los controles para la gestión del formulario.

Las etiquetas para crear controles en los formularios son:

  • <input>
  • <button>
  • <select>
  • <optgroup>
  • <option>
  • <textarea>

Además, estas etiquetas se pueden agrupar con las etiquetas <fieldset> (crea una caja) y esta a su vez, pueden contar con <legend> que incluye un título en la caja.

Ejemplo:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="manolohidalgo_"/>
    <title>Manolo Hidalgo - 2º DAW</title>
</head>
<body>
    <div id="info">
        <form>
            <fieldset>
             <legend>Información Personal</legend>
             Nombre: <input name='nombre' type='text' tabindex='1'>
             Apellidos: <input name='apellidos' type='text' tabindex='2'>
            </fieldset>

            <fieldset>
             <legend>edad</legend>
             <input type='checkbox' tabindex='20'
                       name='edad' value='20-39' > 20-39
             <input type='checkbox' tabindex='21'
                       name='edad' value='40-59' > 40-59
             <input type='checkbox' tabindex='22'
                       name='edad' value='60-79' > 60-79
            </fieldset>
           </form>
    </div>  
</body>
</html>

El navegador envía únicamente los datos de los controles contenidos en el formulario. En una misma página puede haber varios formularios que envíen datos al mismo o a diferentes agentes.

La etiqueta <input> nos permite especificar su tipo (type) para definir el tipo de dato que queremos por parte del usuario, entre otros podemos obtener el tipo email, date, text, checkbox, month, number… aunque el control del tipo de dato debemos realizarlo tanto en cliente, como en servidor y base de datos, por eso, en algunos ejercicios de DWES no vamos a utilizar estos tipos y lo vamos a hacer sólo en el script.

Más información sobre los tipos de la etiqueta <input> aquí.

Formularios y PHP

Utilizar php en el manejo de los formularios nos va a permitir:

  • Procesar los datos introducidos en el formulario. Como indicaba anteriormente, el valor del atributo action del formulario es el nombre del script responsable del procesamiento.
  • Manejar formularios de forma dinámica:
    • Generar todo el formulario
    • Generar valores iniciales en los campos de entrada
    • Generar una lista de opciones.

Ejemplo formulario.html:

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="manolohidalgo_"/>
    <title>Manolo Hidalgo - 2º DAW</title>
</head>
<body>
    <form action="procesa_formulario.php" method="post">
        <input type="text" name="nombre" placeholder="Nombre" value="" />
        <input type="text" name="apellidos" placeholder="Apellidos" value="" />
        <input type="submit" name="enviar" placeholder="Send" value="Enviar" />
    </form>
</body>
</html>

Este sería el resultado del formulario en pantalla, cuando mandemos la información pulsando el botón “Enviar”, la información se mandará a procesa_formulario.php, el cual está definido por el siguiente script:

Ejemplo de procesa_formulario.php:

<?php
    echo $_POST['nombre'].' ';
    echo $_POST['apellidos'];
    ?>

Como vemos, el resultado que ejecutará este script es una impresión en pantalla de los valores obtenidos en el campo “nombre” y “apellidos”, en este caso quedaría así:

Manolo
Hidalgo

$_SERVER [”PHP_SELF”]

Como hemos visto anteriormente, el atributo action indica cual es el archivo que gestiona el script para procesar el formulario, en el ejemplo anterior vimos como este podía ser otro fichero (procesa_formulario.php) pero también puede utilizarse un mismo fichero tanto para mostrar como para procesar el formulario , esto se puede hacer incluyendo en el atributo action la variable $_SERVER[“PHP_SELF”], la cual contiene el nombre del script que se está ejecutando.

Obviamente, para utilizar el mismo fichero, debemos establecer una capa de control que determine cuando debe ejecutarse el script, si debe generar el formulario, o si debe trabajar con los resultados obtenidos en él.

Para procesar un formulario, lo lógico, es que se hayan rellenado unos campos y se haya pulsado el botón de “Enviar”, esto nos servirá para establecer una variable de control que utilicemos como “llave” para mostrar unos resultados u otros, tal y como veremos en los siguientes pasos.

$_POST y $_GET

La información que ha sido enviada mediante el método POST se almacena en el array asociativo $_POST, mientras que si se hubiese hecho por el método GET, será almacenada en el array asociativo $_GET.

Existen otras formas de recuperar la información, por ejemplo $_REQUEST agrupa el contenido de $_GET y $_POST.

El contenido de los arrays asociativos que recogen la información del formulario, depende de los tipos de inputs utilizados.

De forma general podemos indicar:
Campos de texto. Las variables asociadas tienen el texto introducido.
Grupo de botones de opción. La variable asociada contiene el valor del atributo value de la etiqueta input del botón seleccionado.
Casilla de verificación. La variable asociada contiene el valor contenido en el atributo value de la etiqueta input.
Lista de selección única. La variable asociada contiene el valor contenido en el atributo value de la etiqueta option, o, si no hay atributo value, el valor mostrado en la lista.
Es posible utilizar el atributo value para crear un código que se almacena en la base de datos, mostramos un nombre, pero almacenamos el código.
En este caso el interfaz de usuario (capa de presentación) debe conocer los códigos utilizados, lo cual no es una buena idea. La solución consiste en generar dinámicamente el formulario a partir de la base de datos.
Lista de selección múltiple. La variable asociada contiene el valor del atributo value de la etiqueta option. Si no hay atributo value, el valor mostrado en la lista. Esto es válido sólo para la última opción seleccionada, si la variable es una variable escalar. En consecuencia, para obtener una lista de selección múltiple, es necesario utilizar un array.
Botón de validación. PHP crea una variable que lleva el nombre del botón y tiene como valor el del atributo value, sólo si se pulsa el botón.
Botón de imagen. PHP crea dos variables que llevan el nombre del botón (atributo name) seguido de _x y _y y dando la posición relativa, en pixels, del clic con respecto al ángulo situado en la parte superior izquierda de la imagen. Si en botón no tiene ningún nombre, las dos variables se llaman x e
y.
Botón “reset” o “button”. Sólo permiten realizar una acción simple del lado del cliente

Generación dinámica de formularios

Para crear formularios de manera dinámica utilizaremos PHP para escribir las cadenas con el contenido HTML necesario.

Uno de los aspectos más significativos, es el nombre (valor para el atributo name) que debemos utilizar al generar los campos del formulario. En muchos casos será necesario utilizar una notación tipo array para no tener problemas en el proceso de recuperación.

PHP completa el array añadiendo una línea para cada campo, con un índice entero consecutivo comenzado en 0. Los índices se asignan en función
del orden en el que aparecen los campos en el formulario. Puede aparecer un problema si el orden de los campos cambia. En algunos casos puede evitarse este problema asignando explícitamente el índice, bien como número o como cadena de caracteres.

Ejemplo:

<?php
/**
 * @author: manolohidalgo_
 * @since: 13-10-2020
 */
    $ctdNumeros = 4;
    ?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="manolohidalgo_"/>
    <title>Manolo Hidalgo - 2º DAW</title>
</head>
<body>
    <form action="procesa_formulario6.php" method="post">
        <?php
            for ($i=0; $i < $ctdNumeros; $i++){
                echo "<input type=\"text\" name=\"textos[]\"><br/>";
            }
            echo "<input type=\"submit\" name=\"enviar\" placeholder=\"Send\" value=\"Enviar\" />";
        ?>
    </form>
</body>
</html>

Este código nos genera un formulario con cuatro inputs de tipo texto, pero lo interesante se encuentra en el caso del name donde podemos ver, que los mismos son “textos[]”.

Esto hace que los inputs se almacenen cuando pulsemos sobre “Enviar” en $_POST, pero en vez de ser todos de forma independiente en distintos espacios del array, lo harán en una misma variable (tipo Array) que tendrá por nombre textos[].

Resultado con nombre=textos[]
Resultado con nombre=textos1, nombre=textos2, nombre=textos3, nombre=textos4

IMPORTANTE: Si generando un formulario de forma dinámica, el nombre del input se repite, sólo se almacena el último valor.

Control de datos en formularios

Como comentaba más arriba, el script encargado de realizar la gestión del formulario (que según las necesidades, se recomienda que sea el mismo fichero que lo genera), necesita de una capa de control para mostrar los resultados correspondientes.

Esto quiere decir, que por ejemplo, si hacemos un campo para el login, el usuario, si no mete los datos correctos, obtenga una pantalla de Acceso no autorizado.

Para ello, debemos utilizar la función isset, la cual determina si una variable se encuentra definida y no es NULL.

Ejemplo:

if (!isset ($_POST['enviar']) {
    echo "Acceso no autorizado";
}

Con este código, que podría estar en procesamiento_formulario.php conseguiríamos evitar, que si el usuario incluye en el navegador directamente la URL del fichero, pueda acceder a nada, ya que al no haber llegado a la página mediante el botón “Enviar” del formulario, mostraría Acceso no autorizado.

Esta misma estructura también la podemos utilizar si generamos el formulario directamente en una página generada mediante php, interpretando que si se llega directamente a la página, sin pulsar el botón “Enviar“, nos muestre el formulario vacío, por el contrario, si llegamos tras haber pulsado “Enviar”, el resultado a mostrar será el correspondiente al procesamiento del formulario.

if (!isset ($_POST['enviar']) {
    // Mostramos formulario
} else{
    // Procesamiento del formulario 
}

Evitar que un usuario pueda hacernos llegar datos maliciosos

Una vez introducidos los datos, cuando el usuario pulse “Enviar” debemos comprobar que son correctos, no sólo a nivel de requerimientos y sintaxis, que lo veremos después, sino principalmente a nivel de seguridad.

Para realizar una correcta validación de datos a nivel de servidor es necesario que el script que procesa el formulario y valida los datos sea el mismo que el que lo presenta lo presenta.

<form method = "post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">

</form>

$_SERVER es un array asociativo que contiene información, tales como cabeceras, rutas y ubicaciones de script. Las entradas de este array son creadas por el servidor web.

$_SERVER [”PHP_SELF”] contiene el nombre del script que se está ejecutando.

htmlspecialchars() es una función que convierte caracteres especiales a entidades HTML. Esto significa que va a reemplazar caracteres HTML, como < y > por &lt; &gt;.

$ _SERVER [”PHP_SELF”] puede ser utilizado por los hackers para realizar ataques Cross Site Scripting (XSS).

Cross site scripting (XSS) es un tipo de vulnerabilidad de seguridad informática que se encuentran típicamente en las aplicaciones Web.

XSS permite a los atacantes inyectar secuencias de comandos del lado del cliente en las páginas web. Si PHP_SELF se utiliza en una página, entonces el usuario puede introducir una barra (/) y algo más de Cross Site Scripting (XSS).

¿Cómo se traduce esto explicado anteriormente? veámoslo paso a paso:

Supongamos que tenemos el formulario en una página llamada “test_form.php”:

<form method="post"
            action="<?php echo $_SERVER["PHP_SELF"];?>">

Si un usuario introduce la URL normal en la barra de direcciones:

http://www.example.com/test_form.php

El código anterior sería el equivalente aa:

<form method="post"
            action="test_form.php">

Sin embargo, si un usuario introduce como URL la siguiente dirección:

http://www.example.com/test_form.php/%22%3E%3Cscript%3Ealert(‘hacked’)%3C/script%3E

El código sería traducido a:

<form method="post"
            action="test_form.php/"><script>alert('hacker')</script>

Esta etiqueta, haría aparecer un script con el alert y la palabra “hacker”. Sin embargo, se podría haber ejecutado cualquier código que se insertase entre la etiqueta <script> y por tanto, podría contener un ataque hacia nuestra página web con la finalidad de modificar el funcionamiento de nuestra web o el robo de información.

Para evitar estos ataques, como ya indicamos, podemos utilizar la función htmlspecialchars() la cual convierte los caracteres especiales a entidades HTML.

Así quedaría el código con htmlspecialchars():

<form method = "post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">

</form>

Y cuando el usuario envía su petición mediante la url indicada anteriormente, su traducción sería:

<form method="post"
            action="test_form.php/"><script>alert('hacked')</script>">

Este código no puede ser interpretado, y el intento de exploit fallaría.

Validación de formularios con PHP

Una vez recibimos el array $_POST debemos validar los datos del formulario, para ello debemos seguir los siguientes pasos:

1. Limpiar datos de entrada

  • Sustituir caracteres html por sus correspondientes entidades con la función htmlspecialchars()
  • Eliminar caracteres innecesarios (espacios, tabulación, nueva línea) con la función trim()
  • Eliminar las barras invertidas (\) de los datos de entrada con la función striplashes()

Para llevar a cabo todo este proceso, se recomienda realizar una función (spoiler de los próximos apuntes, pero siguen un funcionamiento muy similar a otros lenguajes).

Aquí os dejo la función que utilizo para la limpieza de los datos, aquí sólo la declaramos, aunque la llamaremos para su uso en el punto 2 (validación de los datos).

/**
* Función que permite realizar la limpieza de los datos recibidos.
* Elimina espacios, tabulaciones y nuevas líneas.
* sustituye caracteres html por sus correspondientes entidades html
* Elimina las barras invertidas en la cadena recibida.
*/

function clearData($cadena)
{
    $cadena_limpia = trim($cadena);
    $cadena_limpia = htmlspecialchars($cadena_limpia);
    $cadena_limpia = stripslashes($cadena_limpia);
    return $cadena_limpia;
}

2. Validación de datos

Para realizar esta validación, debemos realizar las comprobaciones pertinentes de que los datos recibidos por parte del usuario son correctos, por ejemplo, si el campo es requerido, que no se encuentre vacío. Al final de este artículo dejaré un ejemplo de un programa que utiliza diferentes validaciones para comprobar que los datos recibidos son correctos.

Además, también podemos aplicar diferentes filtros de validación:

FILTER_VALIDATE_BOOLEAN
FILTER_VALIDATE_EMAIL
FILTER_VALIDATE_FLOAT
FILTER_VALIDATE_INT
FILTER_VALIDATE_IP
FILTER_VALIDATE_REGEXP
FILTER_VALIDATE_URL

Y también disponemos de filtros que realizan la corrección de la información introducida correctamente, aquí dejamos algunos de estos filtros de saneamiento:

FILTER_SANITIZE_EMAIL
FILTER_SANITIZE_ENCODED
FILTER_SANITIZE_MAGIC_QUOTES
FILTER_SANITIZE_NUMBER_FLOAT
FILTER_SANITIZE_NUMBER_INT
FILTER_SANITIZE_SPECIAL_CHARS
FILTER_SANITIZE_STRING
FILTER_SANITIZE_STRIPPED
FILTER_SANITIZE_URL
FILTER_UNSAFE_RAW

Más información sobre filtros en este enlace.

Redireccionamiento

También podemos redirigir al usuario a otra página desde el script utilizando la función header().

Es necesario utilizarla antes del envío de las cabeceras http. Si la característica de almacenamiento en búffer de la página está activada con la directiva de configuración output_buffering, el resultado del script no
se envía progresivamente, sino que se coloca en un búffer y se envía de forma completa posteriormente.

Otras funciones relacionadas:
header_list: Lista de encabezados de la respuesta.
header_sent: Permite comprobar si los encabezados ya han sido enviados.
get_headers: Lista de los encabezados reenviados por un servidor, para una URL determinada.

Ejemplo de formulario con validaciones

<?php
/**
 * @author: manolohidalgo_
 * @since: 13-10-2020
 */
    $datosPersonales = array ("nombre", "apellidos", "email");

    function clearData($cadena){
        $cadena_limpia = trim($cadena);
        $cadena_limpia = htmlspecialchars($cadena_limpia);
        $cadena_limpia = stripslashes($cadena_limpia);
        return $cadena_limpia;
    }

    // Datos iniciales
    $nombre=$apellidos=$email = "";
    $msgErrorNombre=$msgErrorApellidos=$msgErrorEmail = "";
    $ProcesaFormulario = false;
    
    // Validación
    if (isset($_POST['enviar'])){
        $nombre=clearData($_POST['nombre']);
        $apellidos=clearData($_POST['apellidos']);
        $email=clearData($_POST['email']);
        $ProcesaFormulario = true;
        $correo = $_POST['email'];

        //Validación nombre
        if (empty ($nombre)){
            $msgErrorNombre= "Nombre requerido";
            $nombre="";
            $ProcesaFormulario = false;
        } 
        // Validación apellidos
        if (empty($apellidos)) {
           $msgErrorApellidos = "Apellido requerido";
           $apellidos= "";
           $ProcesaFormulario = false;
        } 
        // Validación email
        if (!(filter_var ($correo, FILTER_VALIDATE_EMAIL))){
            $msgErrorEmail = "Email incorrecto";
            $email = "";
            $ProcesaFormulario = false;
        }
    } 
    ?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="author" content="manolohidalgo_"/>
    <title>Manolo Hidalgo - 2º DAW</title>
</head>
<body>
    <?php
        if ($ProcesaFormulario){
            // muestra los datos
            foreach ($_POST as $valor) {
                if ($valor != "Enviar") echo $valor.'</br>';
            }
        } else {
            if (isset($_POST['enviar'])){
                echo 'Ha ocurrido un error'.$msgErrorNombre.' '.$msgErrorApellidos.' '.$msgErrorEmail;
            }
            echo '<form action="'.htmlspecialchars($_SERVER["PHP_SELF"]).'" method="post">';
            foreach ($datosPersonales as $datos) {
                echo '<input type="text" name="'.$datos.'" placeholder="'.$datos.'" value="'.$$datos.'" />';
            }
            echo "<input type=\"submit\" name=\"enviar\" placeholder=\"Send\" value=\"Enviar\" />";
        }
        ?>
    </form>
</body>
</html>