Un autor que quiera construir un sitio web dinámico deberá instalar un servidor web (como nginx o Apache), configurarlo con FastCGI, escribir un programa (en C, C++, Java, ...) que sea un servidor FastCGI y además se ocupe de la lógica del sitio. Esta tarea puede ser monumental, incluso para un sitio web sencillo. Este fue el sentimiento que tuvo Rasmus Lerdorf en 1994 cuando quiso publicar una página web personal con un mínimo grado de dinamismo, como el conteo de visitantes. Lerdorf encontró que sería más natural insertar pequeños trozos de código (scripts) dentro de sus páginas web estáticas a tener que programar un ejecutable. Cada vez que un visitante solicitara una de estas páginas, un intérprete en el servidor web ejecutaría estos scripts antes de responderle al cliente. Lerdorf llamó "Personal Home Page tools" a su intérprete y lo dispuso como software libre, el cual evolucionaría a ser el lenguaje de scripting más usado en servidores web del mundo, bajo el nombre recursivo de PHP: Hypertext Processor (PHP).
Aunque se pueden escribir programas PHP independientes en línea de comandos o con interfaz gráfica, su uso más difundido ha sido para crear páginas web dinámicas. Una página web es dinámica si su contenido, apariencia o comportamiento puede cambiar cada vez que es solicitada por un navegador web. Así PHP está diseñado para mezclarse con el contenido de las páginas web. El php_hello_world muestra un posible "hola mundo" con PHP.
Al correr el ejemplo del php_hello_world ocurre lo siguiente. El servidor web recibe un mensaje solicitando el recurso /path/to/hello_world.php
, el cual es un simple archivo de texto. En condiciones normales, el servidor web podría retornarle el archivo al navegador, el cual no sabría qué hacer con él y le preguntaría al usuario un lugar para guardarlo. Sin embargo, el servidor web tiene que estar configurado para tratar de forma especial a todos los archivos que terminen con extensión .php
. Esta configuración indica al servidor web que debe ejecutar los scripts que se encuentran en el archivo .php
antes de enviarlos al navegador.
La configuración del servidor web también le indica cómo encontrar el intérprete de PHP. Así el servidor invoca al intérprete pasándole el archivo /path/to/hello_world.php
. El intérprete carga el archivo y lo recorre (parse) buscando scripts de PHP, los cuales se encuentran únicamente dentro de la etiqueta especial <?php ... ?>
, lo demás el intérprete lo deja intacto. El intérprete ejecuta el código en los scripts de PHP y reemplaza la etiqueta especial por la salida que generó su ejecución. Una vez que el intérprete de PHP ha terminado de ejecutar todos los scripts de PHP en el archivo, el resultado regresa al servidor web quien lo despacha al navegador como una página web normal.
En el caso del listado php_hello_world el script de las líneas 10 a 12 es reemplazado completamente por la salida de la función print()
en la línea 11. La línea 1 contiene otro script de PHP, el cual invoca a la función header()
que indica al intérprete de PHP agregar la directiva Content-type: application/xhtml+xml
al mensaje de respuesta HTTP que se enviará al navegador para que trate el contenido de dicho mensaje como un documento XHTML. Si esto no se hace, se asumirá el tipo MIME por defecto: text/html
y el navegador interpretará la página como HTML.
El tipo MIME por defecto es indicado a PHP por la directiva default_mimetype
del archivo de configuración php.ini
. Si todas o la mayoría de páginas del sitio web que se van a generar con PHP son XHTML se aconseja cambiar el valor de esta directiva a application/xhtml+xml
, y utilizar la función header()
sólo en aquellos casos excepcionales o cuando no se tienen permisos para modificar el archivo php.ini
. Tenga en consideración que la función header()
debe invocarse antes de generar cualquier otra salida, por lo que se aconseja hacerlo siempre al inicio del archivo .php
. En los ejemplos de este capítulo se asumirá que el MIME type por defecto es XHTML por lo que no se harán más invocaciones a la función header()
.
El navegador sólo recibe el resultado de ejecutar los scripts, no el código fuente PHP que generó el resultado. El lector puede comprobarlo corriendo el ejemplo del php_hello_world y revisando en su navegador el código fuente de la página.
Durante muchos años se utilizó un short tag <? ... ?>
en lugar de <?php ... ?>
para escribir el código PHP. El uso del "short tag" debe evitarse a toda costa, ya que genera conflicto con las instrucciones de procesamiento de XML <?xml ... ?>
. De hecho, si se quiere servir documentos XHTML 4.01 (que tengan la declaración XML) con scripts de PHP, se debe deshabilitar la directiva short_open_tag
en la configuración del intérprete de PHP (en el archivo php.ini
).
En los listados de código de este capítulo, el lector puede hacer clic en los nombres de las funciones PHP para acceder a la documentación en línea. La mayoría de estas funciones no se explicarán en este texto, sino que se insta al lector a estudiar su propósito en la documentación oficial con el fin de comprender cada ejemplo completo. El php_web_server_date muestra la fecha y hora del servidor web.
El php_web_server_date muestra otras características de PHP. En el ejemplo del hola mundo (php_hello_world) el párrafo se generó dentro del script de PHP (línea 11), mientras que en el ejemplo de la hora (php_web_server_date) el script se escribió dentro de un elemento p
. Esto muestra que el autor puede intercalar código PHP y (X)HTML a conveniencia. Algunos programadores prefieren abrir una única etiqueta <?php ... ?>
y escribir mucho código PHP, otros prefieren escribir código (X)HTML y abrir etiquetas <?php ... ?>
sólo para el mínimo código PHP necesario. El segundo método es ligeramente más eficiente.
Reescriba el ejemplo de la hora del servidor (php_web_server_date) utilizando sólo código PHP, es decir, su archivo tendrá sólo una etiqueta real: <?php ... ?>
, y las etiquetas XHTML se generan a partir de strings multilínea en PHP. Trate de utilizar la menor cantidad de strings posible. La salida de su script debe ser un documento XHTML 5 válido. Debe configurar un servidor web local con PHP para hacer pruebas (revise el capítulo 6).
Las líneas 10 y 11 del php_web_server_date invocan una función estándar de PHP: date()
, la cual retorna un string con la fecha u hora en el formato que se le indique por parámetro. PHP tiene una biblioteca de funciones cuya documentación en línea es muy rica, ya que los visitantes aportan contenido útil en ella en forma de comentarios.
El string resultado de invocar a date()
en la línea 10 del php_web_server_date es almacenado en la variable $fecha
. PHP exige anteponer el símbolo de dólar ($
) a un identificador para distinguirlo como una variable, de la misma forma que ocurre en Perl y en Unix shell scripting, con el objetivo de hacer más eficiente el reconocimiento de las variables a la hora de ejecutar el código. Al igual que en JavaScript, las variables pueden almacenar valores de cualquier tipo de datos, incluso cambiar en el tiempo. Una variable se crea simplemente asignándole un valor.
Escriba un programa PHP que imprima la cantidad de días que faltan para navidad (25 de diciembre) del año en curso. Si la navidad ya pasó, imprime la cantidad de días que faltan para la navidad del próximo año. Busque en la documentación de PHP una función que le pueda ayudar a construir fechas a partir de sus valores separados (hora, mes, día, etc.). Sugerencia: puede también estudiar los ejemplos provistos en la documentación de la función date()
.
La línea 12 del php_web_server_date imprime un texto resultado de concatenar dos strings literales con las variables $fecha
y $hora
utilizando el operador punto (.
). Esto crea la primera diferencia con JavaScript, que utiliza el operador de suma (+
) para la concatenación, mientras que PHP utiliza el operador punto (.
) por influencia de Perl. El ejemplo anterior pudo haberse escrito de otra forma muy común:
El ejemplo del php_variable_interpolation sólo cambia la línea 12 con respecto al php_web_server_date. En la nueva versión se insertaron las variables directamente en el string sin hacer concatenación. A esto se le llama interpolación de variables o sustitución de variables y el intérprete de PHP lo hace únicamente en las cadenas con comillas dobles (como en "$var"
) y no en cadenas con comillas simples (como en '$var'
). Lo mismo aplica para las secuencias de escape como "\n"
.
El desarrollador web debe tener clara esta diferencia, ya que JavaScript trata de forma idéntica a las cadenas literales con comillas dobles y simples, mientras que PHP hace interpolación de variables y de secuencias de escape sólo en cadenas literales con comillas dobles y no con comillas simples. El lector podría estar pensando en utilizar siempre comillas dobles para evitar sorpresas, sin embargo, debe tener en cuenta que el procesamiento de las cadenas literales con comillas simples es más eficiente que con comillas dobles, y en situaciones donde la interpolación resulta inadecuada se debe utilizar comillas simples.
Pruebe el efecto de cambiar las comillas dobles por simples en el ejemplo del php_variable_interpolation. Guarde una captura de pantalla del resultado que obtuvo en el navegador.
Para escribir un programa en PHP el desarrollador web sigue normalmente la siguiente rutina. Crea un archivo de texto con extensión .php
donde escribe su código dentro de etiquetas <?php ... ?>
. Lo guarda en alguna carpeta que forma parte de su sitio web. Accede al archivo .php
a través de un navegador escribiendo su URL. El servidor invoca al intérprete de PHP y envía el resultado al navegador. El desarrollador puede entonces ver la salida de su programa.
Dependiendo de la configuración de PHP, esta rutina se vuelve en contra del desarrollador o de la empresa cuando el programa PHP tiene errores. Si la configuración dicta que el intérprete de PHP debe informar los errores en la salida estándar, éstos aparecerán en el navegador, lo cual ayudará enormemente al desarrollador a depurar sus programas, pero podría hacer visible información sensible de la empresa a los visitantes, o confundirlos arruinando el contenido y diseño del sitio web.
Si la configuración dicta que el intérprete de PHP debe callar los errores (o a lo sumo enviarlos a una bitácora), el desarrollador difícilmente detectará que los hay, a menos de que el intérprete se niegue a ejecutar el código PHP con errores y no regrese del todo una respuesta al servidor web, el cual optará por enviar un mensaje de respuesta con un código de estado 500 Internal server error
al navegador. Esto es mucho más eficiente y evita que el servidor web exponga información sensible a los visitantes, aunque estos se molestarán en todo caso.
La solución aconsejada es tener dos ambientes: uno de producción y otro de desarrollo. El ambiente de producción aloja el sitio web que atiende a los visitantes, se considera estable y los errores en el código PHP no son reportados al navegador. El ambiente de desarrollo es de uso exclusivo de los desarrolladores, en él los errores y advertencias son reportados al navegador. Estas diferencias se configuran en la sección Error handling and logging del archivo php.ini
.
Los ambientes de producción y desarrollo podrían estar en computadoras diferentes, o en la misma bajo servidores virtuales distintos. Es importante que el sitio web completo se encuentre administrado por algún software de control de versiones, como Subversion o Git, lo cual además facilita el mantenimiento del código entre estos ambientes.
Configure dos ambientes: uno de producción (ej: en el servidor de cursoweb) y otro de desarrollo (su máquina local). Configure el ambiente de producción para silenciar errores de PHP. Configure el ambiente de desarrollo para que reporte al navegador todos los posibles errores y advertencias (warnings). Escriba un archivo .php
que tenga uno o más errores de sintaxis, como omitir un punto y coma, o escribir un trozo de texto literal sin comillas. Pruebe qué ocurre al ejecutar su programa en dos ambientes diferentes: uno local y otro en el servidor del curso. Guarde una captura de pantalla de cada ambiente.
Antes de publicar código PHP en el sitio web o en el repositorio de control de versiones, el desarrollador siempre debería chequear sus programas con el intérprete de PHP, lo cual puede hacerse en la línea de comandos de la siguiente forma:
php /path/to/file.php
El desarrollador no puede verificar un documento .php
contra los validadores del Consorcio Web (como Unicorn), pero sí su salida. Es decir, que el desarrollador deberá hacer dos verificaciones, la primera es llamar al intérprete de PHP en línea de comandos para verificar que su código PHP sea correcto, y luego la salida que su programa genera debe verificarla contra los estándares del Consorcio Web. Una tercera verificación debe hacerse si se tiene código JavaScript, inspeccionando la consola de JavaScript en el navegador.
La sintaxis de PHP se basa en C y Perl; y tiene mucha familiaridad con C++ y Java. Esto implica que los comentarios, condicionales, ciclos y operadores son similares. Este material asume que el lector conoce C y JavaScript, y se concentra en los aspectos que hacen a PHP distinto a estos lenguajes. Los comentarios en PHP se pueden escribir en cualquiera de las siguientes tres formas:
// Este es un comentario de una línea con estilo BCPL # Este es un comentario de una línea con estilo Perl /* Este comentario puede abarcar varias líneas pero no anidarse */
Al igual que JavaScript, PHP diferencia mayúsculas y minúsculas (case sensitive), y la aritmética se hace en punto flotante, es decir, 5 / 2
genera 2.5
y no 2
como ocure en C. PHP permite que un string se expanda varias líneas, y en tal caso, los espacios en blanco y cambios de línea pasan a formar parte natural del string, por ejemplo:
$table = /* ... */; $condition = /* ... */; $query = " SELECT * FROM $table WHERE $condition ";
equivale a haber escrito el siguiente código en una sola línea, pero que resulta más ilegible:
$query = "\n\tSELECT *\n\tFROM $table\n\tWHERE $condition\n";
Al igual que JavaScript, las variables pueden almacenar valores de diferentes tipos de datos a lo largo de la ejecución del script, y las conversiones de tipos de datos se hacen automáticamente de acuerdo al contexto. En PHP el operador de suma (+
) se utiliza sólo para valores numéricos y no hace concatenación, lo cual es responsabilidad del operador punto (.
). Por ejemplo:
El lector habrá notado que en algunos ejemplos se ha utilizado print()
y en otros echo
para generar la salida que recibirá el navegador. No son funciones realmente, son constructos del lenguaje y por ende pueden utilizarse con o sin paréntesis. La diferencia radica en que print
recibe únicamente un parámetro de tipo string y retorna siempre el entero 1; mientras que echo
no tiene un valor de retorno y puede recibir un número arbitrario de parámetros de cualquier tipo separados por comas, los cuales tratará de convertir a string automáticamente. En general, print()
es un constructo del lenguaje utilizado para simular una función en un contexto donde se requiere una, mientras que echo
es un constructo del lenguaje que no puede utilizarse en el contexto de una función y debe obligatoriamente usarse sin paréntesis si recibe dos o más parámetros. Exceptuando esas extrañas restricciones, en la mayoría de contextos puede utilizarse cualquiera de los dos, y la escogencia la hace el programador de acuerdo a sus gustos personales o las políticas de la empresa.
Escriba un programa PHP que reporte estadísticas sobre el uso de la memoria secundaria en la carpeta donde está alojado su archivo .php
(la cual se puede obtener con la expresión dirname(__FILE__)
). Imprima en una tabla (X)HTML como la siguiente.
Rubro | Tamaño | Porcentaje |
---|---|---|
Espacio utilizado | 22.98 GiB | 98.63% |
Espacio libre | 322 MiB | 1.37% |
Espacio total | 23.00 GB | 100.00% |
Estudie las funciones disk_free_space()
y disk_total_space()
. Note que la cantidad de bytes debe imprimirse en unidades amigables (kiB, MiB, GiB, etc.) [Puede reutilizar código provisto en los ejemplos de la documentación]. Imprima en rojo el espacio libre si es menor al 5% para alertar al usuario de que sus programas PHP, y por ende, su sitio web, podrían fallar por falta de espacio en disco.
Los tipos de datos que PHP dispone son: booleanos, números (enteros y flotantes), strings, arreglos, objetos, recursos (resource
) y el valor especial null
. Son prácticamente los mismos tipos de datos de JavaScript con algunas diferencias. Al igual que en JavaScript, una variable PHP no tiene tipo de datos, sino que puede almacenar valores de cualquiera de esos tipos de datos. Para saber el tipo de datos del valor almacenado en la variable $var
invoque la función gettype($var)
, la cual retorna un string con el nombre del tipo de datos.
Las variables booleanas (boolean
) sólo admiten los valores true
y false
, los cuales se pueden escribir en cualquier combinación de mayúsculas y minúsculas (case-insensitive).
Internamente PHP almacena los números utilizando enteros (int
) siempre que sea posible, y cuando el número es muy grande o tiene decimales, se representa internamente como un número flotante (double
). El programador no necesita preocuparse por estas diferencias. Sin embargo, ambas representaciones tienen limitaciones en cuanto a la capacidad y precisión, inadecuadas cuando se tiene que hacer cálculos precisos o manejo de dinero. En tales circunstancias el programador puede recurrir a alguna extensión matemática de PHP.
Los strings en PHP no son objetos, por tanto, no tienen propiedades ni métodos como length()
; sino que deben ser manipulados en forma similar a C: a través de funciones como strlen(str)
y substr(str, start, length)
. Sin embargo hay una diferencia importante, en C los strings son punteros mientras que en PHP son valores y por tanto, los operadores de comparación (==
, ===
, <
, <=
, >
, >=
) y concatenación (.
) trabajan de forma esperada en PHP.
En PHP todos los arreglos son realmente arreglos asociativos, también conocidos como mapas (maps), tablas hash o diccionarios; porque almacenan parejas llave => valor
. Si las llaves no se especifican, se asumen valores enteros secuenciales. Los arreglos se crean con el constructo array(v1, v2, ..., vN)
que recibe una lista de valores separados por comas, e inserta esos valores en orden asociándolos con llaves enteras correspondientes a la posición del elemento. Las llaves también pueden ser strings, y en tal caso se especifican usando la notación key => value
. Un arreglo que almacene varias de estas parejas se crea utilizando la notación array(k1 => v1, k2 => v2, ..., kN => vN)
, como se aprecia en el php_arrays_intro. Al igual que los string, los arreglos no son objetos, por lo que su manipulación se hace a través de funciones como count($arr)
y sort($arr)
. Este tema se estudiará con más detalle adelante.
Los objetos de PHP se parecen mucho a los de Java. Un objeto es una instancia de una clase, la cual puede tener herencia y polimorfismo con otras clases. Los objetos se crean con el operador new
y sus miembros se acceden con el operador ->
. Este tema se explica más adelante.
El tipo de datos y valor especial null
sirve para indicar que una variable no tiene valor. Si se trata de hacer referencia a una variable que no existe, PHP puede o no generar un warning de acuerdo a su configuración, y el valor de reemplazo de la variable no existente es null
, el cual se puede escribir en cualquier combinación de mayúsculas y minúsculas.
Cuando se utiliza un valor de un tipo de datos en un contexto donde se espera otro, PHP trata de hacer conversiones automáticamente; lo cual funciona bien en la mayoría de casos para los valores primitivos. El programador puede forzar la conversión de datos utilizando explicit type cast, anteponiendo el tipo de datos entre paréntesis a la expresión que se quiere convertir, de la misma forma que se hace en C, como en el ejemplo del php_explicit_type_cast.
El programador puede escribir los mismos operadores que usa en C o JavaScript sin cambios en PHP. Las diferencias son mínimas, como las que se exponen a continuación.
El operador de concatenación en PHP es el punto (.
), así el operador de suma (+
) pretende sólo recibir operandos numéricos, de lo contrario, tratará de convertirlos a números.
Al igual que en JavaScript, el operador de igualdad ==
indica si dos valores son el mismo aún después de hacer conversiones, así la expresión "+12.3" == 12.30
se evaluará como verdadera. El operador de identidad ===
se evalúa verdadero sólo si sus operandos son iguales sin hacer conversiones.
Los operadores lógicos clásicos &&
y ||
trabajan de la misma forma que en otros lenguajes de programación. Sin embargo, PHP introduce los operadores lógicos nombrados and
y or
(que también pueden escribirse en mayúsculas: AND
y OR
), que tienen una prioridad menor, por lo que pueden causar conflictos si se combinan operadores clásicos con los nombrados en una misma expresión. Se aconseja al programador utilizar sólo los operadores clásicos a menos de que se entienda bien el efecto del operador nombrado.
Los condicionales en PHP se indican con tres cláusulas: if/else/elseif
, ?:
y switch
. La cláusula else if
se puede escribir con el espacio o sin él (elseif
); su efecto es el mismo en ambos casos, como se aprecia en el php_elseif.
Los ciclos en PHP se realizan con las mismas cláusulas de C: while
, do-while
y for
, las cuales mantienen la misma sintaxis. PHP agrega una estructura de control más: foreach
, la cual sirve para iterar arreglos y objetos, como se verá más adelante en sus respectivas secciones.
En PHP los arreglos son contenedores asociativos, es decir, son contenedores de parejas key => value
, donde las llaves (key
) pueden ser números o strings, y si se omiten, PHP asume valores enteros. Un arreglo se crea simplemente asignándole valores a sus elementos o con el constructo array()
. Para crear una pareja key => value
y agregarla al arreglo, se emplea la siguiente notación:
$arr[key] = value;
Si se asigna un valor a una llave que ya existe dentro del arreglo, no se inserta de nuevo, si no que se reemplaza su valor anterior. Si se omite la llave en la asignación, PHP busca el último entero asociado en el arreglo y asigna el sucesor al valor. Esto se ilustra en el php_array_assign.
La segunda forma de crear un arreglo es utilizando el constructo array(k1 => v1, k2 => v2, ..., kN => vN)
, que recibe las parejas llave=valor utilizando la notación key => value
que asemeja una asignación, pero en lugar de ser una asignación directa indica actualizar el valor del índice o llave key
. En el constructo array()
, si se provee un valor sin llave, se asume el próximo índice que sigue al último entero utilizado en lugar de la llave (como ocurre con el valor 'vicente'
en la línea 2 del php_array_construct). El resultado final del php_array_assign se podría escribir utilizando array()
como se aprecia en el php_array_construct.
PHP provee el ciclo foreach-as
, el cual recorre un arreglo por cada una de sus parejas key => value
y permite al programador hacer algo con ellas en cada iteración del ciclo. El orden de recorrido es el mismo en que se encuentran los elementos en el arreglo, y puede alterarse con funciones de ordenamiento.
Las llaves en un arreglo asociativo sólo pueden ser enteras o string, pero el valor puede ser de cualquier tipo de datos, incluso otro arreglo. Esto permite representar matrices o arreglos de más dimensiones.
Las funciones en PHP se declaran con la palabra reservada function
seguido por la lista de parámetros entre paréntesis, de la misma forma que se hace en JavaScript. El php_factorial_function ilustra la función factorial()
y su uso para imprimir los primeros 19 factoriales.
En JavaScript el manejo de valores o referencias es implícito, es decir, el lenguaje se reserva su manejo y el programador no puede alterarlo. En cambio, en PHP el manejo de valores y referencias es explícito, es decir, el programador debe indicar cuándo quiere hacer una copia de un valor o cuando quiere tener una referencia hacia el valor original. Al igual que C++, una referencia se crea antecediendo el operador ampersand (&
) al nombre de una variable, de la forma &$var
.
El php_byvalue_byreference declara dos funciones idénticas: incrementar_salario_1()
recibe sus dos parámetros por copia, mientras que incrementar_salario_2()
recibe su primer parámetro por referencia. Ambas intentan aplicar un incremento de salario a todos los empleados recibidos en un arreglo por el primer parámetro. Dado que incrementar_salario_1()
recibe una copia del arreglo, hará el ajuste de salario a un arreglo temporal que es descartado al terminar el llamado a la función. Por su parte, incrementar_salario_2()
recibe una referencia hacia el arreglo original (línea 9) logrando el efecto deseado para este ejemplo.
Escriba una función PHP que retorna un arreglo asociativo con los nombres de los usuarios del sistema operativo y la cantidad de bytes que cada uno de ellos está consumiendo en su carpeta personal. Es fácil obtener esta información en la línea de comandos de Linux/Unix. PHP dispone de funciones para invocar comandos del sistema operativo y capturar su salida, como shell_exec()
y las comillas invertidas (``
), por ejemplo:
Construya otra función PHP que recibe por parámetro una referencia a un arreglo asociativo, y lo imprime en una tabla (X)HTML ordenable. Invoque ambas funciones para generar una tabla que permita a un administrador conocer los usuarios que más espacio consumen. Reutilice su biblioteca JavaScript para ordenar tablas (ejercicio 5.37 y 5.38). Es importante que el ordenamiento se haga en el lado del cliente y no del lado del servidor, con el fin de ahorrar recursos. Si gusta puede ordenar descendentemente por espacio utilizado la tabla con JavaScript al cargar la página.
En la tabla resultado debe aparecer el nombre del usuario (por ejemplo, a54321
) y no su carpeta de inicio (/home/a54321
). El programador debe ser consciente del poder que le confiere PHP al permitirle invocar cualquier programa en el servidor; y utilizarlo con cuidado. Por ejemplo, se debe evitar invocar programas que consuman muchos recursos, ya que uno o muchos visitantes están esperando una respuesta del servidor. Además se debe evitar brindar información sensible a usuarios que no son de confianza.
Una importante diferencia de PHP con otros lenguajes de programación son sus reglas de alcance de variables (variable scope). PHP asume que toda referencia a variable que se haga en el cuerpo de una función, es una referencia a una variable local, a menos de que se indique lo contrario. En el php_variable_scope_error, la función print_factorials()
falla en la línea 23 al tratar de acceder a $count
, ya que PHP busca una variable $count
local, la cual no fue declarada en ese contexto.
Este problema es un error común de programación, debido a PHP rompe la norma de la mayoría de lenguajes en los cuales las variables globales son accesibles desde cualquier lugar del código. PHP provee varias alternativas. Si es factible, se recomienda pasar por parámetro el valor que requiere la función, así se mantiene la modularidad del código y se reduce la dependencia de variables globales, como se hizo en las líneas 20 y 26 del php_variable_scope_fix1.
Si por alguna razón es imprescindible acceder directamente a las variables globales, se le puede indicar a la función que la variable $count
no está definida en el contexto local si no en el global, lo cual se hace anteponiéndole la palabra reservada global
, como se aprecia en la línea 20 del php_variable_scope_fix2. En todo caso, el uso de variables globales se considera una mala práctica de programación.
PHP provee algunas variables globales que son accesibles desde cualquier contexto, es decir, sin tener que declararlas primero con la palabra reservada global
. Se les conocen como variables superglobales y son arreglos asociativos con información muy útil para el programa. Se listan en la php_superglobal_variables y cada uno se explica más adelante en este mismo capítulo.
Arreglo | Descripción |
---|---|
$GLOBALS |
Sirve para acceder a las variables globales definidas en el programa. Es especialmente útil cuando se quiere acceder a una variable global y existe una homónima en el contexto local. |
$_SERVER |
Contiene variables enviadas por el servidor web al intérprete de PHP, como $_SERVER['PHP_SELF'] que tiene la ruta del programa PHP relativa a la raíz del sitio, el nombre del servidor web ($_SERVER['SERVER_NAME'] ), detalles del navegador que solicitó ejecutar el programa PHP ($_SERVER['HTTP_USER_AGENT'] ), el IP de la máquina del cliente ($_SERVER['REMOTE_ADDR'] ) y muchos otros. |
$_GET |
Aloja las variables pasadas al script de PHP por parámetro en el URL (método HTTP GET). |
$_POST |
Aloja las variables pasadas al script de PHP por parámetro utilizando el método HTTP POST. |
$_FILES |
Aloja información sobre los potenciales archivos que se hayan enviado (uploaded) por el método HTTP POST. |
$_COOKIE |
Un arreglo con variables y valores que el navegador aloja por petición del sitio web. |
$_SESSION |
Almacena variables globales para controlar la sesión con el navegador. |
$_REQUEST |
Un arreglo que contiene la unión de los arreglos $_GET , $_POST y $_COOKIE . |
$_ENV |
Las variables ambiente que dispone el intérprete de JavaScript. Son dependientes del sistema operativo. |
Siempre que se va a usar una variable cuyo valor fue provisto por una fuente externa, como un parámetro, debe evitarse que su valor sea ejecutado por el intérprete de JavaScript o genere código (X)HTML/JavaScript, ya que permite a un atacante inyectar código maligno que podría revelar información confidencial de su sitio web u otro tipo de daño. PHP provee varias funciones que convierten código en una representación no ejecutable, como htmlspecialchars()
que reemplaza los caracteres especiales (<
, >
, &
) por sus entidades correspondientes (<
, >
, &
). Estas funciones se estudiarán más adelante (ver php_sanitize_functions).
Escriba un programa PHP que imprima en tablas (X)HTML el contenido de todos los arreglos superglobales de PHP. Su programa no debe generar errores o advertencias si un arreglo no está definido para su script (indague sobre la función isset()
). Estudie el resultado de su programa para encontrar algunos valores que le pueden resultar de utilidad en sus aplicaciones web. Pruebe su programa enviándole algunos parámetros GET en el URL. Puede utilizar el siguiente probador:
Los objetos en PHP utilizan prácticamente la misma sintaxis de Java, con algunas variaciones de C++. Los objetos son instancias de clases. Una clase en PHP se declara con la palabra reservada class
seguida por un identificador. Las propiedades (miembros de datos) y métodos de la clase, se deben anteceder con el tipo de acceso que deben tener: private
, protected
o public
. Si se omite, PHP asume public
.
Los métodos se declaran como cualquier otra función normal de PHP, sólo que dentro del cuerpo de la clase. Un método de una clase a diferencia de una función normal, tiene acceso a tres identificadores reservados: $this
es una referencia hacia la instancia del objeto al que se le invocó el método, y se utiliza para acceder a sus propiedades y otros métodos. self::
es una referencia hacia la clase misma y sirve para acceder a sus miembros estáticos. parent::
permite acceder a propiedades y métodos de la clase base.
Para instanciar un objeto se utiliza el operador new clase(parametros)
, enviándole los parámetros que recibe la clase en el constructor. Un constructor es un método con el nombre especial __construct
, el cual se encarga de inicializar los miembros de datos del objeto. Aunque se puede utilizar un método con el mismo nombre de la clase como constructor, no se recomienda esta práctica, ya que versiones recientes de PHP lo interpretan como un método normal.
Para crear una instancia de una clase se utiliza el operador new
, el cual retorna un puntero hacia el nuevo objeto. Para acceder a las propiedades (datos y métodos públicos) del objeto se utiliza el operador ->
, ya que el operador punto (.
) hace concatenación de strings.
Escriba un programa PHP que cuente cuántos días feriados "caen" cada día de la semana para un año cualquiera. Imprima el resultado para el año en curso, en una tabla (X)HTML, por ejemplo:.
Feriados | Detalle | |
---|---|---|
Domingo | 1 | Año nuevo |
Lunes | 0 | |
Martes | 2 | Día del trabajo, Navidad |
Miércoles | 3 | Batalla de Rivas, Anexión de Nicoya, Día de la madre |
Jueves | 1 | Día de la Virgen de los Ángeles |
Viernes | 1 | Día de las culturas |
Sábado | 1 | Independencia de Costa Rica |
Sugerencia. Escriba una clase Feriado
para representar un día feriado cualquiera del año. Cree una instancia de su clase por cada uno de los feriados fijos: día del trabajo, día de la madre, independencia, etc. Almacene todos los días feriados en un arreglo. Implemente en su clase Feriado
un método que reciba un año por parámetro y retorne el día de la semana que cae ese feriado en el año dado. Indague sobre la función mktime()
en la documentación de PHP. Finalmente haga un conteo de los días en que caen los feriados y guárdelos en un arreglo asociativo, con el cual es fácil imprimir la tabla resultado.
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 con 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.
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 su 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 dado 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.
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 mismo 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 y le otorgará el archivo bloqueándolo 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 corrige el ejemplo de la bitácora (php_log_file_append) utilizando candados en las líneas 6 y 15.
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).
Modifique su solución del ejercicio anterior para ser capaz de manejar múltiples contadores en un mismo archivo. Modifique las páginas de las secciones principales (inicio, intro, xml, xhtml, css, js, y php) de su sitio web personal para mostrar el número de veces que han sido visitadas en el pie de página. 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.
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 o Server2Go que ya lo incluyen. 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.
El mecanismo oficial para acceder a una bases de datos, es utilizar un lenguaje de programación y el API (Application Programming Interface) que el DBMS provee; como se hace desde PHP. Sin embargo, la mayoría de motores de bases datos, proveen clientes amigables al usuario, en línea de comandos (como 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 ingresar 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 intérprete 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 sobre él debería únicamente 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
).
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.
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:
mysql_connect()
.mysql_select_db()
.mysql_query()
.mysql_fetch_row()
o con mysql_fetch_assoc()
, se hace algún procesamiento con ellos, y se construye alguna salida útil para el visitante.mysql_free_result()
.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 cada tabla de la base de datos Universidad si no existe $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);
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'
como se ve en el php_db_credentials), 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 39 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 39, 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 63 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.
El uso de funciones para acceder desde PHP al motor de base de datos ha sido el método tradicional desde PHP 2.0. Sin embargo, a partir de PHP 5.0 se desaconseja el uso de estas funciones, en favor de una interfaz orientada a objetos. Para MySQL hay dos objetos: mysqli y PDO. La clase mysqli es una clase específica para MySQL y permite acceder a la mayoría de la funcionalidad provista por este motor de base de datos. La clase PDO es una clase genérica utilizada para acceder con una misma interfaz a un número arbitrario de motores de bases de datos distintos. En este material se preferirá la clase PHP Data Object (PDO) dadas las ventajas de su portabilidad.
La conexión con el motor de bases de datos se hace en el constructor de la clase PDO, indicando un string que indica a PDO el tipo de DBMS y algunos parámetros sobre la base de datos a utilizar. A este string se le conoce como Data Source Name (DSN) y es dependiente del DBMS. Además del string DSN, el constructor de la clase PDO recibe por parámetro el nombre del usuario del DBMS y su contraseña como se aprecia en el [Contenido pendiente].
' . $this->points . ' ' . $this->nick_name . ' ' . $this->date . ' '; } } try { // Connect to the database $pdo = new PDO("mysql:host=localhost;dbname=database", 'username', 'password'); // Sanitize parameters sent by user $total = PDO::quote($_GET['total']); // Print top n high scores $sql = " SELECT nick_name, points, date FROM high_scores ORDER BY points DESC LIMIT $total"; // Get each result as an object instead of an array $result = $pdo->query($sql); $result->setFetchMode(PDO::FETCH_CLASS, 'HighScore'); // Print high scores into a table echo "
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.
A diferencia de otros medios tradicionales, como la radio y televisión; el web permite una comunicación interactiva, donde el visitante puede expresarse, retroalimentar a los autores y hasta comunicarse con otros visitantes, como se verá en el próximo capítulo. El formulario web el mecanismo que permite al visitante interactuar; 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 tradicional. Los campos para los destinatarios, el campo para enviar 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); aunque clientes de correo modernos como Gmail emplean documentos anidados (iframe
) con el atributo contenteditable="true"
para permitir edición con formato (WYSIWYG). Los botones del cliente de correo 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 el correo 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 23 a 27 del php_login_form_get. La primera vez que se carga este programa, ninguna de las variables $username
y $password
tendrán un valor (líneas 10 y 11), por lo que la condición del if
de la línea 14 será falsa y se ejecutará el bloque de código del else
, el cual despliega el formulario web de las líneas 23 a 27, utilizando el operador <<<
, conocido como Heredoc, el cual 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 se interpretan como un string que puede tener 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 28.
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 24), un campo de texto para ingresar contraseñas llamado password
(línea 25) y el botón de "submit" (línea 26). 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 23), 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 23).
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=value2&...&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 al programador el acceso a los parámetros. Cada vez que se llama un script, el intérprete de PHP "parsea" el query string, extrae cada valor que en él encuentre, y los agrega a un arreglo asociativo superglobal $_GET
, de tal forma que el programador puede obtener el valor de un parámetro 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 10 y 11; 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 14 se evalúa como false
y hace que el intérprete de PHP imprima el formulario (líneas 23 a 27), 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 24), un campo de texto especial para escribir contraseñas llamado password
(línea 25), y el botón de enviar (línea 26). 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 23), 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
El URL anterior puede leerse como una solicitud al servidor web de ejecutar el programa http://www.ejemplo.com/path/login1.php
enviándole dos parámetros: username
con el valor chema
, y password
con valor semeolvido
. El navegador pone este URL en la barra de direcciones y envía el siguiente mensaje de solicitud HTTP al servidor web (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 10 y 11. 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.
Modifique el php_holidays sobre días feriados, para que muestre un formulario compuesto de un campo de texto donde el visitante puede ingresar el año del cual quiere ver los días feriados; y un botón para enviar la consulta. Si no se especifica un año, su programa debe mostrar el conteo de días feriados del año en curso.
En el ejemplo del php_login_form_get se envía al servidor una contraseña por el método GET, haciéndola visible al usuario en el URL, quien además puede modificarla fácilmente. 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 11, 12 y 24).
Como se dijo antes, cuando el intérprete de PHP ejecuta un script, toma las parejas campo=valor
del query string presente en el URL y las agrega al arreglo asociativo $_GET
. Simultáneamente, 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
de su elemento form
. El programador también puede acceder a estos valores con el arreglo superglobal $_REQUEST
sin importar por cuál método fueron enviados, ya que $_REQUEST
es la unión de los arreglos asociativos $_GET
, $_POST
y $_COOKIE
.
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 (los cambios de línea son significativos):
POST /path/login2.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.
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
, $_POST
y $_REQUEST
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
, $_POST
o $_REQUEST
, debe primero "esterilizarlo" (en inglés, sanitize); 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 . |
htmlspecialchars($str) |
Retorna un string resultado de reemplazar los caracteres especiales en (X)HTML (< , > , & , ' , " ) por sus respectivas entidades (< , > , & , ' , " ). |
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. |
El php_sanitize muestra un archivo con funciones de conveniencia que llaman a las listadas en la php_sanitize_functions. El php_login_form_post_sanitize reescribe el ejemplo del formulario de autenticación, utilizando las funciones de conveniencia del archivo sanitize.php
para evitar la inyección de código mal intencionado.
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 15).
Haga que si se provee un año que su programa no soporta (por ejemplo, 1492, 2040, -234, ó 'asdfg'), presente un mensaje de error 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). Para determinar el rango de años válidos, consulte en la documentación las restricciones de las funciones que utiliza (por ejemplo mktime()
).
El estándar (X)HTML permite varios tipos de controles para ingresar datos, los cuales se exponen en las siguientes subsecciones. 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 esta sección incluyen rótulos para ilustrar esta práctica.
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 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>
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.
Modifique su programa de días feriados para permitir al visitante escribir un rango de años, por ejemplo 2010 a 2013. Al enviar la consulta, su programa despliega los días feriados para cada año en el rango (2010, 2011, 2012 y 2013 en este ejemplo). Utilice dos controles del tipo que guste. Si el visitante sólo provee un valor, se despliega los días feriados para ese año particular. Si el rango está mal formado (el año inicial es mayor que el año final), despliegue un mensaje de error para hacer al visitante consciente de la anomalía, en lugar de presentar resultados que podrían hacerle creer que hizo la consulta correcta.
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.
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 | 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.
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>
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.
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. Su código de JavaScript debe estar en un archivo externo .js
, al igual que los estilos en un archivo .css
.
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 o cuando el rango esté mal elaborado (el año inicial debe ser menor o igual que el año final). Inmediatamente después de que el visitante haya corregido el año, debe regresar al color normal del campo, por ejemplo, negro. Estos estilos deben estar definidos en un archivo .css
externo.
Por defecto en un sitio web todos los visitantes son anónimos. Cuando se quiere dar un tratamiento personalizado a cada visitante, es necesario poder reconocerlo cada vez que se acerque al sitio, incluso aunque utilice diferentes computadoras (en la casa, el trabajo, en sus dispositivos móviles, etc.). El web provee varios mecanismos para facilitar este reconocimiento y almacenar preferencias del visitante. En esta sección se estudiarán los cookies, la autenticación HTTP, y la administración de sesiones.