Estos códigos se corresponden con ejemplos que se exponen en los capítulos V y VI, sobre la validación de datos recibidos de formularios.

form1.php

<?php
/* Ejemplo de un script PHP para validar formulario
 * Andrés de la Paz © 2010
 * http://www.wextensible.com
 */
//Necesitamos sólo cookies para la sesión
ini_set("session.use_cookies", 1);
ini_set("session.use_only_cookies", 1);
ini_set("session.use_trans_sid", 0); 
//Damos un nombre a estas sesiones e iniciamos sesión
define("SESION", "ejemploValidaForm1");
session_name(SESION);
session_start();
//Esta constante da el nombre al campo identificador de formulario.
define("IDENTIFICAFORM", "identifica-form1");
//Comprobamos si la sesión ya fue iniciada
$hay_sesion = (isset($_SESSION["ident1"]) && isset($_SESSION["ident2"]));
//Comprobar si entramos con POST
$hay_post = isset($_POST[IDENTIFICAFORM]);
//En la página hay un segundo formulario con un único botón para finalizar
//la sesión y reiniciar el formulario de nuevo
$finaliza_sesion = isset($_POST["fin-sesion"]);
//para identificar el usuario en "ident1"
$usuario_identificado = false;
//para identificar el formulario en "ident2"
$form_identificado = false;
//Para detectar la primera vez que entramos en el formulario
$hay_regeneracion = false;
//Nos dice si los campos del formulario se han validado correctamente
$form_validado = false;
//En caso de validarse se mantiene "", en otro caso contiene un mensaje con
//los errores de validación
$mensaje_validar = "";
//Incluimos aquí el script que valida el formulario
include "valida-form.php";
//Array con la definición y valores de campos
$array_campos = array(
IDENTIFICAFORM => array(
    "valor" => "",
    "titulo" => IDENTIFICAFORM,
    "tipo" => IDENTIFICAFORM, 
    "longitud-min" => 32,    
    "longitud-max" => 32,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "hidden",
        "class" => PREFIJOVAL
        )
    ), 
"nombre" => array(
    "valor" => "",
    "titulo" => "Nombre",
    "tipo" => "nombre de persona", 
    "longitud-min" => 0,
    "longitud-max" => 40,
    "requerido" => false,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),    
"user" => array(
    "valor" => "",
    "titulo" => "Usuario",
    "tipo" => "usuario", 
    "longitud-min" => 4,    
    "longitud-max" => 10,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),
"pwd" => array(
    "valor" => "",
    "titulo" => "Contraseña",
    "tipo" => "contraseña", 
    "longitud-min" => 4,    
    "longitud-max" => 10,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "password",
        "class" => PREFIJOVAL
        )
    )
);
//Construimos el array de patrones para esta definición
contruir_array_patrones($array_campos);
//Iniciamos el proceso de control de sesión
if (!$hay_sesion || ($hay_sesion && !$hay_post) || $finaliza_sesion) {
    //Con sesiones no iniciadas, iniciadas sin post o finalizadas
    $hay_regeneracion = true;
    session_regenerate_id(true);
    $_SESSION["ident1"] = md5($_SERVER["HTTP_USER_AGENT"]);
    $_SESSION["ident2"] = md5(uniqid(rand(), true));
    //Guardamos el valor del identificador de formulario en el array
    $array_campos[IDENTIFICAFORM]["valor"] = $_SESSION["ident2"];
} else {//Con sesiones iniciadas y con POST
    //¿es el mismo usuario?
    if (isset($_SERVER["HTTP_USER_AGENT"])) $usuario_identificado = 
                ($_SESSION["ident1"] == md5($_SERVER["HTTP_USER_AGENT"]));
    //Si se identificó el navegador, vemos si hay POST recibido
    if ($usuario_identificado){
        //Recibimos los campos y nos devuelve si se identificó el formulario
        $form_identificado = recibir_campos($_POST, $array_campos, $_SESSION["ident2"]);
        if ($form_identificado){
            //Validamos los campos del formulario
            $mensaje_validar = validar_form($array_campos);
            if ($mensaje_validar == "") {
                //Si no hay mensajes es que el formulario es válido
                $form_validado = true;
                //En ese caso destruimos la sesión actual y la cookie del navegador
                //pues los datos validados serán enviados a otro proceso (aunque
                //en este ejemplo se limita a presentarlos otra vez
                $_SESSION = array();
                if (ini_get("session.use_cookies")) {
                    $params = session_get_cookie_params();
                    setcookie(SESION, '', time() - 42000,
                        $params["path"], $params["domain"],
                        $params["secure"], $params["httponly"]
                    );
                }
                session_destroy(); 
                //Aquí iría el proceso de destinar los datos validados en $array_campos
                //a algún sitio (archivo, base de datos, etc) y luego redirigiría a
                //algúna otra página para seguir algún otro proceso. En este ejemplo
                //volvemos a presentar esta página presentando los datos en pantalla. 
            }
        }
    }
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
<head>
    <title>Validar formulario</title>
    <meta http-equiv="X-UA-Compatible" content="IE=8" />    
    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
    <meta name="author" content="Andrés de la Paz" />
    <meta name="copyright" content="© 2010" />
    <link rel="stylesheet" type="text/css" href="/res/sty/formatos.css" />
    <script type="text/javascript" src="/res/inc/general.js" charset="ISO-8859-1"></script>
    <script type="text/javascript" src="valida-form.js" charset="UTF-8"></script>    
    <?php if (!$form_validado) echo construir_array_js(); ?>
    <script>
        //Esta función rellena ejemplos en los campos para probar la validación
        function rellenarEjemplo(){
            try {
                var formulario = document.forms[0];
                if (formulario != null) {
                    formulario["nombre"].value = "Juan";
                    formulario["user"].value = "usuario";
                    formulario["pwd"].value = "a12_B";
                }                
            } catch (e) {}
        }    
    </script>        
</head>
<body>
    <a href="http://www.wextensible.com"><img src="/res/img/wextensible.gif" 
    width="41" height="40" style="border: gray solid 1px;" alt="wextensible" 
    title="www.wextensible.com" /></a>
    <h3>Validación de formulario 1</h3>
    <?php 
    if ((!$form_identificado)||($form_identificado && !$form_validado)) {
        ?>
        <p>Se necesitan cookies para recibir este formulario.</p>
        <noscript>
            <p class="verde">Se recomienda activar Javascript para facilitar el 
            uso de este formulario.</p>
        </noscript>      
        <?php
        if (!$form_identificado && !$hay_regeneracion){
            echo "<p style='color: red'>El formulario no se pudo identificar. Por ".
            "favor, reinicie el formulario.</p>";
        } else {
            echo construir_form($array_campos, $mensaje_validar);
        } ?>    
        <form  action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="post">
            <input type="submit" name="fin-sesion" value="reiniciar formulario" />
        </form>            
    <?php
    } else {
        echo "<p style='color: green'>SE PROCESAN LOS DATOS RECIBIDOS:</p>";
        //Se procesan los datos solo si se identificó el usuario y el formulario
        ?>
        <p>En este ejemplo se presentan los datos en esta misma página, pero
        en un caso real quizás se enviarían los datos a otro proceso. La siguiente
        lista se obtiene con la función <code>listar_campos()</code>:
        </p>
        <?php echo listar_campos($array_campos, "ol"); ?>
        <p>También podemos obtener un array de campos con la función
        <code>extraer_valores()</code>. Así podemos manejar este array y pasarlo
        a algún script que trate sus valores, en lugar de manipular el array
        <code>$_POST</code>:
        </p>
        <pre><?php echo print_r(extraer_valores($array_campos)); ?></pre>
    <?php } ?>    
    <br />
    <!-- Este código dentro de este div sólo es para explicar el proceso -->
    <div style="color: gray; border: gray solid 1px;">
        <?php if (!$form_validado) { ?>
        <h3>Utilidades para probar este ejemplo</h3>
        <p><input type="checkbox" id="no-valida-js" />
        Podemos saltar la validación en JavaScript en el navegador del usuario
        con esta casilla de verificación</p>
        <p><input type="button" value="rellenar ejemplo"
        onclick="rellenarEjemplo('form-validar-1')" />
        Con este botón rellenamos los campos con un ejemplo para probar.
        </p>
        <?php } ?>
        <h3>Valores de configuración</h3>
        <ul>
            <li>Nombre sesión <code>session_name()</code>: <code class="verde">
            <?php echo session_name(); ?>
            </code></li>
            <li>Identificador sesión <code>session_id()</code>: <code class="verde">
            <?php echo session_id(); ?>
            </code></li>
            <li>Cookie con el identificador de sesión <code>$_COOKIE["<?php echo SESION; ?>"]</code>:
            <code class="verde">
            <?php if (isset($_COOKIE[SESION])) {
                echo htmlspecialchars($_COOKIE[SESION]);
            } else {
                echo "<span class='rojo'>NO EXISTE</span>";
            }
            ?>
            </code></li>            
            <li>Identificador navegador usuario <code>$_SESSION["ident1"]</code>:<code class="verde"> 
            <?php if (isset($_SESSION["ident1"])) {
                echo $_SESSION["ident1"]; 
            } else {
                echo "<span class='rojo'>NO EXISTE</span>";
            }
            ?>
            </code></li>
            <li>Navegador del usuario <code>$_SERVER["HTTP_USER_AGENT"]</code>:<code class="verde"> 
            <?php if (isset($_SERVER["HTTP_USER_AGENT"])) {
                echo $_SERVER["HTTP_USER_AGENT"]; 
            } else {
                echo "<span class='rojo'>NO EXISTE</span>";
            }
            ?>
            </code></li>            
            <li>Identificador formulario <code>$_SESSION["ident2"]</code>:<code class="verde"> 
            <?php if (isset($_SESSION["ident2"])) {
                echo $_SESSION["ident2"]; 
            } else {
                echo "<span class='rojo'>NO EXISTE</span>";
            }
            ?>
            </code></li>
        </ul>
        <h3>Cookies en el navegador</h3>
        <p>Busca en el navegador con JavaScript cookies con nombres
        <code>"<?php echo SESION; ?>"</code>.
        </p>    
        <code id="mi-cookie" style="border: red solid 1px;">&nbsp;</code>
        <script>
            var arrayCookies = document.cookie.split(";");
            var contenido = "";
            for (var i=0; i<arrayCookies.length; i++){
                var unaCookie = arrayCookies[i].split("=");
                var nombre = quitaEspacios(unaCookie[0]);
                if (nombre == "<?php echo SESION; ?>") {
                   contenido += escapaHtml(arrayCookies[i]) + "; ";
                }
            }
            if (contenido == "") contenido = "No hay cookies en el navegador.";        
            setInnerText(document.getElementById("mi-cookie"), contenido);
        </script>
        <br />
    </div>
</body>
</html>

valida-form.php

<?php
/* Script PHP para validar campos de formulario
 * Andrés de la Paz © 2010
 * http://www.wextensible.com
 */

/* Constante para incluir un asterisco u otra cosa para indicar los campos 
 * de formulario que son requeridos. Si no se desea, poner un campo vacío "".
 */
define("ASTERISCO", "<span style=\"color:red\">*</span>");

/* Un caracter para separar la cadena tipo#[requerido|opcional]#longitud en el title del
 * campo al construir el HTML. No debe ser un espacio, pues el tipo puede 
 * ser una frase para comunicarle al usuario el tipo de campo que se espera.
 */
define("SEPARA", "#");

/* Esta cadena la incorporaremos en la matriz de definición de campos dentro del
 * atributo "class" de los elementos que se construirán dinámicamente. Los campos
 * que lleven este valor de atributo serán validados.
 */
define("PREFIJOVAL", "val-");

/* El array con los patrones base para validar los campos. El tipo puede ser una frase
 * pero no debe contener el carácter señalado en la constante SEPARA. Es obvio que
 * el tipo es un clave para indexar el array, por lo que no habrán duplicados.
 * En los patrones deben escaparse las barras invertida y las comillas y NO deben
 * añadirse los caracteres de inicio y fin de patrón (usualmente las barras derecha).
 */
$patrones_tipos_base = array(
IDENTIFICAFORM => array(
    "patron" => "^[a-zA-Z0-9]+$",
    "mensaje" => "Identificador de formulario no válido."),
"número natural" => array(
    "patron" => "^(?:0|[1-9]\\d*)$",
    "mensaje" => "Debe ser un número natural (no negativo)."),        
"número entero" => array(
    "patron" => "^(?:0|\\-?[1-9]\\d*)$",
    "mensaje" => "Debe ser un número entero."),
"número real" => array(
    "patron" => "^(?:0|\\-?[1-9]\\d*)(?:\\.\\d+)?$",
    "mensaje" => "Debe ser un número natural, entero o real."),
"usuario" => array(
    "patron" => "^\\w+$",
    "mensaje" => "Un nombre de usuario debe contener letras a-z, A-Z, dígitos ".
    "0-9 o guión bajo."),
"contraseña" => array(
    "patron" => "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])\\w+$",
    "mensaje" => "Una contraseña debe contener de letras a-z, A-Z, dígitos 0-9 ".
    "o guión bajo, pero debe tener al menos 1 letra minúscula, 1 mayúscula y 1 dígito."),
"DNI" => array(
    "patron" => "^[1-9]\d*[A-Z]$",
    "mensaje" => "Debe contener de 1 a 8 dígitos numéricos y una letra mayúscula."),
"teléfono" => array(
    "patron" => "^\\d+$",
    "mensaje" => "Debe contener dígitos numéricos."),
"email" => array(
    "patron" => "^\\w+(?:[\\-\\.]?\\w+)*@\\w+(?:[\\-\\.]?\\w+)*(?:\\.[a-zA-Z]{2,4})+$",
    "mensaje" => "Debe ser de la forma 'xx@xx.yy' donde en 'xx' se permiten dígitos ".
    "númericos, letras, guiones y puntos, mientras que en 'yy' debe contener uno o más ".
    "dominios separados por punto, pero cada uno de ellos debe tener entre 2 y 4 letras."),
"verificación" => array(
    "patron" => "^si$",
    "mensaje" => "Casilla de verificación con un valor no esperado."),
"nombre de persona" => array(
    "patron" => "^[A-ZÑÇÁÉÍÓÚ][a-zA-ZñÑçÇáéíóúüÁÉÍÓÚÜ \\-]+$",
    "mensaje" => "Un nombre de persona empieza con mayúscula y debe contener letras ".
    "a-zA-ZñÑçÇáéíóúüÁÉÍÓÚÜ, espacios o guiones -."),
"texto simple" => array(
    "patron" => "^[\\wñÑçÇáéíóúüÁÉÍÓÚÜ \\-\\.\\,\\?¿\\!¡]*$",
    "mensaje" => "Debe contener texto limitado a las letras a-z o A-Z incluyendo ".
    "ñ, Ñ, ç, Ç y vocales con tildes y los signos _-.,¿?¡!"),
"texto Latín-B" => array(
    "patron" => "^[ -ɏ]*$",
    "mensaje" => "El texto debe contener caracteres permitidos hasta Latín extendido B (u0020-u024F)")
);

/* Los patrones base anteriores son todos los que existen. Para el uso del formulario
 * donde se aplique, se extraerán sólo los que se necesiten usando la función
 * construir_array_patrones()
 */
$patrones_tipos = array();

/* Construye un array de patrones a usar en el formulario definido en
 * el array del argumento $array_campos. Se extraen del array de 
 * patrones base ($array_patrones_base) usando sólo los que se
 * necesiten. Así evitamos manejar un array muy grande y, sobre todo,
 * no traspasamos a Javascript aquellos que no se usarán. 
 */
function contruir_array_patrones($array_campos){
    global $patrones_tipos_base;
    global $patrones_tipos;
    $arr_tipos = array();
    foreach ($array_campos as $arr){
        $tipo = $arr["tipo"];
        if (($tipo != "valor")&&($tipo != "selector")&&
            ($tipo != "menú")&&
            (!in_array($arr["tipo"], $arr_tipos))){
            $arr_tipos[] = $arr["tipo"];
        }
    }
    foreach ($arr_tipos as $tipo){
        $patron = $patrones_tipos_base[$tipo]["patron"];
        $mensaje = $patrones_tipos_base[$tipo]["mensaje"];
        $patrones_tipos[$tipo] = array("patron"=>$patron, "mensaje"=>$mensaje);
    }
}

/* Esta función nos permite construir un elemento <script> para incluir en el HTML
 * con el array $patrones_tipos para validar con JavaScript.
 */
function construir_array_js(){
    global $patrones_tipos;
    $cadena = "";
    foreach ($patrones_tipos as $tipo => $arr){
        if ($cadena != "") $cadena .= ",";
        $cadena .= "\"".$tipo."\",/".$arr["patron"]."/,\"".$arr["mensaje"]."\"";
    }
    $cadena = "<script type=\"text/javascript\">\r\n".
                  "//<![CDATA[\r\n".
                    "var patronesTipos = Array(".$cadena.");\r\n".
                  "//]]>\r\n".
              "</script>\r\n";
    return $cadena;
}


/* Con esta función recibimos los campos desde $_POST a través del primer
 * argumento $array_post. En $array_campos, que se pasa por referencia
 * pues podrían darse modificaciones, pasamos el array con la definición
 * de campos. En $ident_form pasamos el identificador de formulario que
 * está en una variable de sesión $_SESSION para identificar el
 * formulario con un campo definido en $array_campos que se llama
 * como la constante IDENTIFICAFORM.
 */
function recibir_campos($array_post, &$array_campos, $ident_form){
    //Esta variable será devuelta al final para indicar si se
    //identificó el formulario
    $form_identificado = false;
    //Vemos si tiene magic_quotes activado el servidor
    $magicq = (get_magic_quotes_gpc() == 1);
    //Array temporal para control de casillas de verificación (checkbox)
    $array_check = array();   
    foreach($array_post as $campo=>$valor){
        $un_campo = htmlspecialchars($campo, ENT_QUOTES);
        if ($magicq) $un_campo = stripslashes($un_campo); 
        $un_valor = htmlspecialchars($valor, ENT_QUOTES);
        if ($magicq) $un_valor = stripslashes($un_valor);  
        //Vemos si el nombre del campo está en las entradas del array de
        //definiciones de campos $array_campos                     
        if (array_key_exists($un_campo, $array_campos)){
            //Buscamos el campos identificador de formulario. Si el valor
            //no coincide con el de la sesión, damos por no identificado al
            //formulario y salimos inmediatamente del bucle.
            if ($un_campo == IDENTIFICAFORM){
                $form_identificado = ($un_valor == $ident_form);
                if (!$form_identificado) break;                       
            }
            //Por seguridad, se cortan los datos al doble de lo esperado. Luego
            //se pasará la validación y se comprueba si tienen la longitud esperada.
            $longitud_doble = 2 * $array_campos[$un_campo]["longitud"];
            $array_campos[$un_campo]["valor"] = substr($un_valor, 0, $longitud_doble);
            //Si el campo recibido es una casilla de verificación, la guardamos en
            //un array temporal al mismo tiempo que ponemos el atributo "checked"
            if (($array_campos[$un_campo]["tag"] == "input")&&
                ($array_campos[$un_campo]["atributos"]["type"] == "checkbox")){
                //Guarda en array temporal los checkbox recibidos
                $array_check[] = $un_campo;
                //Si llega un checkbox, es que se envió pulsado
                $array_campos[$un_campo]["atributos"]["checked"] = "checked";
            }
        } else if (($campo != "envio")&&($valor != "enviar")) {
            //Si se recibe algún campo no esperado (que no esté en el array de
            //definiciones), inmediatamente damos por no identificado el formulario.
            //A no ser que el usuario haya desactivado JavaScript, lo que causará que
            //se remita también el botón de envío, botón que no se recibe con Javascript
            //debido a que el form tiene un onsubmit que lo omite. De todas formas 
            //en este caso sólo permitiremos este campo "extra".
            $form_identificado = false;
            break;
        }
    }
    //Ahora repasamos el array temporal de casillas de verificación para analizar
    //cuáles no se recibieron y en consecuencia desactivaríamos el atributo "checked"
    foreach ($array_campos as $campo => $arr){
        if (($arr["tag"] == "input") &&
            ($arr["atributos"]["type"] == "checkbox") && 
            (!in_array($campo, $array_check))){
            $array_campos[$campo]["atributos"]["checked"] = "";
        }
    }
    //Devolvemos este booleano que indica si se identificó el formulario 
    return $form_identificado;
}


/* Aquí validamos los campos del formulario. Tomamos el array $patrones_tipos
 * definido como variable global e iteramos por $array_campos donde traemos
 * el array de definición de campos, que también contiene los valores, para
 * entonces proceder a validarlos con los patrones. Pasamos el array por valor
 * pues aquí no necesitamos modificarlo.
 * Recordemos que el tipo "selector" es para elementos <input type="radio"> y
 * que el tipo "menú" es para elementos <select><option>...</select>
 */
function validar_form($array_campos){
    //En esta variable está la lista de patrones
    global $patrones_tipos;
    //Aquí concatenaremos todos los errores
    $mensaje = "";    
    //Iteramos por el array de campos que contiene tipos y valores
    foreach ($array_campos as $campo => $arr){
        //Mensaje de longitud
        $mensaje_longitud = "(";
        if ($arr["longitud-max"] == $arr["longitud-min"]){
            $mensaje_longitud .= $arr["longitud-max"];
        } else {
            $mensaje_longitud .= "de ".$arr["longitud-min"]." a ".$arr["longitud-max"];
        }
        $mensaje_longitud .= " caracteres)";    
        //Si la clase es PREFIJOVAL, no se validará el campo
        if ($arr["atributos"]["class"] == PREFIJOVAL){
            //Si el tipo no es "valor", "selector" o "menú", entonces debe
            //estar entre los tipos de la lista de patrones
            if (($arr["tipo"] != "valor") &&
                ($arr["tipo"] != "selector") &&
                ($arr["tipo"] != "menú") &&             
                (!array_key_exists($arr["tipo"], $patrones_tipos))){
                $mensaje .= "<li>Falta el tipo "."'".$arr["tipo"].
                "' para el campo '".$arr["titulo"]."'.</li>";
            } else {
                //Verificamos la longitud del campo
                $longitud = strlen($arr["valor"]);
                if (($longitud < $arr["longitud-min"]) || ($longitud > $arr["longitud-max"])) {
                    $mensaje .= "<li>El tamaño del valor del  campo '".$arr["titulo"]."' es ";
                    if ($longitud > $arr["longitud-max"]) {
                        $mensaje .= "mayor que el esperado ".$mensaje_longitud.".</li>";
                    } else {
                        $mensaje .= "menor que el esperado ".$mensaje_longitud.".</li>";
                    }
                } else {
                    //Si el tipo es "selector" o "menú", verificamos que el
                    //valor seleccionado está en la lista de opciones
                    if ((($arr["tipo"] == "selector")||($arr["tipo"] == "menú")) &&
                        (!in_array($arr["valor"], $arr["opciones"]))){
                        $mensaje .= "<li>El campo '".$arr["titulo"].
                        "' contiene un valor no esperado '".$arr["valor"].
                        "' ".$mensaje_longitud."</li>";                         
                    }
                    //Verificamos si el valor es requerido y que no esté vacío
                    if ($arr["requerido"] && ($arr["valor"] == "")){
                        $mensaje .= "<li>El valor del campo '".$arr["titulo"].
                        "' es requerido ".$mensaje_longitud."</li>";                
                    } else if (($arr["tipo"] != "valor") &&
                              ($arr["tipo"] != "selector") &&
                              ($arr["tipo"] != "menú") &&                         
                              ($arr["requerido"] ||
                              (!$arr["requerido"] && $arr["valor"] != ""))){
                        //Verificamos el tipo con los patrones
                        $patron = "/".$patrones_tipos[$arr["tipo"]]["patron"]."/";
                        if (!preg_match($patron, $arr["valor"])){
                            $mensaje .= "<li>Error en el campo '".$arr["titulo"]."'".
                            ": ".$patrones_tipos[$arr["tipo"]]["mensaje"].
                            " ".$mensaje_longitud."</li>";
                        }
                    }
                }   
            }
        }
    }
    //Si hay entradas de mensaje, componemos el resto
    if ($mensaje != "") {
        $mensaje = "<p>Hay errores con la validación de este formulario:</p>".
        "<ol>$mensaje</ol>";
    }
    //Devolvemos el mensaje, vacío o lleno. Si está vacío es que NO
    //se produjeron errores en la validación.
    return $mensaje;
}


/* Usando el array de definiciones y valores de campos $array_campos (por valor)
 * procedemos a construir los campos interiores al elemento <form>.
 * Recordemos que el tipo "selector" es para elementos <input type="radio"> y
 * que el tipo "menú" es para elementos <select><option>...</select>
 */
function construir_form($array_campos){
    //En esta cadena vamos concatenando todo el literal HTML
    $cadena = "<form  action=\"".$_SERVER["PHP_SELF"]."\" method=\"post\" ".
    "onsubmit=\"return validaForm(this)\">"; ;
    foreach ($array_campos as $campo => $arr){
        //contenedor que encierra cada campo
        $contenedor = "label";
        $atributos_contenedor = "";
        //elemento que encierra el texto de la etiqueta de cada campo
        $etiqueta = "span";
        if ($arr["tipo"] == "selector"){
            //Para los <input type="radio"> el contenedor es un <fieldset>
            $contenedor = "fieldset";
            if ($arr["estilo-fieldset"] != ""){
                $atributos_contenedor .= " style=\"".$arr["estilo-fieldset"]."\"";
            }
            if ($arr["clase-fieldset"] != "") {
                $atributos_contenedor .= " class=\"".$arr["clase-fieldset"]."\"";
            }
            //La etiqueta serán un <legend> propio del <fieldset>
            $etiqueta = "legend";
        }
        $cadena .= "<".$contenedor.$atributos_contenedor.">";
        $cadena .= "<".$etiqueta.">";
        //Agregamos lo de campo requerido, con constante ASTERISCO
        if (!(($arr["tag"] == "input")&&($arr["atributos"]["type"] == "hidden"))) {
            $cadena .= $arr["titulo"];
            if ($arr["requerido"] &&
                (($arr["tag"] == "textarea") ||
                (($arr["tag"] == "input") && 
                (($arr["atributos"]["type"] == "text") ||
                ($arr["atributos"]["type"] == "password"))))){
                $cadena .= ASTERISCO;   
            }
            $cadena .= ": ";
            //Con <textare> y <select> de lista no desplegable, 
            //agregamos un salto
            if (($arr["tag"] == "textarea") ||
               (($arr["tipo"] == "menú") && ($arr["atributos"]["size"] > 1))) {
                $cadena .= "<br />";
            }
        }
        //Cerramos la etiqueta
        $cadena .= "</".$etiqueta.">";
        //Declaramos una array temporal para contener todos los elementos de un
        //tipo selector (<input type="radio">) o elementos <option> de un tipo menú
        //(elemento <select>).
        $tages = array();
        if (($arr["tipo"] == "selector") || ($arr["tipo"] == "menú")){
            foreach ($arr["opciones"] as $clave => $valor){
                $tages[$clave] = $valor;
            }
            if ($arr["tipo"] == "menú"){
                //Ponemos el tag <select> para elementos tipo "menú"
                $cadena .= "<select name=\"".$campo."\"";
                //Ponemos en el atributo title el tipo, requerido y longitud
                $cadena .= " title=\"".$arr["tipo"];
                if ($arr["requerido"]) {
                    $cadena .= SEPARA."requerido";   
                } else {
                    $cadena .= SEPARA."opcional";                
                }
                $cadena .= SEPARA.$arr["longitud-min"].SEPARA.$arr["longitud-max"]."\"";;
                //Ponemos los atributos específicos para este campo
                $attr = $arr["atributos"];
                foreach ($attr as $atributo => $valorAtributo){
                    $cadena .= " ".$atributo."=\"".$valorAtributo."\"";                        
                }
                $cadena .= ">";
            }
        } else {
            //Si no es <input type="radio"> o <option> para <select>, sólo ponemos 
            //un item en el array temporal
            $tages["sin"] = $arr["valor"];
        }
        //Iteramos por el array temporal para construir elementos
        foreach ($tages as $titulo_r => $valor_r){
            if ($arr["tipo"] == "menú"){
                //Elementos <option> se tratan aparte
                $cadena .= "<".$arr["tag"]." value=\"".$valor_r."\"";
                if ($valor_r == $arr["valor"]){
                    $cadena .= " selected=\"selected\"";
                }
                $cadena .= ">".$titulo_r."</".$arr["tag"].">";
            } else {
                //En elementos <input type="radio"> agregamos su título
                if ($arr["tipo"] == "selector"){
                    $cadena .= "<label><span>".$titulo_r.":</span>";
                }
                //Ponemos el tag del campo
                $cadena .= "<".$arr["tag"]." name=\"".$campo."\"";
                //Ponemos en el atributo title el tipo, requerido y longitud
                $cadena .= " title=\"".$arr["tipo"];
                if ($arr["requerido"]) {
                    $cadena .= SEPARA."requerido";   
                } else {
                    $cadena .= SEPARA."opcional";                
                }
                $cadena .= SEPARA.$arr["longitud"]."\"";
                //Ponemos los atributos específicos para este campo
                $attr = $arr["atributos"];
                foreach ($attr as $atributo => $valorAtributo){
                    if (($atributo != "checked")||
                        (($atributo == "checked")&&($valorAtributo == "checked"))){
                        $cadena .= " ".$atributo."=\"".$valorAtributo."\"";                        
                    }
                }
                //para los <input type="text"> agrega size y maxlength
                //según longitud
                if (($arr["tag"] == "input") && 
                   (($arr["atributos"]["type"] == "text")||
                   ($arr["atributos"]["type"] == "password"))){
                    $cadena .= " size=\"".$arr["longitud-max"].
                        "\" maxlength=\"".$arr["longitud-max"]."\"";
                }
                //para <input type="radio">, pone checked al seleccionado
                if (($arr["tipo"] == "selector") && ($arr["valor"] == $valor_r)) {
                    $cadena .= " checked=\"checked\"";
                }                    
                //Ponemos el valor y cerramos el tag
                if ($arr["tag"] == "input"){
                    if ($arr["atributos"]["type"] == "password"){
                        //No devolvemos password al usuario cuando hay un error
                        //de validación para evitar que fluya en exceso
                        $cadena .= " value=\"\" />";                        
                    } else {
                        $cadena .= " value=\"".$valor_r."\" />";
                    }
                } else {
                    $cadena .= ">".$valor_r."</".$arr["tag"].">";
                }
                //Para los <input type="radio"> añadimos separador                
                if ($arr["tipo"] == "selector") {
                    $cadena .= "</label>".$arr["separador-selector"];
                }
            }
        }
        //Cerramos el <select> abierto si es un tipo "menú"
        if ($arr["tipo"] == "menú") $cadena .= "</select>";
        //Cerramos el contenedor, un <label> o un <fieldset>
        $cadena .= "</".$contenedor.">";
        //Si el contenedor es un <label> agregamos un salto de línea
        if ($contenedor == "label") $cadena .= "<br />";
    }
    //Agregamos un pie de información sobre requeridos
    if (ASTERISCO != "") {
        $cadena .= "<div style=\"font-family: Arial Narrow\">".
                   "Los campos marcados con ".ASTERISCO.
                   " son requeridos.</div>";
    }
    //Agregamos botones de submit (no reset). NOTA: El name y value son
    //importantes, porque se detectan en validar_form() cuando no está
    //activado Javascript. Si se modifica aquí, tenerlo en cuenta allí.
    $cadena .= "<input type=\"submit\" name=\"envio\" value=\"enviar\" />";
    //Agregamos el mensaje de validar y cerramos form
    $cadena .= "<div style=\"color: red\">".$mensaje_validar."</div>";
    $cadena .= "</form>";   
    //Devolvemos la cadena literal HTML
    return $cadena;
}


/* Esta función nos extrae los nombres de campos y valores a partir
 * del array de definición y valores $array_campos. Así el array retornado
 * podrá ser usado en procesos posteriores después de haber sido validado. 
 */
function extraer_valores($array_campos){
    $array_valores = array();
    foreach ($array_campos as $campo => $arr){
        $cadena = "";
        if (($arr["tag"] == "input")&&($arr["atributos"]["type"] == "checkbox")){
            if ($arr["atributos"]["checked"] == "checked"){
                $cadena .= "si";   
            } else {
                $cadena .= "no"; 
            }
        } else {
            $cadena .= $arr["valor"];
        }        
        $array_valores[$campo] = $cadena;
    }
    return $array_valores;
}


/* Esta función lista los campos y valores en un formato de presentación.
 */
function listar_campos($array_campos, $tag){
    $cadena = "<".$tag.">";
    foreach ($array_campos as $campo => $arr){
         if (!(($arr["tag"] == "input")&&($arr["atributos"]["type"] == "hidden"))) {
            $cadena .= "<li>".$arr["titulo"].": <span style=\"color: blue\">";                    
            if (($arr["tag"] == "input")&&($arr["atributos"]["type"] == "checkbox")){
                if ($arr["atributos"]["checked"] == "checked"){
                    $cadena .= "si";   
                } else {
                    $cadena .= "no"; 
                }
            } else {
                $cadena .= $arr["valor"];
            }
            $cadena .= "</span></li>";                    
        }
    }    
    return $cadena."</".$tag.">";
}

?>

valida-form.js

/* JavaScript para validar los campos de un formulario
 * Andrés de la Paz ©2010
 * http://www.wextensible.com
 * 
 * La validación de campos en el navegador es equivalente a la que se realiza
 * en el servidor con valida-form.php, usando el mismo conjunto de patrones
 * de expresiones regulares. Este array de patrones se encuentra en un
 * script a la cabecera del documento que porta el formulario.
 * 
 * En PHP se usa la función valida_form(), pero aquí desglosamos el proceso
 * en dos partes. En primer lugar la función validaForm() se encarga
 * de extraer un campo de la matriz de controles del formulario. Si la clase
 * empieza por PREFIJOVAL será validado. Entonces obtenemos los atributos
 * name, value y title. Este último contiene el tipo de patrón, requerido y
 * las longitudes del campo. Con todo esto llamamos a la función para validar
 * validaCampo(campo, valor, titulo, tipo, requerido, longitudMin, longitudMax)
 * que devuelve una lista de mensajes de error o un "" si no hubieron.
 * Finalmente procesamos el mensaje y el envío del formulario en su caso.
 */

/* Este valor permite compararlo con el del atributo "class" para ver
 * si validamos o no el campo.
 */
var PREFIJOVAL = "val-";

/* Aquí validamos los campos del formulario, iterando por los elementos y 
 * llamando a la función validaCampo(). Para ello disponemos en el
 * elemento <form> del atributo onsubmit="return validaForm(this)".
 */
function validaForm(formulario) {
    //Para concatenar mensajes
    var mensaje = "";
    //En el documento hay una casilla de verificación por fuera del 
    //formulario que nos sirve para probar a enviarlo sin que se
    //realice la validación con Javascript. Digamos que sería lo
    //mismo que si el usuario no tuviera activado Javascript.
    //Esto lo podemos suprimir en una versión definitiva.
    var nojs = document.getElementById("no-valida-js");    
    if (!nojs.checked){
        //Obtenemos los campos de la matriz de elementos del formulario
        var campos = formulario.elements;
        //Iteramos por estos campos
        for (var i=0; i<campos.length; i++) {
            //Si la clase empieza por PREFIJOVAL no validamos
            var clase = campos[i].className;
            if (clase.substring(0,4) == PREFIJOVAL){
                //Obtenemos nombre y valor del campo
                var campo = campos[i].name;
                var valor = campos[i].value;
                //El elementoTitulo es un <span> que está inmediatamente
                //antes del campo, por lo que podemos acceder mediante
                //previousSibling. Ahí está el título o etiqueta del
                //campo (no confundir con el atributo title que usamos
                //para portar el tipo, requerido y longitud).
                var titulo = "";
                var elementoTitulo = campos[i].previousSibling;
                if ((elementoTitulo != null) && (elementoTitulo.nodeType == 1) &&
                    (elementoTitulo.tagName.toLowerCase() == "span")) {
                    //La función getInnerText nos permite extraer el texto
                    //del <span>. Es una función que unifica el distinto
                    //comportamiento de navegadores. Se explica en este
                    //enlace de este sitio
                    titulo = getInnerText(elementoTitulo);
                    //Quitamos el asterisco de requerido, dos puntos y espacios
                    titulo = titulo.replace(/\*?\:[ \s]*/g, "");
                }
                //Extraemos tipo, requerido y longitud del atributo title
                var tipo = "valor";
                var requerido = false;
                var longitudMin = 0;
                var longitudMax = 0;
                var cadenaTitle = campos[i].title;
                if (cadenaTitle != ""){
                    var arr = cadenaTitle.split("#");
                    //Si no hay 4 entradas en el array, se quedan el tipo, requerido
                    //y longitud declarados antes
                    if (arr.length == 4){
                        tipo = arr[0];
                        var req = arr[1].toLowerCase();
                        if ((req == "requerido")||(req == "opcional")){
                            requerido = (req == "requerido");
                        }
                        longitudMin = parseInt(arr[2]);
                        longitudMax = parseInt(arr[3]);
                    }
                }
                //validamos el campo
                mensaje += validaCampo(campo, valor, titulo, tipo, requerido, longitudMin,
                           longitudMax);
            }
        }
    }
    //Si hay mensaje
    if (mensaje != "") {
        mensaje = "<p>Hay errores con la validación de este formulario:</p>" +
        "<ol>" + mensaje + "</ol>";
    }
    //Esta parte sirve para alertar de que se está enviando el mensaje
    var mensajeEnvio = "";
    var colorMensaje = "red";
    if (mensaje == ""){
        colorMensaje = "green";
        formulario.envio.value = "Enviando...";
        formulario.envio.disabled = true;
        mensajeEnvio = "Se enviarán los datos del formulario...";
    } 
    //El último elemento del formulario será un <div> donde vamos a
    //poner la lista de mensajes
    var mensajeForm = formulario.lastChild;
    mensajeForm.style.color = colorMensaje;
    mensajeForm.innerHTML = mensaje + mensajeEnvio;
    //Advertimos al usuario que se enviará el formulario dándole la
    //posibilidad de cancelar el envío
    if (mensaje == ""){
        if (window.confirm("¿Enviar mensaje?")){
            return true; 
        } else {
            formulario.envio.value = "enviar";
            formulario.envio.disabled = false;
            mensajeForm.innerHTML = "Envío cancelado";            
            return false;            
        }
    } else {
        return false;
    }

}

/* Se valida un campo de formulario usando el array patronesTipos que está
 * en un script en la cabecera de la página. 
 */
function validaCampo(campo, valor, titulo, tipo, requerido, longitud){
    //Aquí pondremos el mensaje de error
    var mensaje = "";
    //Mensaje de longitud
    var mensajeLongitud = "(";
    if (longitudMax == longitudMin){
        mensajeLongitud += longitudMax;
    } else {
        mensajeLongitud += "de " + longitudMin + " a " + longitudMax;
    }
    mensajeLongitud += " caracteres)";     
    //Verificamos longitud
    if ((valor.length < longitudMin) || (valor.length > longitudMax)){
        mensaje += "<li>El tamaño del valor del  campo '" + titulo + "' es ";
        if (valor.length > longitudMax) {
            mensaje += "mayor";
        } else {
            mensaje += "menor";            
        }
        mensaje += " que el esperado  " + mensajeLongitud +".</li>";
    } else {
        //Verificamos que no esté vacío si es requerido
        var estaVacio = (valor == null || valor.length == 0 || /^\s+$/.test(valor))
        if (requerido && estaVacio) {
            mensaje = "<li>El valor del campo '" + titulo + "' es requerido " +
                      mensajeLongitud + "</li>";
        } else if ((tipo != "valor") && 
                  (tipo != "selector") &&
                  (tipo != "menú") &&                  
                  (requerido || (!requerido && !estaVacio))) {
            //Si es un tipo testamos la expresión regular
            var masMensaje = "";
            var encontrado = false;
            for (var i=0; i<patronesTipos.length; i=i+3){
                if (tipo == patronesTipos[i]){
                    //La funcion test de Javascript verifica el patrón, cuyo
                    //resultado true o false ponemos en esta variable
                    var validado = patronesTipos[i+1].test(valor);
                    //Extraemos el mensaje de error
                    if (!validado) masMensaje = patronesTipos[i+2];
                    encontrado = true;
                    break;
                }                
            }
            //Si el tipo no se encontró en el array de patrones
            if (!encontrado){
                mensaje = "<li>Falta el tipo '" + tipo + "' para el " +
                "campo " + titulo + " " + mensajeLongitud + "</li>";
            } else if (!validado){
                //Si no se validó, formamos el mensaje de error
                mensaje = "<li>Error en el campo '" + titulo + "'. " + 
                          masMensaje + " " + mensajeLongitud + "</li>";
            }
        }
    }
    //devolvemos "" o un mensaje de error
    return mensaje;
}

form2.php

Este código es exactamente igual que form1.php, modificándose sólo en el nombre dado a la sesión y en la definición de los campos particulares para este formulario, que exponemos a continuación. Este ejemplo no acompaña las variables de configuración para ver el ejemplo en funcionamiento que veíamos al pie del ejemplo en form1.php.

...
//Damos un nombre a estas sesiones e iniciamos sesión
define("SESION", "ejemploValidaForm2");
session_name(SESION);
...
//Esta constante da el nombre al campo identificador de formulario.
define("IDENTIFICAFORM", "identifica-form2");
...
//Incluimos aquí el script que valida el formulario
include "valida-form.php";
//Array con la definición y valores de campos
$array_campos = array(
IDENTIFICAFORM => array(
    "valor" => "",
    "titulo" => IDENTIFICAFORM,
    "tipo" => IDENTIFICAFORM,
    "longitud-min" => 32,
    "longitud-max" => 32,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "hidden",
        "class" => PREFIJOVAL
        )
    ),
"numero-natural" => array(
    "valor" => "0",
    "titulo" => "Número natural",
    "tipo" => "número natural",
    "longitud-min" => 1,
    "longitud-max" => 16,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),    
"numero-entero" => array(
    "valor" => "0",
    "titulo" => "Número entero",
    "tipo" => "número entero",
    "longitud-min" => 1,
    "longitud-max" => 16,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),
"numero-real" => array(
    "valor" => "0.0",
    "titulo" => "Número real",
    "tipo" => "número real",
    "longitud-min" => 1,
    "longitud-max" => 32,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ), 
"numero-rango" => array(
    "valor" => "10",
    "titulo" => "Número de 10 a 20",
    "tipo" => "número natural",
    "longitud-min" => 2,
    "longitud-max" => 2,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL,
        "onblur" => "javascript: limitaValor(this, 'entero', 10, 20);"
        )
    ),    
"nombre-apellidos" => array(
    "valor" => "",
    "titulo" => "Nombre y apellidos",
    "tipo" => "nombre de persona",
    "longitud-min" => 7, 
    "longitud-max" => 35,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ), 
"user" => array(
    "valor" => "",
    "titulo" => "Usuario",
    "tipo" => "usuario",
    "longitud-min" => 4, 
    "longitud-max" => 20,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),
"pwd" => array(
    "valor" => "",
    "titulo" => "Contraseña",
    "tipo" => "contraseña",
    "longitud-min" => 4, 
    "longitud-max" => 10,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "password",
        "class" => PREFIJOVAL
        )
    ),              
"dni" => array(
    "valor" => "",
    "titulo" => "D.N.I.",
    "tipo" => "DNI",
    "longitud-min" => 9,
    "longitud-max" => 9,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),
"telefono" => array(
    "valor" => "",
    "titulo" => "Teléfono",
    "tipo" => "teléfono",
    "longitud-min" => 9, 
    "longitud-max" => 9,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),
"ver-email" => array(
    "valor" => "si",
    "titulo" => "Publicar e-mail",
    "tipo" => "verificación",
    "longitud-min" => 2,
    "longitud-max" => 2,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "checkbox",
        "class" => PREFIJOVAL,
        "checked" => ""
        )
    ),
"enviar-email" => array(
    "valor" => "si",
    "titulo" => "Enviar e-mail",
    "tipo" => "verificación",
    "longitud-min" => 2,
    "longitud-max" => 2,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "checkbox",
        "class" => PREFIJOVAL,
        "checked" => "checked"
        )
    ), 
"email" => array(
    "valor" => "",
    "titulo" => "E-mail",
    "tipo" => "email",
    "longitud-min" => 8,
    "longitud-max" => 50,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "text",
        "class" => PREFIJOVAL
        )
    ),
"mensaje" => array(
    "valor" => "",
    "titulo" => "Mensaje",
    "tipo" => "texto simple", 
    "longitud-min" => 1,
    "longitud-max" => 100,
    "requerido" => true,
    "tag" => "textarea",
    "atributos" => array(
        "cols" => "30",
        "rows" => "5",
        "class" => PREFIJOVAL
        )
    ),
"mensaje-2" => array(
    "valor" => "",
    "titulo" => "Mensaje con texto general",
    "tipo" => "texto Latín-B", 
    "longitud-min" => 1,
    "longitud-max" => 150,
    "requerido" => true,
    "tag" => "textarea",
    "atributos" => array(
        "cols" => "45",
        "rows" => "5",
        "class" => PREFIJOVAL,
        "onkeypress" => "javascript: controlaTextarea(this, event, 150);"
        )
    ),    
"navegador" => array(
    "valor" => "Sin especificar",
    "titulo" => "Navegador",
    "opciones" => array(
        "Sin especificar" => "Sin especificar",    
        "Internet Explorer" => "Internet Explorer",
        "Firefox" => "Firefox",
        "Opera" => "Opera",
        "Safari" => "Safari",
        "Google Chrome" => "Chrome",
        "Otros" => "Otros"
        ),
    "tipo" => "menú",
    "longitud-min" => 1, 
    "longitud-max" => 20,
    "requerido" => true,
    "tag" => "option",
    "atributos" => array(
        "class" => PREFIJOVAL,
        "size" => 1
        )
    ),
"sistema-operativo" => array(
    "valor" => "Sin especificar",
    "titulo" => "Sistema Operativo",
    "opciones" => array(
        "Sin especificar" => "Sin especificar",
        "Windows" => "Windows",
        "Mac OS X" => "Mac OS X",
        "GNU/Linux" => "GNU/Linux",
        "Linux" => "Linux",
        "Otros" => "Otros"        
        ),
    "tipo" => "selector",
    "estilo-fieldset" => "width: 20em",
    "clase-fieldset" => "",
    "separador-selector" => " ",
    "longitud-min" => 1,
    "longitud-max" => 20,
    "requerido" => true,
    "tag" => "input",
    "atributos" => array(
        "type" => "radio",
        "class" => PREFIJOVAL
        )
    )   
);
//Construimos el array de patrones para esta definición
contruir_array_patrones($array_campos);
...