martes, 28 de enero de 2014

5. Formularios PHP (parte 2)


Ha pasado mucho tiempo (os pido sincero perdón) desde que hice la última publicación, por desgracia no siempre se tiene posibilidad de continuar un proyecto que comenzó con ilusión. Ahora voy a intentar publicar algunas entradas más para tratar de ayudaros en la construcción de formularios y gestión de datos vía php. Veréis que he introducido también alguna actualización en los posts anteriores pero solamente para incluir la descarga de ficheros de los ejemplos (error por mi parte no habéroslo facilitado desde el comienzo).

En la entrada anterior, fijamos conceptos básicos del diseño de formularios y os comenté la filosofía Todo en Uno a la hora de crear estos formularios. Continuando esta “línea argumental”, la filosofía Todo en uno es muy cómoda porque nos permite conservar los valores del formulario remitidos en los mismos campos desde los que fueron enviados (si pusimos un campo “nombre”, podremos rellenarlo con el valor enviado por el usuario sin utilizar ningún campo adicional).
Pero no todo es “bonito”. La filosofía Todo en uno se nos vuelve un auténtico monstruo cuando:
  1. El formulario tiene mucho procesamiento y/o validación.
  2. Se realizan múltiples acciones condicionales (por ejemplo, si introducen un DNI y se detecta el tipo NIF, redirigir a la web X; en otro caso, si se detecta el tipo NIE, comprobar tal cosa y redirigir a la web Y; ...).
  3. En definitiva, cuando, hablando “en bruto”, las líneas del PHP son suficientes como para no entenderlo de un vistazo.
  4. Los cambios adaptativos (introducir nuevas funcionalidades) nos obligan a revisar todo el PHP completo. Como tiende a crecer, al final aumenta la probabilidad de que nos “saltemos” algo y aparezcan más errores de los deseados.

En el desarrollo de webs “importantes”, al principio me encontré con que el Todo en uno me venía muy bien, pero poco a poco fue complicándose:
  • Tenía un “gran” formulario con los datos del solicitante (dni, nombre y apellidos, domicilio, provincia, etc etc) visibles.
  • Tenía algunos campos de control, ocultos al usuario (más adelante os pondré un pequeño ejemplo de cómo utilizarlos) para manejar el flujo de la información (estoy grabando datos, estoy borrando, actualizando...) y el estado de validación (los datos introducidos son correctos / tienen error).
  • Una vez rellenado el formulario, cuando el usuario hacía clic en Enviar ocurría lo siguiente:
    • Comprobaba si venían los campos rellenos y ponía una variable de control a “Grabando” y la validación a “No comprobado”.
    • Re-rellenaba el formulario con los datos que me vinieran por $_POST (generalmente lo usaba siempre en detrimento de $_GET).
    • Según me indicase la variable de control y la de validación, ahora hacía operaciones:
      • Si control no está rellenado ==>es la primera vez que muestro el formulario y, por tanto, no hago nada más (el usuario acaba de entrar)
      • Si control = “Grabando” y validación = “No comprobado” ==> comprobar los datos recibidos (puede que no sean “aceptables” para lo que esperamos encontrar)
      • Si control = “Grabando” y validación = “Erróneo” ==> redirigir el tráfico a una pantalla de Error.
      • Si control = “Grabando” y validación = “Correcto” ==> proceder a su grabación y notificar al usuario.

  • Podrían añadirse muchas más operaciones condicionadas, pero ¿veis que ya empieza a enrevesarse el procesamiento?

Os dejo un ejemplo en el que podréis ver algo así. Se trata de recibir 3 campos del usuario: Nombre, Tipo de Usuario (1 = Normal, 2 = Administrador) y correo electrónico. Supondremos que para admitir los datos, el usuario ha de introducir bien el tipo de usuario y, además, el correo electrónico debe contener el carácter “@”.
Para la resolución del ejemplo sólo necesitáis conocer (aparte de lo visto en posts anteriores) la función strpos de PHP, que opera de la siguiente forma: strpos(cadena, búsqueda,desplazamiento) donde:
-         cadena es el texto donde buscamos (por ejemplo, pepe@pepito.com)
-         búsqueda es el texto que queremos encontrar (en nuestro caso, “@” aunque no tiene por qué ser sólo un carácter).
-         desplazamiento es un parámetro opcional que viene a decir: busca la cadena “búsqueda” en el texto A PARTIR de la posición que te indique. Esto nos es útil en búsquedas anidadas (cuando queremos buscar un texto VARIAS veces dentro de una misma cadena, por ejemplo si quisiéramos encontrar cuántas veces está la letra “e” en pepe@pepito.com o cuántas veces encontramos la sílaba “pe” en el mismo texto).

Strpos nos devolverá la posición (un número comenzando por cero) donde se encuentra la cadena buscada o FALSE si no la ha encontrado. MUCHO OJO con este FALSE. Fijaos en este código:

      if (strpos(“hola”, “x”) == false)
                  Echo “No lo encuentro”;

Y ahora en este otro código:
      if (strpos(“hola”, “h”) == false)
                  Echo “No lo encuentro”;

En el primer caso es evidente que debe acabar mostrando “No lo encuentro”. Pero ¿a que no parece que en el segundo caso deba mostrar también ese texto? Pues lo hace. ¿Por qué? Porque la posición de la letra H en el texto HOLA es la primera, interpretada como cero. Pero es que el valor cero, cuando PHP lo interpreta como valor verdadero/falso, ¡es el único valor que interpreta como FALSO!
¿Hay solución? Sí, claro que la hay. Utilizar el operador === (tres signos igual seguidos)

      if (strpos(“hola”, “x”) === false)
                  Echo “No lo encuentro”;

      if (strpos(“hola”, “h”) === false)
                  Echo “No lo encuentro”;

Ahora, en el primer caso dirá “No lo encuentro” pero no así en el segundo, ya que el operador triple igual fuerza la igualdad booleana Y NO hace la conversión de numérico a booleano.

Podéis ver el ejemplo funcionando en Capitulo 5, ejemplo 1 y también descargarlo en el enlace al final del capítulo con el resto de ejemplos.

Volviendo al tema que nos ocupa en el post (perdonad la divagación con strpos pero es que son de las cosas más extrañas que me encontré al empezar con PHP viniendo de saber C/C++), parece que la opción “Todo en uno” no es útil cuando los formularios son grandes o tienen muchas condiciones de verificación.

¿Qué opciones tenemos entonces? Hay varias que podemos ir explorando en sucesivos posts. No tienen por qué ser excluyentes, se pueden usar varias a la vez:
1)      Utilizar páginas php intermedias cuya función es validar los datos y redirigir a páginas de éxito / error.
2)      Utilizar páginas php intermedias que se encarguen de toda la gestión de base de datos, de forma que cuando tengamos que escribir a base de datos, el código esté en otros php auxiliares.
3)      Utilizar validaciones que no se realicen en el servidor (php) sino en el cliente. Para ello nos apoyaríamos en el lenguaje JavaScript (existen otras opciones como Visual Basic Script pero la más utilizada y donde más recursos vas a encontrar es en JavaScript).

Si estás leyendo este post por “entretenimiento” más que por aprendizaje, verás que estas 3 opciones nos llevan al modelo de desarrollo más extendido que hay actualmente: MVC = Modelo – Vista – Controlador. Pero no te asustes, no voy a escribir sobre frameworks de desarrollo, vamos a seguir trabajando poco a poco cada uno de los puntos para que veas que se puede con un poquito de esfuerzo por tu parte.

Más de uno diréis, ¡eh, eh, espera! ¿MVC? ¿Esto no era un blog para aprender paso a paso? Sí, tranquilos que no voy a dar un salto enorme ni mucho menos. Aún no hemos hablado siquiera de bases de datos o de nociones básicas de javascript como para “saltar” a MVC, pero me gustaría que adoptemos una visión global de a qué queremos llegar (ojo, yo aún estoy en ese camino y llevo 12 años programando en php).

Por el momento vamos a obviar el tema “base de datos” (si alguien está muy interesado y le urge, le recomiendo empezar a mirar php.net y las funciones de MySQL) y nos vamos a centrar en hacer avances en validación php y validación JavaScript. Para no cargar mucho este post, vamos a intentar dividir el ejemplo 1 de forma que tengamos:
-         Una pantalla cuya función sea mostrar el formulario y, a lo sumo, recuperar los valores previos (del array $_POST). La llamaremos c5-e2-principal.php.
-         Una pantalla que informa que los datos son correctos y se ha tenido éxito, llamada c5-e2-exito.php
-         Una pantalla que informa de errores, llamada c5-e2-error.php
-         Un PHP cuya función sea validar los datos y, en función de la calidad de los mismos, redirija el tráfico a una u otra página, denominada c5-e2-validador.php.

La pantalla inicial, con el formulario y recepción de datos, creo que no hace falta que la pongamos aquí completa. A fin de cuentas es un formulario con campos básicos en el que la única diferencia será cómo definimos la etiqueta FORM




<FORM name=”datos” method=”post” action=”c5-e2-validador.php”>

Observad esa etiqueta action porque ahora estamos redirigiendo a un php distinto. Este php, como es lógico, recibirá en $_POST los valores del formulario que se hayan consignado.

¿Qué haremos en este PHP? Construirlo tal que así:

<?

if (strlen($_POST[‘nombre’]) == 0)
{
      header(“location:c5-e2-error.php”);
      exit():
}

if ($_POST['tipo'] != "1" && $_POST['tipo'] != "2")
{
      header (“location:c5-e2-error.php”);
      exit():
}
                             if (strpos($_POST['email'],"@") === false)
{
      header (“location:c5-e2-error.php”);
      exit():
}

header(“location:c5-e2-exito.php”);

?>

Venga, vamos a trabajarlo un poco porque esto se parece muy poco a lo visto hasta ahora. Para empezar, ¿dónde están las etiquetas HTML, HEAD, BODY, etc? La respuesta es obvia: no están. Recuerda que esas etiquetas pertenecen al estándar HTML y en la tercera lección dijimos: “nuestro PHP debe generar un HTML cuando queramos visualizar algo en pantalla” pero ¿necesita el validador MOSTRAR algo en pantalla? Si miras unas líneas más arriba, verás que dijimos que la función del validador era analizar los datos y redirigir el tráfico a una página u otra. Al no necesitar de interacción con el usuario, no necesitamos incluir etiquetas de HTML ya que no mostramos información.
Como es lógico, sí que necesitamos las marcas <? y ?> ya que las comprobaciones las vamos a hacer a través de PHP.
Las siguientes líneas de código (ignorad la sentencia header aunque ahora mismo os la explico) son las mismas que utilizamos para hacer comprobaciones en el ejercicio 1 (salvo el primer strlen que, simplemente, es para comprobar que se nos ha enviado algo en la variable nombre).
Sólo nos queda explicar qué hacen las sentencias header y exit. header nos permite enviar encabezados http sin formato según la especificación http/1.1. En “cristiano”, lo que nos permite header es mandar cabeceras que le sirven al navegador para interpretar el contenido de la página o realizar ciertas operaciones especiales reservadas. Si tienes interés en verlas todas, aquí te dejo el enlace al RFC 2616 – Hypertext Transfer Protocol – HTTP/1.1

La cabecera que nos interesa es Location, que nos permite redirigir el tráfico a la dirección de Internet que indiquemos. Algunos ejemplos más de qué sería válido como header(“location......”) son:

header(“location:www.google.es”);
header(“location:https://www.gmail.com”);
header(“location:http://servidor.com/miphp.php”);
header(“location:miphp.php?variable1=valor1&variable2=valor2”);

Fíjate que podemos redirigir el tráfico a un servidor (Google.es) con lo que abriría la página “por defecto” que tenga configurada (normalmente es index.html, index.php, index.jsp, ...); o bien redirigir a una página dentro de un servidor concreto (servidor.com/miphp.php) o, incluso, enviar parámetros por GET como se ve en el último enlace.

Por ahora y para no complicarnos más la cosa, nuestros location han sido simples: a un php dentro de nuestro servidor sin más.

La sentencia exit, por su parte, lo único que hace es detener el procesamiento en el punto en que se encuentra. Es decir, todo el código que haya por debajo de exit es ignorado y no se ejecuta nada más.

Los php c5-e2-exito.php y c5-e2-error.php van a ser un simple mostrado de una frase orientativa, así que no creo que necesitemos comentar su estructura aquí (no obstante, en el link más abajo os podéis descargar el ejemplo completo).

Podéis ver funcionar este ejemplo en Capitulo 5 - Ejemplo 2

Para descargar todos los ejemplos de este post, pinchad aquí: Ejemplos Capitulo 5