Universidad de Costa Rica
Escuela de Ciencias de la Computación e Informática
CI-2413 Desarrollo de aplicaciones web
Profesor: Jeisson Hidalgo Céspedes
29-Noviembre-2011

Laboratorio 11

Este laboratorio presenta algunos temas prácticos y frecuentes en PHP: manejo de archivos, bases de datos, sesiones y el intercambio de datos entre el cliente y el servidor a través de formularios.

Programación del servidor web: PHP

Manejo de archivos

PHP puede crear o acceder a archivos existentes en el servidor web, por ejemplo, para crear bitácoras o archivos de datos que necesita el programa en PHP.

PHP provee una considerable cantidad de funciones para trabajar con archivos y el sistema de archivos del sistema operativo, la mayoría de ellas fuertemente influenciadas por el lenguaje de programación C. El ejemplo del php_log_file_append muestra cómo crear una bitácora en la misma carpeta donde está el archivo .php, la cual registra detalles sobre cada acceso, como la fecha y la dirección IP del cliente que solicitó el recurso.

// Abrir el archivo para agregarle texto al final (append)
$log_file = fopen('log_file.txt', 'a')
    or die('Error: No se pudo abrir log_file.txt');

// Escribir algunos datos sobre el acceso: fecha, IP del cliente, etc
fprintf($log_file, "%s\t%s\t%s\t%s\n"
    , date('ymd:His')
    , $_SERVER['REMOTE_ADDR']
    , $_SERVER['REMOTE_PORT']
    , $_SERVER['HTTP_USER_AGENT']
    ) or die('Error: no se pudo escribir en la bitácora');

// Cerrar el archivo
fclose($log_file);
Crear un archivo de texto en el servidor. Correr este ejemplo.

El procedimiento para trabajar con archivos en PHP es el mismo que en otros lenguajes: abrir el archivo con la función fopen(), leer o escribir bytes en él (fread(), fwrite(), fprintf(), etc.) y cerrar el archivo con la función fclose(). Todas estas funciones necesitan saber en cuál archivo trabajar. PHP utiliza un tipo de variable especial llamada "recurso" (resource) para representar archivos o registros de una base de datos.

La función fopen() retorna un recurso (resource) hacia el archivo cuya ruta está en el primer parámetro. Pero si fopen() falla al abrir el archivo, retorna false. Es una práctica común detener la ejecución del script con la función die() en caso de no poder acceder a un recurso importante (línea 3 del php_log_file_append) ya que permite rápidamente a los desarrolladores identificar y solucionar el problema; sin embargo, esto nunca debe hacerse. Lo adecuado es presentar una página de error amigable al visitante, y quizá enviar un correo electrónico al administrador del sitio web para avisar del problema. Las principales razones porque fopen() falla al abrir un archivo es por falta de permisos, falta de espacio en disco o porque el nombre del archivo es inválido. Estos problemas son responsabilidad del programador y no del visitante.

El php_log_file_read muestra como recorrer la bitácora creada en el php_log_file_append y presentar su contenido en una tabla XHTML. La lectura se hace en un ciclo hasta encontrar el final del archivo (línea 11). En cada iteración del ciclo se obtiene una línea del archivo y se imprime como una fila de la tabla, a excepción de que sea una línea vacía.

// Abrir el archivo para mostrar su contenido
$log_file = fopen('log_file.txt', 'r')
    or die('Error: No se pudo abrir log_file.txt');

echo "<table><thead><tr>"
    , "<th></th><th>Fecha</th><th>IP</th><th>Puerto</th><th>Agente usuario</th>"
    , "</tr></thead><tbody>\n";

// Imprimir cada línea en una fila de una tabla XHTML
$count = 0;
while ( ! feof($log_file) )
{
    $line = fgets($log_file);
    $line = trim($line);
    if ( strlen($line) == 0 ) continue;
    $line = str_replace("\t", '</td><td>', $line);
    echo "<tr><th>", ++$count, "</th><td>$line</td></tr>\n";
}

echo "</tbody></table>\n";
fclose($log_file);
Leer un archivo de texto en PHP. Correr este ejemplo.

Ejercicio. Escriba un script en PHP que cuente el número de visitas que se le han hecho y las imprima como resultado.

¿Qué pasa si dos programas escriben simultáneamente en un mismo archivo? Si no se hace de forma controlada, la respuesta será que el archivo se corrompe. En un ambiente web, una misma página puede ser visitada por uno, varios o miles de usuarios simultáneamente. Cada uno provoca una ejecución distinta del mismo script PHP. Por eso es tan importante evitar que dos procesos traten de escribir el mismo archivo simultáneamente, o que uno escriba mientras otros están leyendo.

PHP provee la función flock() que bloquea un archivo para uso exclusivo (LOCK_EX) o lo desbloquea (LOCK_UN), y funciona de la siguiente forma. Supóngase que un proceso 1 pide bloquear un archivo con flock(), el cual le otorga permiso de escritura. Mientras el proceso 1 está escribiendo, un proceso 2 solicita bloquear el archivo para escritura; flock() pone en espera al proceso 2 hasta que el proceso 1 haya terminado de escribir. Una vez que proceso 1 desbloquee el archivo invocando a flock() con el parámetro LOCK_UL, flock() sacará de la cola de espera al proceso 2 bloqueando el archivo de nuevo.

El uso de flock() produce una serialización de procesos, y por ende, introduce tiempos de espera que podrían ser notorios para el visitante. Para reducir estos tiempos al máximo se recomienda bloquear el archivo inmediatamente antes de hacer la escritura y desbloquearlo inmediatamente después de escribir en él; es decir, se debe evitar bloquear un archivo desde que inicia el script, y desbloquearlo al final de éste. El php_log_file_lock muestra el ejemplo de la bitácora (php_log_file_append) utilizando candados.

// Abrir el archivo para agregarle texto al final (append)
$log_file = fopen('log_file.txt', 'a')
    or die('Error: No se pudo abrir log_file.txt');

// Escribir algunos datos sobre el acceso: fecha, IP del cliente, etc
if ( flock($log_file, LOCK_EX) )
{
    fprintf($log_file, "%s\t%s\t%s\t%s\n"
        , date('ymd:His')
        , $_SERVER['REMOTE_ADDR']
        , $_SERVER['REMOTE_PORT']
        , $_SERVER['HTTP_USER_AGENT']
        ) or die('Error: no se pudo escribir en la bitácora');

    flock($log_file, LOCK_UN);
}
// Cerrar el archivo
fclose($log_file);
Utilizar candados para evitar que dos procesos escriban un mismo archivo simultáneamente. Correr este ejemplo.

Como se podrá notar en la línea 6 del php_log_file_lock, la solicitud de bloquear un archivo debe hacerse en un if, debido a que flock() retorna false en caso de que el mecanismo de candados no sea implementado por el sistema operativo o el sistema de archivos, como ocurre en FAT32 o NFS (Network File System).

Ejercicio. ¿Puede el script del ejercicio anterior ser reutilizado por varias páginas de un sitio web para mostrar el número de visitas que ha tenido cada una por separado?. Si la respuesta es "No", modifique el script para ser capaz de manejar múltiples contadores. Modifique las páginas de las secciones principales (inicio, currículo, aficiones y computación) su sitio web personal para mostrar el número de veces que han sido visitadas (en un pie de página u otro lugar adecuado). Sugerencia: estudie los elementos del arreglo superglobal $_SERVER para obtener la ruta del script PHP y del sitio web; y el constructo require_once() para reutilizar código PHP. Asegúrese de bloquear el archivo de contadores antes de escribir en él.

Bases de datos

Además del uso de archivos, PHP puede conectarse a diversidad de motores de bases de datos (DBMS, Database Management System) para almacenar información del sitio web, como SQLite, MySQL, PostgreSQL, SQL Server y Oracle. De todos ellos cabe resaltar a MySQL, por ser relativamente liviano y apto para la mayoría de sitios web de mediana escala. Para sitios web gigantes de alta concurrencia, el modelo relacional es inapropiado y el desarrollador debería considerar alguna alternativa, como bases de datos orientadas a documentos. En los ejemplos de esta sección se usará MySQL debido a su histórica popularidad junto con PHP.

Para instalar MySQL en Linux, puede utilizar su administrador de paquetes o compilar desde el código fuente. Además debe instalar el driver de MySQL en PHP. Por ejemplo, en Ubuntu puede lograr estas dos tareas emitiendo el comando sudo apt-get install mysql-server php5-mysql y reiniciar el servidor de PHP si lo hay. Para Microsoft Windows existe un instalador o bien puede utilizar un ambiente de desarrollo integrado como EasyPHP que ya lo incluye. Es normal que en el proceso de instalación se solicite la contraseña del administrador (usuario root) de MySQL, de lo contrario, se asume vacía.

Para acceder a las bases de datos, se debe utilizar un lenguaje de programación y el API (Application Programming Interface) que el DBMS provee. Esto es lo que se hará desde PHP. Sin embargo, la mayoría de motores de bases datos, proveen clientes amigables al usuario, en línea de comandos (trate mysql -u root -p en Ubuntu), gráficos como mySQLAdmin o vía web como phpMyAdmin.

El primer paso para utilizar bases de datos, es crear una en el DBMS y los usuarios que tendrán permiso de accederla. Aunque se pueden crear bases de datos desde PHP dándole permisos de administración del DBMS (la cuenta root de MySQL), no es una solución adecuada ya que compromete la seguridad de los datos. Por esto, es común que la persona que administra el servidor de bases datos utilice alguno de los otros clientes del DBMS para crear una base de datos y los usuarios que podrán modificarla, entre los que se incluye un usuario para los scripts de PHP.

Los siguientes comandos ilustran la creación de una base de datos Universidad utilizando el cliente en línea de comandos de MySQL, y la creación del usuario php_user al cual se le otorgan permisos de administración sobre la base de datos Universidad, pero no sobre otras bases de datos ni el DBMS. Al final se prueba que el usuario php_user haya sido creado exitosamente volviendo a entrar al cliente de MySQL con sus credenciales.

$ mysql -u root -p
Password:

mysql> CREATE DATABASE Universidad;
Query OK, 1 row affected (0.00 sec)

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| Universidad        |
+--------------------+
3 rows in set (0.01 sec)

mysql> USE Universidad;
Database changed

mysql> GRANT ALL ON Universidad.* TO 'php_user' IDENTIFIED BY 'php_user_password';
Query OK, 0 rows affected (0.00 sec)

mysql> QUIT
Bye


$ mysql -u php_user -p
Password:

mysql> QUIT
Bye

Es probable que su sitio web se componga de muchos archivos .php y varios de ellos necesiten acceder a la base de datos. Para evitar redundar código, es conveniente guardar las credenciales en un archivo reutilizable, por ejemplo db_credentials.php, cuyo contenido puede ser como el mostrado en el php_db_credentials. Es muy importante que estas variables estén dentro de la etiqueta <?php...?>, de lo contrario cualquier visitante que trate de acceder a él, verá información sensible. Y si por alguna razón el servidor de PHP dejara de funcionar, el servidor web podría enviarle el archivo db_credentials.php íntegro al navegador para que lo almacene en algún lugar del disco. Por esto, de ser posible el archivo con credenciales debería estar en alguna carpeta del sistema de archivos que no es parte del sitio web, y debería tener permisos de lectura y escritura el administrador del sistema operativo (root en Unix) y permiso de lectura para el usuario del servidor web (algo como www-data) únicamente.

<?php
$db_host = 'localhost';
$db_name = 'Universidad';
$db_user = 'php_user';
$db_pass = 'php_user_password';
?>
Archivo reutilizable con las credenciales para acceder a la base de datos. Al correr este ejemplo debería obtener una página vacía.

Para comunicarse con el DBMS, PHP provee un conjunto de funciones que varían de un DBMS a otro, o la clase genérica PDO (PHP Data Objects) que provee una misma interfaz para acceder a varios DBMS.

Funciones para acceder a MySQL

PHP provee un conjunto de funciones para acceder a MySQL, las cuales inician con el prefijo mysql_. El procedimiento para acceder a la base de datos desde PHP es típicamente el siguiente:

  1. Se establece una conexión con el servidor de bases de datos (DBMS): mysql_connect().
  2. Se escoge la base de datos con la que se va a trabajar: mysql_select_db().
  3. Se emiten consultas (para obtener o actualizar información): mysql_query().
  4. Si los hay, se recorren los registros resultado de la consulta con mysql_fetch_row() o con mysql_fetch_assoc() , se hace algún procesamiento con ellos, y se construye alguna salida útil para el visitante.
  5. Si los hay, se liberan los registros obtenidos de la base de datos: mysql_free_result().
  6. Se cierra la conexión con el DBMS: mysql_close().

El php_db_create_schema muestra un ejemplo parcial de cómo crear las tablas en una base de datos vacía desde PHP. La línea 3 hace que, en esa posición, PHP incruste literalmente el contenido del archivo db_credentials.php mostrado en el php_db_credentials, y luego lo interprete; lo cual ocasiona que las variables $db_host, $db_name, $db_user y $db_pass estén definidas como variables globales para el resto del programa.

// Este PHP se llama solo 1 vez para crear el esquema de la base de datos.
// Cargar las credenciales de la base de datos
require_once('db_credentials.php');

// Conectarse al servidor de base de datos (DBMS)
$db_connection = mysql_connect($db_host, $db_user, $db_pass)
    or die("No se pudo conectar al DBMS: " . mysql_error() );

// De todas las bases de datos que hay en el DBMS, escoger la de Universidad
// Esto equivale a escribir 'USE Universidad;' en un cliente de MySQL
mysql_select_db($db_name)
    or die("No se pudo seleccionar la base de datos: " . mysql_error() );

// Truco: si se pasa el parametro drop_tables=true, se recrea la base de datos
$drop_tables = isset($_GET['drop_tables']) && $_GET['drop_tables'] == 'true';

// Borrar las tablas es util por si el webmaster quiere empezar el sitio de cero
if ( $drop_tables )
{
    // Obtener la lista de tablas existentes para eliminarlas
    $result = mysql_query("SHOW TABLES FROM $db_name")
        or die("No se pudo obtener la lista de tablas: " . mysql_error() );

    // Por cada tabla encontrada en la base de datos, eliminarla
    while ($row = mysql_fetch_row($result))
    {
        // mysql_fetch_row() retorna un arreglo con un valor por cada atributo
        $table_name = $row[0];

        mysql_query("DROP TABLE $table_name") or die( mysql_error() );
        echo "<li>Tabla $table_name: <span class=\"warning\">Eliminada</span>.</li>\n";
    }

    // Ya los registros con los nombres de las tablas no seran usados mas, liberarlos
    mysql_free_result($result);
}

// Crear las tablas que no existen
// Crear cada tabla de la base de datos Universidad
$query =
    'CREATE TABLE IF NOT EXISTS Estudiante (
        Carne CHAR(6),
        Nombre VARCHAR(50) NOT NULL,
        Correo VARCHAR(100) UNIQUE,
        Promedio REAL DEFAULT 0.0,
        CONSTRAINT PRIMARY KEY (Carne),
        INDEX(Nombre(45))
    );';
mysql_query($query) or die("No se pudo crear la tabla Estudiante: " . mysql_error());
echo "Tabla Estudiante: creada exitosamente.<br/>\n";

$query =
    'CREATE TABLE IF NOT EXISTS Profesor (
        Cedula INT(9),
        Nombre VARCHAR(50) NOT NULL,
        Correo VARCHAR(100) NOT NULL UNIQUE,
        CONSTRAINT PRIMARY KEY (Cedula),
        INDEX(Nombre(45))
    );';
mysql_query($query) or die("No se pudo crear la tabla Profesor: " . mysql_error());
echo "Tabla Profesor: creada exitosamente.<br/>\n";

// Es buena practica cerrar la conexion tan pronto como se deja de usar
mysql_close($db_connection);
Creación de tablas en una base de datos MySQL vacía. Obtener el ejemplo completo.

La línea 6 del php_db_create_schema invoca la función mysql_connect($host, $user, $pass) para intentar establecer una conexión con el servidor de base de datos MySQL que está en la computadora local (puesto que el valor de $db_host es 'localhost'), con el usuario y contraseña que se crearon para uso exclusivo de los scripts de PHP. Si mysql_connect() logra establecer la conexión, retorna un recurso de PHP (resource) que representa a la conexión y puede usarse subsecuentemente. Si no se puede establecer la conexión, mysql_connect() retorna false y PHP verifica la segunda condición del or en la línea 7, que detiene la ejecución del script con mensaje de error para el navegador. Como se dijo antes, este mensaje es útil para el desarrollador, pero poco amigable para el visitante.

Si la ejecución alcanza la línea 11 del php_db_create_schema, indica que la conexión con el DBMS fue establecida exitosamente. El DBMS puede tener muchas bases de datos a su cargo, y es necesario indicarle con cual de todas ellas se quiere trabajar con la función mysql_select_db($db_name).

A partir de la línea 40 en adelante, se empiezan a crear las tablas de la base de datos Universidad, pero si ya existen, se mantienen inalteradas. Sin embargo, pueda que por alguna razón el administrador del sitio web de la Universidad (webmaster) quiera limpiar la base de datos y empezar de cero. Para efectos ilustrativos, si se ha provisto el parámetro create_schema.php?drop_tables=true en el URL (de esto se hablará luego) se destruirán todas las tablas existentes de la base de datos sin confirmación alguna, lo cual no es un buen diseño, pero se hace aquí con propósitos ilustrativos.

En caso de que el administrador solicite recrear las tablas, el método más eficiente es eliminarlas de golpe (DROP TABLE) y volverlas a crear vacías. Para poder borrarlas se necesita conocer el nombre de cada una de ellas. El script podría tener estos nombres en un arreglo, o bien, solicitarlos al DBMS mediante una consulta SHOW TABLES de MySQL. Cada cual tiene sus ventajas y desventajas. Aquí se seguirá la segunda.

La consulta SHOW TABLES opera de la misma forma que una consulta SELECT de SQL. La consulta es ejecutada con la función mysql_query($sql_query), la cual retorna un recurso que representa el resultado de la consulta, a veces llamado "record set". En la línea 21 del php_db_create_schema, este recurso se almacena en una variable $result.

Es importante hacer notar que $result almacena un recurso, no los datos que se obtuvieron de la consulta. En el modelo relacional, los datos resultado de una consulta son una tabla temporal, y aunque algunos DBMS permiten acceder a todos ellos como si fuesen una matriz en memoria aleatoria (RAM, Random Access Memory), es más eficiente recorrerlos una fila a la vez. Cada invocación a la función mysql_fetch_row($query_result) trae de la base de datos la próxima fila resultado de una consulta, y la retorna en un arreglo que permite acceder a cada valor por un índice que equivale al número de la columna. Para el caso de SHOW TABLES sólo se retorna una columna con el nombre de cada tabla presente en la base de datos. La línea 28 del php_db_create_schema muestra cómo acceder a este valor. Si se quiere acceder al valor por el nombre de la columna en lugar de un índice, utilícese la función mysql_fetch_assoc($query_result), que puede tener el efecto de hacer al código más portable.

Una vez que se ha obtenido el nombre de la tabla, se emite otra consulta DROP TABLE para eliminarla de la base de datos (línea 30). Nótese que la invocación a mysql_fetch_row() se hace en un ciclo, ya que es muy probable que existan varias tablas en la base de datos. Cuando se ha terminado de procesar la última de ellas, mysql_fetch_row() retornará false en lugar de un arreglo de valores.

La línea 35 del php_db_create_schema invoca la función mysql_free_result($query_result) para liberar las estructuras de datos en memoria que alojan el resultado de la consulta obtenido en la línea 21 con mysql_query(). Si se omite esta invocación, el intérprete de PHP lo hará automáticamente cuando el script termine su ejecución.

En síntesis, el código de las líneas 18 a 36 del php_db_create_schema se ejecuta sólo cuando se ha solicitado eliminar las tablas existentes de la base de datos, obteniendo el nombre de cada una de ellas y eliminándolas con DROP TABLE. Una vez que se han eliminado, el programa sigue su ejecución normal en la línea 40, que emite otras consultas (CREATE TABLE) con la misma función mysql_query(), pero debido a la naturaleza de la instrucción CREATE TABLE de SQL, no se generan registros de datos como resultado, sino los valores true y false indicando respectivamente si la creación de la tabla fue exitosa o no. Como puede inferirse, la función mysql_query() se utiliza para ejecutar cualquier tipo de consulta SQL: crear tablas, insertar valores, actualizar valores, eliminar valores y obtener datos.

Para mysql_query() la consulta es un simple string que pasa directamente al DBMS. La sintaxis de dicha consulta es completamente dependiente del DBMS en uso. El desarrollador web debe consultar la documentación oficial de su DBMS para comprobar su validez u otras opciones útiles. En los ejemplos de este documento se ha utilizado MySQL cuya sintaxis se puede consultar en línea.

Finalmente la línea 64 del php_db_create_schema desconecta al intérprete de PHP del servidor de bases de datos con la función mysql_close(), la cual libera recursos y permite que el DBMS pueda aceptar otras conexiones; aspecto importante cuando se atienden miles de visitantes simultáneamente en un sitio web.

Ejercicio. Modifique el contador de visitas de varias páginas de su sitio web hecho en el ejercicio anterior, para almacenar los contadores en una base de datos. Estudie la documentación de su DBMS para utilizar transacciones cada vez que se actualiza un contador.

Objetos para acceder a la base de datos

Formularios web

A diferencia de otros medios tradicionales, como la radio y televisión; el web permite una comunicación interactiva, donde el visitante puede expresarse y retroalimentar a los autores. El mecanismo de interacción más primitivo es activar un elemento, haciendo click sobre él con el puntero del ratón o navegando con el teclado. Sin embargo, es el formulario web el mecanismo que permite al visitante comunicar información textual, a través de campos de texto, seleccionando valores en una lista emergente, adjuntando un archivo de su computadora, y otros controles que guardan mucha similitud a los que dispone la interfaz de una aplicación de escritorio.

Las aplicaciones web actuales recurren al formulario web como el escenario donde la interacción con el usuario tiene lugar. Tómese de ejemplo un cliente web de correo (como GMail). Los campos para el destinatario, las copias, y el título del mensaje, se implementan con campos de texto (text fields); el cuerpo del mensaje se escribe en un control de área de texto (text area); los botones permiten formatear el texto del mensaje, adjuntar archivos, y uno en especial, el botón de enviar (submit) termina la edición del mensaje y envía copias a los destinatarios.

En la mayoría de casos, la interacción con un formulario web tiene repercusiones en el lado del sevidor. Por ejemplo en el caso del cliente de correo, además de enviar el mensaje a los destinatarios, se guardará una copia en el buzón de salida. Es decir, algún texto se concatenará a un archivo, o un registro se agregará a la base de datos del servidor de correo. Es por esto que la implementación de un formulario web requiere trabajo en ambas partes: en el lado del cliente, el navegador despliega el formulario y asiste al informante en el ingreso de información mediante JavaScript; mientras que en el lado del servidor se reciben los datos, se realiza la validación de los mismos y se aplica el efecto para el cual se ideó el formulario (enviar un correo, almacenar datos, buscar información, etc.).

El formulario web es un concepto que existe desde los inicios de la web, y se escribe con el elemento form como se ejemplifica en las líneas 12 a 16 del php_login_form_get. La primera vez que se carga este programa, ninguna de las variables $username y $password tendrán un valor, por lo que el control alcanza el else y despliega el formulario web de las líneas 12 a 16. Nota: El operador <<< se conoce como Heredoc, y es seguido por un identificador y un cambio de línea. Todos los caracteres que se encuentren entre ese cambio de línea y la próxima ocurrencia del identificador forman un string en el cual se hace interpolación de variables. Es útil para encerrar texto que contiene comillas dobles sin tener que utilizar caracteres de escape (\). La única restricción es que el identificador de cierre sólo puede estar precedido por un cambio de línea como se ve en la línea 17.

<body><?php
    $username = isset($_GET['username']) ? $_GET['username'] : '';
    $password = isset($_GET['password']) ? $_GET['password'] : '';
    if ( $username != '' && $password != '' )
    {
        echo "<p>Bienvenido(a) <strong>$username</strong> a nuestro sitio seguro. ";
        echo "(No eres <a href=\"login1.php\">$username</a>?).</p>";
    }
    else
    {
        echo <<<_EOT
        <form method="get" action="login1.php">
            <p><label>Usuario: <input type="text" name="username"/></label></p>
            <p><label>Contraseña: <input type="password" name="password"/></label></p>
            <p><input type="submit" value="Enviar"/></p>
        </form>
_EOT;
    }
?></body>
Un formulario web minimalista. Correr este ejemplo.

Un formulario web (form) se compone de varios campos de entrada (input, select y textarea) en los que el visitante ingresa o escoge información. En el caso del php_login_form_get el formulario consta de un campo de texto con nombre username (línea 13), un campo de texto para ingresar contraseñas llamado password (línea 14) y el botón de "submit" (línea 15). El usuario llena estos campos y cuando está listo, presiona el botón de enviar (submit). Esto causa que el navegador recopile el valor de todos los campos del formulario como parejas nombre_campo=valor y las envíe a algún programa en el servidor web para que las procese. El URL de este programa se especifica en el atributo action del elemento form (línea 12), que en el caso del php_login_form_get es el programa mismo.

¿Cómo hace el programa indicado en el atributo action para recuperar los datos ingresados por el visitante? El protocolo HTTP establece dos métodos estándar para transferir los datos de un formulario: GET y POST, el cual se escoge con el atributo method del elemento form. En el php_login_form_get se utilizó el método GET (línea 12).

El método GET

El método GET indica que el navegador debe enviar las parejas nombre_campo=valor al servidor en el URL, separadas del programa (indicado por el valor del atributo action del elemento form) por un signo de interrogación, y cada pareja separada de otra por un ampersand (&), de la forma siguiente:

action?field1=value1&field2=value1&...&fieldN=valueN

Al texto que aparece después del carácter signo de pregunta en el URL anterior (marcado en negritas) se le conoce como query string, y es visible al visitante. Esto se puede probar al correr el programa del php_login_form_get, escribir un par de valores en los campos de texto y examinar la barra de direcciones del navegador tras presionar el botón de enviar. Incluso, el visitante puede cambiar los valores en la barra de direcciones y ejecutar de nuevo el programa con ellos; o bien guardar el URL en sus favoritos.

PHP facilita el trabajo al programador, y cada vez que se llama un script, el intérprete de PHP "parsea" el query string y extrae cada valor que en él encuentre, y los agrega a un arreglo asociativo superglobal llamado $_GET, de tal forma que el programador puede acceder a los valores con la expresión:

$field_value = $_GET['field_name'];

donde field_name corresponde al valor del atributo name del campo input, select o textarea definido en el formulario web. En lo siguiente se explicará paso a paso la lógica del php_login_form_get.

La primera vez que el visitante accede al programa login1.php normalmente no provee un query string, y por ende las variables $username y $password adquirirán cadenas vacías en las líneas 2 y 3; ya que la función isset() retorna true sólo para variables definidas, lo cual no ocurre con las entradas $_GET['username'] y $_GET['password'] en el arreglo asociativo $_GET. De esta forma, la condición del if en la línea 4 se evalúa como false y hace que el intérprete de PHP imprima el formulario (líneas 11 a 16), el cual es enviado al navegador.

El navegador presenta el formulario vacío, el cual consta de los siguientes controles: un campo de texto normal con nombre username (línea 13), un campo de texto especial para escribir contraseñas llamado password (línea 14), y el botón de enviar (línea 15). Supóngase que el visitante escribe 'chema' en el campo username, 'semeolvido' en el campo password y presiona el botón Enviar. Para el navegador este botón es especial, y cuando se activa busca el formulario al cual pertenece (línea 12), y examinando su atributo action obtiene el programa al que se le debe enviar los valores ingresados por el usuario (login1.php) y a través de cuál método (get). El navegador recopila los nombres y los valores de cada campo, arma el query string y lo concatena al URL del programa, lo cual genera el siguiente URL:

http://www.ejemplo.com/path/login1.php?username=chema&password=semeolvido

Seguidamente, el navegador pone este URL en la barra de direcciones y envía el siguiente mensaje de solicitud HTTP al servidor (los cambios de línea son significativos):

GET /path/login1.php?username=chema&password=semeolvido HTTP/1.1
Host: www.ejemplo.com:80
User-Agent: Chrome/13

Como se puede ver, los valores de los campos viajan en la línea de solicitud (request line) del mensaje de solicitud HTTP, precedidos por el método de solicitud GET del estándar HTTP. Al recibir este mensaje, el servidor web localiza dentro de su sitio el recurso /path/login1.php y de acuerdo a su configuración, la extensión .php le indica que debe invocar al intérprete de PHP enviándole por parámetro el mensaje de solicitud completo, más otra información.

El intérprete de PHP recibe la información del servidor web, y la distribuye en los arreglos superglobales $_SERVER, $_ENV, etc. "Parsea" el query string; llena el arreglo asociativo $_GET e inicia la ejecución del login1.php del php_login_form_get, pero esta vez habrá una diferencia importante: las entradas $_GET['username'] y $_GET['password'] están definidas en el arreglo asociativo $_GET, por lo que las variables $username y $password adquirirán los valores 'chema' y 'semeolvido' respectivamente en las líneas 2 y 3. La condición del if esta vez se evaluará como verdadera y se imprimirá el texto de bienvenida, el cual es regresado como resultado al servidor web, quien lo despachará al cliente.

Ejercicio. Modifique el ejercicio 1.7 del laboratorio 10 para que muestre un campo de texto donde el visitante puede ingresar el año del cual quiere ver los días feriados. Si no se especifica un año, su programa debe mostrar el conteo de días feriados del año en curso.

El método POST

En el ejemplo del php_login_form_get se envía al servidor una contraseña por el método GET, lo cual la hace visible al usuario en el URL, quien además puede modificarla. El método POST realiza el mismo trabajo del método GET de transferir valores de un formulario al servidor web, con la diferencia de que éstos se envían en el cuerpo del mensaje de solicitud HTTP en lugar de la línea de solicitud, lo cual los hace invisibles al usuario, impidiendo que los puedan modificar o agregar a los marcadores del navegador. Los cambios para hacer el php_login_form_get funcionar con el método POST son pocos, como se resaltan en el php_login_form_post (líneas 2, 3 y 12).

<body><?php
    $username = isset($_POST['username']) ? $_POST['username'] : '';
    $password = isset($_POST['password']) ? $_POST['password'] : '';
    if ( $username != '' && $password != '' )
    {
        echo "<p>Bienvenido(a) <strong>$username</strong> a nuestro sitio seguro. ";
        echo "(No eres <a href=\"login1.php\">$username</a>?).</p>";
    }
    else
    {
        echo <<<_EOT
        <form method="post" action="login1.php">
            <p><label>Usuario: <input type="text" name="username"/></label></p>
            <p><label>Contraseña: <input type="password" name="password"/></label></p>
            <p><input type="submit" value="Enviar"/></p>
        </form>
_EOT;
    }
?></body>
Un formulario de autenticación utilizando el método POST. Correr este ejemplo.

Cuando el intérprete de PHP ejecuta un script, toma las parejas campo=valor del query string y las agrega al arreglo asociativo $_GET, y las parejas que el navegador envía en el cuerpo del mensaje HTTP, las agrega al arreglo asociativo $_POST. Como es de notar, ambos métodos no son excluyentes. El programador debe simplemente tener el cuidado de utilizar el arreglo asociativo correspondiente al método escogido en el atributo method del elemento form.

A modo de ilustración supóngase que el visitante ingresa los mismos valores: 'chema' en el campo username, 'semeolvido' en el campo password y presiona el botón Enviar del php_login_form_post. Esta vez el navegador detecta que el método de envío de datos es POST, y ensambla el siguiente mensaje de solicitud HTTP:

POST /path/login1.php HTTP/1.1
Host: www.ejemplo.com:80
User-Agent: Chrome/13
Content-Type: application/x-www-form-urlencoded
Content-Length: 38

username=chema&password=semeolvido

En la mayoría de situaciones el método POST es preferido sobre el método GET, no sólo porque asegura a la aplicación web mayor control sobre los datos ingresados, sino por una razón más. La cantidad de bytes que se puede escribir en un URL es bastante limitada, mientras que en el cuerpo del mensaje HTTP puede crecer arbitrariamente. Esto es especialmente necesario cuando se tienen grandes formularios o donde el usuario puede escribir extensas cantidades de texto en sus campos.

Seguridad y validación de datos

Los ejemplos del php_login_form_get y php_login_form_post no deben ponerse en funcionamiento en un sistema en producción, debido a que tienen una gran vulnerabilidad: los valores que provienen de los arreglos asociativos $_GET y $_POST son provistos por el visitante, y nada impide que éste pueda enviar código (X)HTML, JavaScript, PHP o SQL con malas intenciones, por ejemplo, para descubrir contraseñas o información sensible. A esta práctica se le llama inyección de código y aunque es bastante fácil evitar sus efectos nocivos, sigue siendo un tipo de ataque común ya que es fácil para el programador olvidar defender su código.

Siempre que el programador va a tomar un valor de los arreglos $_GET y $_POST, debe "esterilizarlo" (sanitize) primero; lo cual se logra neutralizando el efecto de los caracteres especiales en cada lenguaje. PHP provee varias funciones para esterilizar código, las cuales se listan en la php_sanitize_functions.

Función Descripción
stripslashes($str) Retorna un string resultado de eliminar los backslahes (\) de $str.
htmlentities($str) Retorna un string resultado de reemplazar los caracteres especiales en (X)HTML (<, >, &, ', ") por sus respectivas entidades (&lt;, &gt;, &amp;, &apos;, &quot;).
strip_tags($str) Elimina etiquetas (X)HTML que hayan en $str y retorna el resultado en una nueva cadena.
mysql_real_escape_string($str) Inserta backslahes (\) a los caracteres que tienen significado especial en SQL, como cambios de línea y comillas.
Funciones para esterilizar código en PHP

El php_sanitize muestra un archivo con funciones de conveniencia que llaman a las listadas en la php_sanitize_functions, las cuales se utilizan en las líneas 2, 3 y 4 del php_login_form_post_sanitize para evitar inyección de código maligno.

<?php
function sanitize($str)
{
    return strip_tags(htmlentities(stripslashes($str)));
}

function sanitize_mysql($str)
{
    return sanitize(mysql_real_escape_string($str));
}

function sanitize_trim($str) { return trim(sanitize($str)); }
function sanitize_mysql_trim($str) { return trim(sanitize_mysql($str)); }
?>
Funciones de conveniencia para esterilizar código (X)HTML, PHP y SQL.
<body><?php
    require_once('sanitize.php');
    $username = isset($_POST['username']) ? sanitize_trim($_POST['username']) : '';
    $password = isset($_POST['password']) ? sanitize_trim($_POST['password']) : '';
    if ( $username != '' && $password != '' )
    {
        echo "<p>Bienvenido(a) <strong>$username</strong> a nuestro sitio seguro. ";
        echo "(No eres <a href=\"login1.php\">$username</a>?).</p>";
    }
    else
    {
        echo <<<_EOT
        <form method="post" action="login1.php">
            <p><label>Usuario: <input type="text" name="username"/></label></p>
            <p><label>Contraseña: <input type="password" name="password"/></label></p>
            <p><input type="submit" value="Enviar"/></p>
        </form>
_EOT;
    }
?></body>
Esterilización de código. Correr este ejemplo.

Ejercicio. Modifique su solución del ejercicio anterior para impedir efectos secundarios ante un ataque de inyección de código.

La validación de datos provistos por el informante es una tarea más semántica, como por ejemplo, verificar que los números se encuentren dentro de un rango apropiado, los textos no sean muy cortos o largos, o estén en algún lenguaje natural. En el php_login_form_post_sanitize simplemente se ha discriminado cadenas vacías o constituidas únicamente de espacios en blanco (línea 5).

Ejercicio. Haga que si se provee un año que su programa no soporta (por ejemplo, -234, 659071 ó 'asdfg'), presente un mensaje indicando el rango de años válidos para su aplicación, además del formulario para ingresar otro año (pero no los resultados para el año actual, ya que podría confundir al visitante).

Controles en el formulario

El estándar (X)HTML permite varios tipos de controles para ingresar datos, los cuales se listan en la xhtml_form_control_types. Normalmente cuando se escribe un formulario, el autor incluye algún texto cercano a cada campo que ayude al informante a saber qué tipo de información debe proveer en él, al cual se le llama rótulo o etiqueta (label). Si tanto el texto como el campo se escriben dentro de un elemento label, el navegador los asociará semánticamente, de tal forma que cuando el usuario hace click en el rótulo, el navegador transferirá el efecto al campo asociado. Este es el comportamiento natural que el usuario espera, en especial para checkboxes y radio buttons. Los ejemplos de la xhtml_form_control_types incluyen rótulos para ilustrar esta práctica.

Nombre Ejemplo
Checkbox

<label><input type="checkbox" name="recordarme" value="si" checked="checked"/>Recordarme</label>
Radio buttons

Sexo:

<p>Sexo:
    <label><input type="radio" name="sexo" value="1" checked="checked"/>Hombre</label>
    <label><input type="radio" name="sexo" value="2"/>Mujer</label>
</p>
Combo box

<label>Nivel académico aprobado:
    <select name="nivel_academico">
        <option value="0">Ninguno</option>
        <option value="1">Kindergarten</option>
        <option value="2">Primaria</option>
        <option value="3">Secundaria académica</option>
        <option value="4">Secundaria técnica</option>
        <option value="5">Universitaria</option>
    </select>
</label>
Multiple list

<label>Marque los lenguajes que conoce:<br/>
    <select name="aficiones" size="6" multiple="multiple">
        <option value="1">Ensamblador</option>
        <option value="2">C</option>
        <option value="3">C++</option>
        <option value="4">Objective-C</option>
        <option value="5">Java</option>
        <option value="6">C#</option>
        <option value="7">PHP</option>
        <option value="8">JavaScript</option>
        <option value="9">Ruby</option>
        <option value="10">SQL</option>
        <option value="11">Lisp</option>
        <option value="12">Prolog</option>
    </select>
</label>
Button

<input type="button" value="Validar"/>
Submit button

<input type="submit" value="Buscar"/>
Reset button

<input type="reset" value="Limpiar"/>
Text field

<label>Buscar:
    <input type="text" name="buscar" maxlength="255" size="50" value="Escriba su consulta aquí"/>
</label>
Password

<label>Contraseña: <input type="password" name="contrasena" maxlength="12" size="12"/></label>
Text area

<label>
    Describa el problema: <br/>
    <textarea name="problema" cols="60" rows="6">Escriba los pasos para reproducir el problema</textarea>
</label>
                
File select

<input type="file" name="avatar"/>
Hidden field
<input type="hidden" name="userid" value="chema"/>
Tipos de controles disponibles en un formulario web

A excepción de las listas (select) y las áreas de texto (textarea), la mayoría de controles en (X)HTML se escriben con el elemento input. Su único atributo obligatorio es type, que indica el tipo de control que se quiere: checkbox, radiobutton, .

Todos los controles comparten el atributo name, cuyo valor es un identificador que no necesariamente debe ser único en el documento. Cuando un formulario es aceptado (al presionar el botón "submit"), el navegador ensambla las parejas campo=valor en el query string a partir de los nombres de los controles y sus respectivos valores. Si un control no tiene nombre, el navegador simplemente lo ignora. Los demás atributos son dependientes del tipo de control, como se explica en los siguientes párrafos.

Las casillas de verificación (checkboxes) permiten al usuario indicar un valor booleano al marcar o no un rectángulo. Se escriben con la notación <input type="checkbox" name="checkbox_name" value="yes" checked="checked"/>. Su estado inicial es desmarcado, a menos de que se provea el atributo checked="checked". Cuando se acepta el formulario, si la casilla de verificación está desmarcada, el navegador simplemente no la incluye en el query string. Si la casilla de verificación está marcada, se incluye una pareja checkbox_name=on en el query string. El valor "on" lo asume el navegador, pero se puede reemplazar por un valor más significativo indicándolo en el atributo value="valor".

Los botones de radio (radio buttons) permiten al informante escoger una única opción entre varias disponibles. El autor debe proveer un elemento <input type="radio" name="nombre_grupo" value="valor" checked="checked"/> por cada opción. Si varios botones de radio comparten el mismo nombre, conforman un grupo de botones de radio y sólo uno de ellos puede estar marcado a la vez. Inicialmente todos los botones de radio están desmarcados a menos de que uno tenga el atributo checked="checked". Cuando se acepta el formulario (se presiona el botón Submit), si ningún botón de radio en el grupo está seleccionado, el navegador omite el grupo por completo en el query string; si el botón seleccionado no tiene un valor en el atributo value, el navegador envía el valor "on" para el grupo, lo cual carece de utilidad para el desarrollador; por eso es importante proveer un valor adecuado en el atributo value de cada botón de radio, y en tal caso, al aceptar el formulario el navegador agrega una pareja nombre_grupo=valor, donde valor es el valor del atributo value del botón seleccionado por el usuario.

Los botones (button) se especifican con <input type="button" value="label del botón"/>. No tienen una acción asociada a menos de que se les establezca una con JavaScript. Su rótulo se puede cambiar con el atributo value. El botón de enviar (submit button) se escribe <input type="submit" value="label del botón"/> ejecuta la acción especificada en el atributo action del formulario (elemento form). Existe una variación del botón enviar que permite reemplazarlo por una imagen, por ejemplo: <input type="image" src="enviar.svg" alt="Enviar datos"/>. El botón de limpiar (reset button) se escribe <input type="reset" value="label del botón"/> y cuando se presiona indica al navegador que regrese todos los controles del formulario a sus valores originales.

Los campos de texto (text field) son quizá el tipo de control más usado en el web. Permiten introducir una línea de texto, cuya longitud está limitada por el valor del atributo maxlength. El atributo size indica la cantidad de caracteres que tendrá el ancho visible del control, sin embargo, es mejor ajustar el ancho mediante hojas de estilo (CSS). Si se escribe un texto en el atributo value, se tomará como el valor inicial del campo. En caso de que se quiera delimitar el campo para que permita únicamente números o valores en cierto formato, se debe emplear JavaScript.

Una variación del campo de texto son los campos de contraseña (password field) que despliegan asteriscos u otro carácter especial para encubrir los reales, con el fin de introducir información sensible que podría ser vista por una persona ajena al informante. Tiene otras características como deshabilitar funciones del portapapeles y disparar el sistema de recuerdo de contraseñas del navegador.

El selector de archivo (file select) permite al visitante adjuntar un archivo cualquiera de su computadora local al formulario. Se escribe con el elemento <input type="file" name="nombre_selector"/>. Una vez enviado al servidor, el nombre del archivo y su contenido se pueden obtener en PHP a través del arreglo superglobal $_FILES.

El elemento <select name="nombre_lista" size="elementos_visibles" multiple="multiple" value="default_value">...</select> sirve para construir listas desplegables, listas simples y listas múltiples. Una lista desplegable (combo box) presenta sólo el elemento seleccionado y en caso de que se active el control, una lista emergente muestre todas las opciones. Las listas desplegables se forman con el atributo size = 1, y aunque permiten selección múltiple, no es un comportamiento natural. Si el valor del atributo size es 2 o más, el navegador presentará un control de lista simple; y si el atributo multiple="multiple" está presente, el navegador presentará una lista múltiple donde el usuario podrá seleccionar varios elementos con el ratón mientras mantiene la tecla Ctrl o Command presionada.

Los elementos de la lista se escriben con elementos <option value="valor">Texto</option>. Por defecto el primero ellos estará seleccionado en caso de que la lista sea desplegable (combo box). Si se quiere preseleccionar otro valor, debe especificarse con el atributo value del elemento select. Se pueden crear grupos de opciones con el elemento optgroup, como se ilustra en el xhtml_hierarchical_list_control.

<label>Cantón de nacimiento:<br/>
    <select name="canton" size="10">
        <optgroup label="San José">
            <option value="101">San José</option>
            <option value="102">Escazú</option>
            ...
            <option value="120">León Cortés</option>
        </optgroup>
        <optgroup label="Alajuela">
            <option value="201">Alajuela</option>
            <option value="202">San Ramón</option>
            ...
            <option value="215">Guatuso</option>
        </optgroup>
        ...
    </select>
</label>
Grupos de opciones en un control de lista.

Los campos ocultos (hidden field) son útiles para almacenar valores provenientes del servidor web que no es necesario mostrar al usuario; pero que pueden ser leídos por código JavaScript, y también por código PHP cuando el formulario vuelva a ser enviado. Se suelen transferir identificadores de usuario o sesión mediante estos campos; los cuales no se deben asumir seguros, ya que sus valores se pueden descubrir simplemente mirando el código fuente de la página web en el navegador.

El autor puede ajustar la apariencia de todos los controles del formulario mediante hojas de estilos CSS. Es una práctica común emplear una tabla dentro del formulario para dar una apariencia ordenada. En tal caso habrá que separar los rótulos de los controles. Por dicha el elemento label tiene el atributo for="id_del_control", que permite conectar el rótulo con el control mediante el id del control, indiferentemente de dónde ése se encuentre dentro del documento web.

Ejercicio. Modifique su programa de días feriados para permitir al visitante escribir un rango de años, por ejemplo 2010 a 2012. Al enviar la consulta, su programa despliega los días feriados para cada año en el rango (2010, 2011 y 2012 en nuestro ejemplo). Utilice los controles que guste.

Ejercicio. Su implementación actual del programa de días feriados funciona de la siguiente forma. La primera vez que el visitante carga la página, se presenta un formulario vacío. En él escribe un rango de años y envía la consulta. El sistema le responde con el resultado de la consulta y el formulario vacío de nuevo para permitirle emitir una nueva consulta. Haga que el formulario en la página resultado conserve en sus campos los valores que el visitante escribió en la consulta previa.

Ejercicio. Haga que su sitio lleve cuenta del número de veces que se consulta los días feriados de cada año. Despliegue este contador en sus resultados, por ejemplo:

Feriados de 2011 por día de la semana (consultado 18 veces)
Feriados Detalle
Domingo 2 Día del trabajo, Navidad
Lunes 3 Batalla de Rivas, Anexión de Nicoya, Día de la madre
Martes 1 Día de la Virgen de los Ángeles
Miércoles 1 Día de las culturas
Jueves 1 Independencia de Costa Rica
Viernes 0
Sábado 1 Año nuevo

Nota: El rojo es sólo para hacer notar la diferencia. En su solución el contador puede aparecer del color natural del texto.

Validación de datos con JavaScript

Cuando se implementa un formulario web es apremiante hacer una doble validación de datos, tanto en el servidor web como en el navegador. La validación de datos en el lado del cliente es instantánea. Permite al informante tener asistencia y retroalimentación antes de enviar los datos, sin tener que esperar y consumir recursos para que el servidor web se pronuncie. El autor podría estar tentado a pensar que validando los datos en el navegador, éstos estarán limpios y listos para ser procesados o almacenados en la base datos, por lo que es innecesaria una segunda validación con PHP. Sin embargo, la validación en el lado del servidor es ineludible. El visitante podría deshabilitar JavaScript en su navegador, o modificar su código fuente, tanto (X)HTML como JavaScript y poner a prueba su servidor de ingeniosas formas, en especial si la información que está en juego es sensible o de alto interés (como dinero).

En general la validación con JavaScript consiste en obtener el valor de cada control del formulario y revisar que sea adecuado. Existen varias formas de acceder al valor de un control. Quiźa la más sencilla sea utilizando el valor del atributo name. Cuando el navegador carga el documento, crea un objeto tipo arreglo document.forms[] con cada formulario que en él encuentre. Además si se provee un nombre al formulario de la forma <form name="form_name"...> creará un objeto document.form_name, y los controles de dicho formulario se pueden acceder de la forma document.form_name.field_name, en especial es de interés su valor con la propiedad document.form_name.field_name.value.

La validación se puede realizar en distintos momentos: cuando el usuario cambia el valor de un control (onchange) o cuando intenta enviar el formulario. En el ejemplo del js_web_form_validation se hace de la segunda forma, interceptando el evento onsubmit del formulario (línea 2). Nótese que se emplea la palabra reservada return al invocar la función validadora. Si esta función retorna true, el navegador continúa el envío de datos al servidor; pero si retorna false, el manejo del evento se interrumpe, evitando que el formulario sea enviado al servidor.

<body>
    <form name="login" method="post" action="login3.php" onsubmit="return validate()">
        <table>
            <tr>
                <th><label for="username">Usuario:</label></th>
                <td><input type="text" name="username" id="username"/></td>
            </tr>
            <tr>
                <th><label for="password">Contraseña:</label></th>
                <td><input type="password" name="password" id="password"/></td>
            </tr>
            <tr>
                <td colspan="2"><input type="submit" value="Enviar"/>
                <input type="reset" value="Limpiar"/></td>
            </tr>
        </table>
    </form>
    <script type="text/javascript">
    <!--
        function validate()
        {
            var result = '';
            result += validateUsername(document.login.username.value);
            result += validatePassword(document.login.password.value);

            if ( result == '' ) return true;

            alert(result);
            return false;
        }

        function validateUsername(value)
        {
            // Quitar espacios en blanco (trim)
            value = value.replace(/^\s+|\s+$/g, '');

            // El nombre es obligatorio
            if ( value == '' ) return 'Especifique el nombre de usuario\\n';

            // Impedir caracteres especiales
            if ( /[ !"\#\$%&'\(\)\*\+\,\-\.\/\:;<=>\?@\[\]\^\`\{\|\}\~\\\\]/.test(value) )
                return 'No utilice caracteres especiales en el nombre\\n';

            return '';
        }

        function validatePassword(value)
        {
            // Quitar espacios en blanco (trim)
            value = value.replace(/^\s+|\s+$/g, '');

            // La contraseña es obligatoria
            if ( value.length < 8 ) return 'La contraseña debe ser al menos de 8 caracteres\\n';

            // Debe tener mayusculas, minusculas y numeros
            if ( ! /[a-z]/.test(value) || ! /[A-Z]/.test(value) || ! /[0-9]/.test(value) )
                return 'La contraseña debe tener mayúsculas, minúsculas y números';

            return '';
        }
    -->
    </script>
</body>
Validación de dos campos de un formulario utilizando JavaScript. Correr este ejemplo.

La validación se realiza probando condiciones contra los datos provistos por el usuario. El código del js_web_form_validation emplea expresiones regulares para hacer más sencilla la programación. Una expresión regular en JavaScript se escribe entre dos caracteres slash (/ /) que no están seguidos (de lo contrario formarían un comentario). Internamente JavaScript crea un objeto RegExp que provee el método test(str), el cual recibe una cadena de caracteres, y si logra encontrar la expresión regular dentro de ella, retorna true. Por su parte, el método str.replace(/exp/g, text) busca todos los textos de str que cumplen la expresión regulgar exp y los reemplaza por text. El estudio de la nomenclatura de las expresiones regulares se deja como ejercicio para el lector.

Ejercicio. Utilizando JavaScript deshabilite el botón de enviar en su formulario de consulta de días feriados, hasta que el usuario provea un año o un rango de años válidos.

Ejercicio. Utilizando JavaScript provea retroalimentación visual en tiempo real al visitante. Resalte en rojo el valor de un campo cuando se escribe en él un año fuera de rango. Inmediatamente después de que el visitante haya corregido el año, debe regresar al color normal del campo, por ejemplo, negro.

Evaluación

Comprima los archivos que ha generado durante el laboratorio (.php, .js) y súbalos a la plataforma educativa (Moodle) de la ECCI, en la asignación con título Tarea13.