Manual PHP

562
Instalando Apache 2.2.17 ¿Qué es un servidor WEB? Podríamos definir un servidor WEB como una aplicación que permite acceder a los recursos contenidos en algunos de los directorios del ordenador que la alberga a usuarios remotos que realizan sus peticiones mediante el protocolo HTTP. Por tanto, instalar un servidor web no es otra cosa que instalar y configurar un programa en una unidad o directorio de un ordenador cualquiera. ¿Qué es «Apache»? Bajo este nombre suele hacerse referencia a Apache Software Foundation, organización norteamericana que se autodefine con el objetivo de «... facilitar ayuda organizativa, legal y financiera para los proyectos de desarrollo de software tipo Open Source (código abierto)». Uno de los proyectos más populares de Apache es el desarrollo y suministro -de forma gratuita y libre- de un software de servidor HTTP, conocido también como el servidor Apache. La corta historia de «Apache» Apache Software Foundation tiene su origen en febrero de 1995. Hasta ese momento el software más popular de servidores de HTTP era el desarrollado por Rob McCool, miembro del Centro Nacional para Aplicaciones de Super computación (NCSA), de la Universidad de Illinois. El desarrollo de aquel primitivo software de servidor de NCSA (software de dominio público y código abierto) tuvo algunos problemas a mediados del año 1994. Esta circunstancia obligó a que muchos de los webmasters que utilizaban aquella aplicación tuvieran que desarrollar sus propias extensiones y corregir de forma individual los fallos de funcionamiento de la aplicación original. Un pequeño grupo de aquellos webmasters entró en contacto –vía e-mail– con el objetivo de coordinar y conjuntar sus trabajos de corrección y mejora de la aplicación original de NCSA. Fueron Brian Behlendorf y Cliff Skolnick quienes –a través de una lista de correo– coordinaron el trabajo y lograron establecer un espacio compartido –de libre acceso para quienes participaran en el proyecto– en un ordenador instalado en California. En febrero de 1995, ocho colaboradores del primitivo proyecto decidieron aunar sus esfuerzos de forma organizada y fundaron lo que fue conocido como Grupo Apache. Usando como base el HTTPD 1.3 de NCSA y aplicando los patchs desarrollados hasta ese momento, lanzaron la primera versión oficial (versión 0.6.2) del software de servidor de Apache en abril de 1995. Aquella primera versión entró en un rápido e intenso proceso de mejoras y alcanzó una gran implantación como software de servidor –inicialmente era sólo para UNIX– para diferentes plataformas, una de las cuales es la versión para Windows que nosotros utilizaremos y que frente a otras alternativas de servidor HTTP tiene las ventajas de ser gratuita y muy eficaz. Instalación de Apache En el sitio de descargas de Apache o en el mirror español puedes encontrar un fichero llamado httpd-2.2.17-win32-x86- openssl-0.9.8o.msi (es la versión que he incluido en el autoinstalador) o parecido. Digo parecido porque las versiones van evolucionando y en esas direcciones tienes la última de las versiones y es posible que cuando accedas ya se haya actualizado. En cualquier caso si tienes interés por una versión específica de Apache puedes encontrarla en esta especie de repositorio histórico. Una vez descargado el programa instalador bastará con que hagas doble clic sobre su icono para que te aparezca una pantalla como esta:

Transcript of Manual PHP

Page 1: Manual PHP

Instalando Apache 2.2.17

¿Qué es un servidor WEB?

Podríamos definir un servidor WEB como una aplicación que permite acceder a los recursos contenidos en algunos de los directorios del ordenador que la alberga a usuarios remotos que realizan sus peticiones mediante el protocolo HTTP.

Por tanto, instalar un servidor web no es otra cosa que instalar y configurar un programa en una unidad o directorio de un ordenador cualquiera.

¿Qué es «Apache»?

Bajo este nombre suele hacerse referencia a Apache Software Foundation, organización norteamericana que se autodefine con el objetivo de «... facilitar ayuda organizativa, legal y financiera para los proyectos de desarrollo de software tipo Open Source (código abierto)».

Uno de los proyectos más populares de Apache es el desarrollo y suministro -de forma gratuita y libre- de un software de servidor HTTP, conocido también como el servidor Apache.

La corta historia de «Apache»

Apache Software Foundation tiene su origen en febrero de 1995. Hasta ese momento el software más popular de servidores de HTTP era el desarrollado por Rob McCool, miembro del Centro Nacional para Aplicaciones de Super computación (NCSA), de la Universidad de Illinois.

El desarrollo de aquel primitivo software de servidor de NCSA (software de dominio público y código abierto) tuvo algunos problemas a mediados del año 1994. Esta circunstancia obligó a que muchos de los webmasters que utilizaban aquella aplicación tuvieran que desarrollar sus propias extensiones y corregir de forma individual los fallos de funcionamiento de la aplicación original.

Un pequeño grupo de aquellos webmasters entró en contacto –vía e-mail– con el objetivo de coordinar y conjuntar sus trabajos de corrección y mejora de la aplicación original de NCSA. Fueron Brian Behlendorf y Cliff Skolnick quienes –a través de una lista de correo– coordinaron el trabajo y lograron establecer un espacio compartido –de libre acceso para quienes participaran en el proyecto– en un ordenador instalado en California.

En febrero de 1995, ocho colaboradores del primitivo proyecto decidieron aunar sus esfuerzos de forma organizada y fundaron lo que fue conocido como Grupo Apache. Usando como base el HTTPD 1.3 de NCSA y aplicando los patchs desarrollados hasta ese momento, lanzaron la primera versión oficial (versión 0.6.2) del software de servidor de Apache en abril de 1995.

Aquella primera versión entró en un rápido e intenso proceso de mejoras y alcanzó una gran implantación como software de servidor –inicialmente era sólo para UNIX– para diferentes plataformas, una de las cuales es la versión para Windows que nosotros utilizaremos y que frente a otras alternativas de servidor HTTP tiene las ventajas de ser gratuita y muy eficaz.

Instalación de Apache

En el sitio de descargas de Apache o en el mirror español puedes encontrar un fichero llamado httpd-2.2.17-win32-x86-openssl-0.9.8o.msi (es la versión que he incluido en el autoinstalador) o parecido. Digo parecido porque las versiones van evolucionando y en esas direcciones tienes la última de las versiones y es posible que cuando accedas ya se haya actualizado. En cualquier caso si tienes interés por una versión específica de Apache puedes encontrarla en esta especie de repositorio histórico.

Una vez descargado el programa instalador bastará con que hagas doble clic sobre su icono para que te aparezca una pantalla como esta:

Page 2: Manual PHP

Si en vez de esta pantalla apareciera un mensaje de error, es muy probable que se deba a que no tienes instalada la aplicaciónWindows Installer Redistributable necesaria para ejecutar los ficheros con extensión .msi. Las versiones modernas de Windows –a partir de Windows98– la tienen incorporada, pero si por cualquier circunstancia –usando Windows98– no dispones de ella, puedes descargarla desde este enlace.

Al pulsar Next en la ventana anterior se llega a esta otra en la que es preciso tener seleccionada la opción I accept the terms in the license agreement para que se active el botón Next y poder continuar la instalación.

Al llegar a esta otra ventana –las anteriores solo requieren ir pulsando en el botón Next– es imprescindible rellenar los camposNetwork Domain, Server Name y Administrator's e-mail adress.

Page 3: Manual PHP

Como Network Domain y como Administrator's e-mail adress puedes poner nombres cualesquiera siempre que no contengan espacios ni caracteres especiales y en el caso de la dirección e-mail ésta debe contener el símbolo @. Como Server Name escribelocalhost.

La instalación recomendada de Apache –For All Users, on port 80, as a Service– contempla el uso del servidor como un servidor de red de área local accesible desde el resto de los ordenadores de esa red. Es lógico que en esas circunstancias el servidor se configure conautoarranque y que se inicie automáticamente en el momento en que se arranca el ordenador

Si piensas utilizar el servidor para probar y depurar tus propios scripts PHP seguramente vas a ocupar en ello sólo una pequeña parte del tiempo de uso del ordenador, por lo que parece ser más eficaz la opción de arrancarlo únicamente cuando sea necesario y, de esa forma, ahorrar esos recursos del sistema –que podrían ralentizar el uso de otros programas– cuando no necesites usar el servidor. Por esa razón te sugiero elegir la opción only for the Current User, on port 8080, when started manually y pulsar de nuevo Next.

En la pantalla que aparece inmediatamente después de la anterior se permite elegir el modo de instalación: Typical ó Custom. Elige la opción Custom y pulsa nuevamente sobre Next.

Page 4: Manual PHP

Te aparecerá esta nueva pantalla en la que el instalador te indica el directorio por defecto en el que pretende instalar el servidor Apache.

En este caso –aunque no es necesario hacerlo– te sugiero pulsar sobre Change y cambiarlo únicamente por razones de comodidad. La idea es agrupar todos los programas relacionados con PHP en un solo directorio (es la razón de definir c:/ServidoresLocales para ese fín) y dentro de él asignar un nombre fácilmente identificable para cada uno de los programas (Apache, en este caso).

Después del Change de la ventana anterior aparecerá esta otra. Ponla con la misma configuración que aparece en la imagen inferior, es decir, con Look in : (ServidoresLocales) y deja como Folder name: C:/ServidoresLocales/Apache/

Como podrás leer en la propia pantalla de instalación, es muy importante poner la barra invertida (\) detrás del nombre de la unidad al indicar el Folder name. No te olvides de hacerlo.

Page 5: Manual PHP

Una vez hayas pulsado OK volverá a aparecer la ventana que permite cambiar el directorio de instalación. Comprueba que ahora digaInstall to: C:/ServidoresLocales/Apache/ y pulsa Next en esa ventana y en las siguientes.

Aparecerá una última ventana donde dice Installation Wizard Completed y una vez que hayas pulsado el botón Finish la instalación de Apache habrá terminado.

Desactivar el monitor

Durante el proceso anterior se instala automáticamente el icono del monitor de servicio en la barra de tareas tal como puedes ver en esta imagen. Puedes cerrarlo pulsando con el botón derecho del ratón sobre el icono y eligiendo la opción exit.

Durante el proceso de instalación de Apache se incluye en el menú iniciode Windows un enlace al Monitor de Apache. Ello significa que cada vez que reinicies el equipo se instalará en la barra de tareas el icono del monitor. Para desactivarlo de forma definitiva bastará con eliminar tal acceso directo de la forma que puedes ver en la imagen inferior.

Arrancar Apache

Contemplando la opción de arranque manual, cada vez que quieras poner en marcha el servidor tendrás que acceder al icono Start Apache in console, que podrás encontrar en la localización de la imagen que ves aquí debajo.

Cuando se inicia Apache se abre siempre la consola de MS-DOS. En los primeros arranques es posible que aparezca en ella un mensaje como el que estás viendo en la imagen inferior.

Page 6: Manual PHP

Este mensaje de advertencia no afecta a las pruebas ni al funcionamiento de servidor. No obstante, en el esquema del proceso de configuración que tienes un poco más abajo puedes ver cómo hacer una modificación en la configuración del servidor que evita su aparición.

Otra forma de efectuar el arranque sería ejecutar directamente el programa httpd.exe que se encuentra en:C:/ServidoresLocales/Apache/bin. Haciendo doble clic sobre su icono o tecleando el comando desde el símbolo del sistema de la consola de Windows aparecerá una ventana de MS-DOS tal como la que ves en la imagen de arriba. ¡No la cierres nunca! Si lo haces sólo conseguirías apagar el servidor y además lo harías de una forma muy poco ortodoxa. Si te resulta incómoda minimízala pero no la cierres.

Una página de prueba

Con el servidor arrancado y funcionando, bastará con que abras tu navegador y escribas en la barra de direcciones una de las siguientes: http://localhost:8080 o http://127.0.0.1:8080. para que, si la instalación de Apache es correcta, se abra una página similar a la ves en la imagen. Sí eso ocurre, es la prueba de que el servidor está funcionando correctamente.

Página de prueba del servidor Apache. Observa la dirección:http://localhost:8080 Si te estás preguntado por qué esas direcciones –localhost ó 127.0.0.1– o por qué no hemos escrito el nombre de ninguna de página web, ¡ten un poco de paciencia! Trataremos de ello cuando configuremos Apache.

Apagar Apache

Aunque se puede desconectar cerrando la ventana de MS-DOS, el método correcto es: establecer esa ventana como ventana activa, pulsar las teclas Ctrl+C y esperar unos pocos segundos. Transcurrido ese tiempo la ventana MS-DOS se cerrará automáticamente y se habráapagado Apache.

Desinstalación de Apache

El servidor Apache se desinstala como cualquier otro programa de Windows. Bastará con acceder a Paneles de control, opción deAgregar o quitar programas elegirlo en la lista de programas instalados y desinstalarlo.

Antes de ejecutar ese proceso es necesario que el servidor esté apagado y saber que quedarán algunos restos de la instalación que –si fuera necesario– habría que eliminar manualmente.

El desinstalador de Apache no eliminará ni el directorio raíz –C:/ServidoresLocales/Apache/– ni los subdirectorios conf y htdocs del mismo. La razón es la seguridad. El directorio conf contiene los archivos de configuración del servidor –en páginas posteriores veremos que hay que modificar sus valores por defecto– y al no eliminarlos nos estará dando la opción de poder reutilizarlos (sin tener que empezar de cero) en futuras instalaciones.

Lo mismo ocurre con htdocs. En ese directorio será donde se almacenen los documentos que vayamos creando –páginas web, imágenes, etcétera– y, como es lógico, un error en el proceso de desinstalación podría provocar su pérdida. Esa es la razón de que se conserven. Por idénticas razones, si al reinstalar Apache ya existieran esos directorios no serían sobrescritos manteniéndose las configuraciones y documentos de la versión anterior.

Page 7: Manual PHP

Configuración de Apache

El correcto funcionamiento del servidor Apache requiere efectuar una serie de modificaciones en su fichero de configuración (httpd.conf). Las modificaciones propuestas y su justificación son las que se resumen en la siguiente tabla.

Fichero inicial httpd.conf (está en C:/ServidoresLocales/Apache/conf) Guardaremos el fichero modificado con dos nombres distintos. Uno de ellos

para hacer la primera prueba del servidor (httpd.conf) y el otro (httpd.orig) para utilizarlo como base de las modificaciones posteriores.

Guardar como (*) httpd.orig

Guardar como httpd.conf

Modificaciones en el fichero inicial

Línea Cambios Justificación

46

Donde dice: Mediante esta modificación evitaremos que el servidor escuche el puerto 8080 y le obligaremos a utilizar el puerto por defecto (80). Observa que en la página de prueba anterior habíamos escrito:http://localhost:8080 (para utilizar el puerto 8080 que era el que escuchaba el servidor) y una vez hecha esta modificación bastará poner: http://localhost ya que 80 es el puerto por defecto y no es necesario especificarlo.

Listen 8080

cambiar por:

Listen 80

172

Donde dice: Al descomentar (quitar #) esta línea evitaremos el mensaje de error que aparecía en la consola de MS-DOS al realizar las pruebas anteriores. Una vez omitamos el valor :8080 ya no será preciso especificar el puerto de acceso al servidor al pedir las páginas desde el navegador. Las peticiones se canalizarán por el puerto por defecto, el puerto 80.

#ServerName localhost:8080

cambiar por:

ServerName localhost

Nombres de dominio en un servidor local

Está opción no es necesaria pero es posible que te apetezca tener tu propio dominio local para poner utilizar una dirección personalizada, alternativa a localhost, como nombre de dominio.

Si utilizas Windows98 encontrarás un fichero con el nombre hosts –o hosts.sam– en el directorio Windows. Si tu sistema operativo esW2000, NT ó XP , Vista o Windows 7 tendrás que buscar ese fichero en un subdirectorio, del directorio de sistema, llamado\system32\drivers\etc\

Al editar ese fichero –hosts.sam o hosts- encontrarás algo similar a esto:

# For example: # # 102.54.94.97 rhino.acme.com # source server # 38.25.63.10 x.acme.com # x client host 127.0.0.1 localhost

Si añades una línea como la señalada en rojo –la IP 127.0.0.1 ha de mantenerse– y pones cualquier nombre de dominio (mispruebas.as) en el caso del ejemplo

# For example: # # 102.54.94.97 rhino.acme.com # source server # 38.25.63.10 x.acme.com # x client host 127.0.0.1 localhost 127.0.0.1 mispruebas.as

bastará con que guardes el fichero –con los cambios– con el nombre hosts (sin ninguna extensión y en el mismo directorio). A partir de ese momento, ya podrás escribir en el navegador -con Apache en marcha- cualquiera de estas direcciones: http://localhost o http://127.0.0.1 ohttp://mispruebas.as

Page 8: Manual PHP

Nombres de dominio en redes locales

Cuando se utiliza un servidor a través de una red de área local, lo habitual es acceder a él escribiendo como dirección la IP de ordenador en el que se encuentra instalado el servidor. Mediante un proceso similar al descrito más arriba, se puede modificar el fichero HOSTS de uno (o varios) de los equipos de la red añadiendo una línea con el nombre del dominio ficticio precedida de la dirección IP del servidor.

Si la IP del servidor local fuera 168.0.12.234, al incluir en el fichero hosts una línea diciendo: 168.0.12.234 mispruebas.as podríamos acceder a él tanto mediante: http://168.0.12.234 como a través de: http://mispruebas.as

¡Cuidado! Esta versión de Apache de 32 bits funciona sin problema aparente en Windows XP, Vista y W 7 tanto en sus versiones de 32 bits como en las de 64 bits.

Instalación de PHP 5.3.8

Qué es PHP y que requiere para ser usado

El lenguaje PHP –acrónimo de Hypertext Pre Processor– suele definirse como: interpretado y de alto nivel. Es un lenguaje de script cuyo código va insertado dentro de las páginas HTML y es interpretado, siempre, en el servidor. Se trata un lenguaje de estilo clásico, multiplataforma y cercano en su sintaxis a C++.

Creado por Rasmus Lerdorf en 1994; siendo su labor continuada por diversos desarrolladores, entre ellos Andi Gutmans y Zeev Suraski que fueron quienes propiciaron la aparición de la versión PHP 3.0. Actualmente la implementación principal de PHP (el estándar defacto), es producida por The PHP Group y se publica bajo la PHP License, a la que la Free Software Foundation considera una licencia desoftware libre.

Para trabajar con PHP es necesario tener instalado un servidor HTTP –en nuestro caso, Apache para Windows– y configurarlo de tal manera que sea posible la interacción entre ambas aplicaciones.

Instalando el PHP

En el sitio oficial de PHP podrás encontrar, bajo el epígrafe VC9 x86 Thread Safe, un fichero llamado php-5.3.8-Win32-VC9-x86.zip. Igual que ocurría en el caso de Apache las versiones pueden haber evolucionado. Por si tienes capricho por una versión concreta aquí tienes el museo del PHP dónde puedes encontrar cualquiera de las versiones que ya han pasado a formar parte de la historia.

Si tienes instalado Winzip, al hacer doble clic sobre el icono se te abrirá la ventana de este descompresor y al pulsar sobre el icono Extract se te abrirá una ventana como la que aparece en la imagen.

Si has seguido los pasos indicados en la página anterior para la instalación del servidor Apache, tendrás en tu ordenador un directorio llamado C:\ServidoresLocales.

Selecciona como directorio de descompresión C:/ServidoresLocales/php53 –tal como estás viendo en la figura– y realiza la extracción.

Page 9: Manual PHP

Una vez concluida la descompresión se habrá creado el directorio C:/ServidoresLocales/php53 que contendrá a su vez una serie de subdirectorios necesarios para el correcto funcionamiento de PHP. El proceso de instalación de PHP habrá terminado. Solo resta la configuración de Apache y PHP para poder empezar a utilizarlo. Esto será lo que veremos en la página siguiente.

¿Podemos probar ahora PHP?

Si ya has realizado el proceso de instalación tal como lo hemos descrito en los párrafos anteriores, puede que te preguntes si –como hemos hecho al instalar Apache– es posible hacer ahora una prueba de su funcionamiento.

La respuesta es NO. Analiza con un poco de detenimiento el proceso que hemos seguido y observarás que no hemos hecho una instalación sino que nos hemos limitado a descomprimir una serie de ficheros colocándolos en un directorio determinado.

Lo hemos hecho así porque lo que acabamos de instalar no es una aplicación que pueda funcionar de forma autónoma, sino elintérprete de un lenguaje de scripts –del lado del servidor– que requiere ser invocado por medio de un software de servidor. Así que, antes de que pueda ser utilizado, tendremos que indicarle al servidor Apache el sitio dónde ha de buscarlo y cuándo debe utilizarlo.

Configuración de Apache 2.2.17 para PHP 5.3.8

Para facilitar las cosas

A lo largo de este proceso –y en otros sucesivos– vas a encontrar ficheros que tienen el mismo nombre y que se diferencian sólo por la extensión. Sería una buena idea tener Windows configurado de forma que se visualicen siempre las extensiones de todos los tipos de ficheros.

Si nunca has usado esa opción de Windows bastará con que hagas doble clic en el icono Mi PC y vayas a la opción Ver (en el caso deWindows2000, XP o Vista habrás de ir a la opción Herramientas) de la ventana que se abre. Cuando se trate de Windows7 habrás de ir aPaneles de Control->Opciones de carpeta en el submenú de esa opción elige Opciones de Carpeta. Una vez en Opciones de Carpeta debes elegir la opción Ver de la nueva ventana y buscar la línea en la que dice Ocultar extensiones

Page 10: Manual PHP

para los tipos de archivos conocidos y,desmarcando su casilla de verificación y pulsando sobre Aplicar y Aceptar ya podrás visualizar las extensiones de todos los ficheros.

Modificación del fichero httpd.conf para configurar PHP

Tal como comentábamos en la página anterior la utilización de PHP requiere introducir algunos cambios en la configuración de Apache. En el momento en el que se instala este servidor se crea automáticamente en el subdirectorio C:/ServidoresLocales/Apache/conf unfichero llamado httpd.conf que contiene la configuración por defecto del servidor Apache. Como recordarás lo hemos modificado durante el proceso de instalación para que el servidor utilice el puerto 80.

Tendremos que hacer algunas modificaciones más. Pero, como precaución por si tenemos algún problema y necesitamos volver a utilizar el fichero de la configuración por defecto, vamos a hacer una copia de seguridad. Abriremos el documento httpd.conf con un editor de textos cualquiera –lo más cómodo será utilizar Notepad ++ u otro editor que señale los números de línea– y con la opción guardar comocrearemos una copia con el nombre httpd.orig

¡Cuidado! Si has utilizado el bloc de notas de Windows es probable que en el proceso anterior no te haya guardado comohttpd.orig sino como httpd.orig.txt. Comprueba los ficheros del directorio C:/ServidoresLocales/Apache/conf y si te ha ocurrido lo que comentamos tendrás que recurrir al conocido método de pulsar sobre el icono del fichero con el botón derecho del ratón, elegir la opción Cambiar nombre y quitar el .txt que aparece al final del nombre del archivo.

Una vez hecho esto ya podremos hacer las modificaciones con toda tranquilidad, así que volveremos a abrir el fichero httpd.conf para hacer los cinco cambios siguientes:

Fichero inicial httpd.conf No es necesario modificar el nombre de este fichero. Ya debemos de tener una copia previa llamada http.orig Guardar como httpd.conf

Modificaciones en el fichero inicial

Línea Cambios Justificación

129

Donde dice: Hemos de indicar a Apache que cargue el módulo que se encuentra en el sitio que indican la ruta y el nombre del fichero. Este módulo específico para Apache 2.2 (php5apache2_2.dll) es el que permite que el servidor Apache interactúe con PHP 5.

(línea en blanco) cambiar por:

LoadModule php5_module C:/ServidoresLocales/php53/php5apache2_2.dll

219

Donde dice:

Evita que se visualice la lista de ficheros contenidos en un directorio de servidor si se intenta acceder a una página inexistente

Options Indexes FollowSymLinks cambiar por:

Options -Indexes FollowSymLinks MultiViews

241

Donde dice:

Permite indicar que ficheros y en que orden han de buscarse cuando en la petición se indique el nombre del directorio pero no se especifique ninguna página.

DirectoryIndex index.html cambiar por:

DirectoryIndex index.html index.htm index.php

384

Donde dice: Le indica a Apache que los únicos ficheros susceptibles de contener scripts que deban ser ejecutados por el intérprete de PHP son aquellos que tienen como extensión .php. Si un fichero con extensión distinta contuviera scripts PHP éstos no serían ejecutados.

(línea en blanco) cambiar por:

AddType application/x-httpd-php .php

385

Donde dice: La inclusión de la línea PHPIniDir (una novedad respecto a versiones anteriores de Apache y PHP) permite indicar la ruta de acceso al fichero de configuración de PHP (php.ini) En la página siguiente trataremos la configuración de PHP.

# sustituir por:

PHPIniDir "C:/ServidoresLocales/php53"

Una vez efectuados estos cambios ya podremos guardar el fichero, sin cambiar su nombre original –httpd.conf–, y tendremos lista la nueva configuración de Apache. Solo nos falta reiniciar el servidor para que los cambios sean efectivos

Algunos detalles importantes sobre httpd.conf

Page 11: Manual PHP

La directiva de configuración de Apache, DirectoryIndex, tiene particular interés para comodidad del usuario. A través de ella le estaremos diciendo a Apache que cuando reciba una petición –dirigida a uno cualquiera de los directorios accesibles a través de HTTP– en la que no se especifique ningún nombre de página, debe comprobar si en ese directorio existe alguna página llamada index.html.

En caso de que dicha página existiera la mostraría y en caso contrario volvería a comprobar para ver si existe alguna otra llamadaindex.htm (el segundo nombre de página contenido en esa línea). Si tampoco se diera esa coincidencia continuará viendo si existe index.php. En el caso de que no encontrara ninguna que coincida con los nombres indicados en esta directiva daría un mensaje de error del tipo: File not found.

Esta opción de configuración de Apache es la que nos permite escribir direcciones del estilo www.boe.es en las que –sin especificar ningún nombre de página– nos aparece en pantalla el mismo contenido que si hubiéramos escrito: www.boe.es/index.php

Si –con la configuración descrita en la tabla anterior– intentamos acceder a la dirección: http://localhost/images/ nos aparecería un mensaje como este:

Esto ocurre como consecuencia de haber puesto el signo menos delante de Indexes (¡cuidado! debe ir pegado a Indexes sin ningún espacio intermedio) en la línea Options -Indexes FollowSymLinks MultiViews.

El subdirectorio images no contiene ficheros con nombre index.html, ni tampoco index.php ni index.htm (los especificados en la directiva DirectoryIndex) y el signo menos lo que hace es denegar el acceso (en el caso de no indicar el nombre de algún documento en la dirección) a los directorios que no los contengan. Si no hubiéramos modificado esa directiva nos aparecería una lista con todos los ficheros contenidos en ese subdirectorio, tal como puedes ver en esta imagen.

Page 12: Manual PHP

La configuración de Apache permite múltiples opciones y ofrece muchas posibilidades. Tantas, que justificarían todo un curso dedicado al estudio de este servidor y sus opciones de configuración. No entraremos en ese ámbito pero, aunque no vamos a modificarlos, es conveniente conocer algunos otros elementos importantes de httpd.conf.

DocumentRoot "C:/ServidoresLocales/Apache/htdocs"

Esta línea que se ha incluido automáticamente al hacer la instalación indica la ruta y el nombre del directorio en el que,obligatoriamente, han de estar los documentos –y los subdirectorios– susceptibles de ser servidos a través del protocolo HTTP.

Cualquier documento que estuviera fuera de este directorio sería inaccesible, por lo tanto todos los documentos PHP que vayas creando deberás guardarlos dentro de este directorio htdocs. Como es lógico, bastaría cambiar esa línea de la configuración para utilizar como raíz del servidor otro directorio cualquiera.

ServerName localhost

Esta otra línea –también contenida en httpd.conf– es la que determina el nombre del servidor y a través de ella se identifican las peticiones que el navegador realiza a ese servidor. Por esta razón, cuando probábamos la instalación de Apache, escribíamos como dirección http://localhost.

Configuración de PHP 5.3.8

Un directorio importante

Antes de comenzar la configuración de php es conveniente que creemos dentro de C:/ServidoresLocales/ un subdirectorio con nombre tmp que será utilizado para contener las eventuales variables de sesión que fuera necesario manejar por parte de PHP y cuya ubicación ha de ser especificada en la configuración de este.

No es preciso que utilicemos ni este nombre ni este directorio ni tampoco es necesario que se incluya en ningún directorio concreto del servidor. La elección del directorio ServidoresLocales obedece únicamente a la razón ya comentada de intentar incluir allí todo lo relativo al PHP.

Configuración de PHP

Con la configuración de PHP ocurre lo mismo que en el caso de Apache. También existen un montón de posibilidades de configuración –iremos viendo algunas de ellas a medida que vaya siendo necesario– a través de las cuales se puede modificar de forma sustancial el comportamiento de PHP.

El elemento clave de la configuración de PHP es un fichero denominado php.ini que debe estar contenido en el directorioC:\ServidoresLocales\php53 dado que fue esa la ubicación que hemos incluido en la directiva PHPIniDir cuando configurábamos Apache en la página anterior.

Antes de empezar la configuración

Si abrimos nuestro directorio: C:/ServidoresLocales/php53 encontraremos dentro de él tres ficheros que deberemos copiar a nuestro sistema tal como se indica en la siguiente tabla.

Ficheros originales php5ts.dll libeay32.dll ssleay32.dll Copiar en el subdirectorio que se indica (dentro del que contiene el S.O.)

S.O. Directorio

Windows98 \system

W2000 Windows NT Windows XP Windows Vista W 7 32 bits

\system32

W 7 64 bits \SysWOW64

Page 13: Manual PHP

Si ya tuviéramos un fichero con ese nombre en el directorio de destino habríamos de sobrescribirlo sustituyendo el preexistente por el que tenemos en C:/ServidoresLocales/php53. Con ello estaríamos asegurándonos de que las versiones de estas librerías son las correspondientes a la versión de PHP que pretendemos utilizar.

Configuración de PHP

En el directorio C:/ServidoresLocales/php53 tenemos un fichero llamado php.ini-development. Lo abriremos con un editor de textos –por ejemplo el Notepad ++– y haremos estas modificaciones:

Fichero inicial php.ini-development El fichero php.ini no viene incluido en las distribuciones de PHP. Por esa razón debemos crearlo partiendo, en este caso, de su versión de desarrollo Guardar como php.ini

Modificaciones en el fichero inicial

Línea Cambios Justificación

798

Donde dice: Asignaremos como valor de doc_root una ruta que, como observarás, apunta el directorio htdocs. En otras palabras, le estamos diciendo a PHP en qué sitio del ordenador –fíjate que incluimos una ruta absoluta– se ubicarán los ficheros cuyos scripts debe interpretar.

doc_root = cambiar por:

doc_root=C:/ServidoresLocales/Apache/htdocs

809

Donde dice: Con esta modificación le señalamos al intérprete de PHP en qué sitio debe buscar las extensiones que pueda necesitar para la ejecución de sus scripts. Estas extensiones, que vienen con la instalación de PHP, se descomprimen –por defecto– en un subdirectorio llamado ext y esa es la razón por la que la ruta incluida en esta modificación apunta a un directorio con ese nombre.

; extension_dir = "ext" cambiar por:

extension_dir ="C:/ServidoresLocales/php53\ext\"

950

Donde dice:

Descomentaremos (quitamos el punto y coma que lleva delante) la línea alusiva a la librería php_gd2 con lo cual estamos activando la opción de que PHP pueda ejecutar instrucciones relativas a imágenes dinámicas.

;extension=php_gd2.dll cambiar por:

extension=php_gd2.dll

951

Donde dice: Descomentaremos la línea alusiva a la librería php_gettext.dll con lo cual estamos activando la opción de incluir soporte para GNU gettext la API del NLS (Native Language Support) que permite internacionalizar las aplicaciones PHP

;extension=php_gettext.dll cambiar por:

extension=php_gettext.dll

957

Donde dice: Descomentaremos la línea alusiva a la librería php_mbstring.dll con lo cual estamos activando la opción que permite utilizar caracteres multibyte lo cual evita la restricción de uso exclusivo de idiomas con un máximo de 256 carácteres

;extension=php_mbstring.dll cambiar por:

extension=php_mbstring.dll

959

Donde dice:

Descomentaremos la línea alusiva a la librería php_mysql.dll con lo cual estaremos habilitando la posibilidad de usar las funciones MySQL

;extension=php_mysql.dll cambiar por:

extension=php_mysql.dll

1085

Donde dice: Asignamos la dirección IP que utilizaremos para el proceso Simple Mail Transfer Protocol cuando PHP se utilizado conjuntamente con un servidor de correo para el envío de emails

SMTP= localhost cambiar por:

SMTP = 127.0.0.1

1091

Donde dice:

Es la dirección del remitente que utilizaremos para el envío de mensajes de correo electrónico

;sendmail_from= [email protected] cambiar por:

sendmail_from= [email protected]

1471

Donde dice: Cuando PHP utiliza sesiones es necesario establecer un lugar del servidor en que almacenar informaciones temporales relativas a ellas. Para esa finalidad hemos creado el directorio tmp e incluimos aquí la ruta absoluta hacia ese directorio

;session.save_path = "/tmp" cambiar por:

session.save_path = C:/ServidoresLocales/tmp

997

Donde dice:

Las últimas versiones de PHP requieren que especifiquemos la zona horaria correspondiente al lugar donde está ubicado nuestro servidor

;date.timezone = cambiar por:

date.timezone ='Europe/Madrid'

Page 14: Manual PHP

226

Donde dice: El caso de short_open_tag resulta particularmente interesante. En versiones anteriores se incluía la opción ON por defecto. A partir de esta versión parece que aparecerá como OFF y será preciso cambiar su configuración (tal como hacemos en nuestro caso) o tener en cuenta las restricciones que impone su modo OFF. Si está en modo OFF NO PERMITE la sintaxis <? ?> para incluir el código PHP siendo obligatorio hacerlo mediante <?php ?> Cuando su estado es ON interpreta ambas formas de sintaxis.

short_open_tag = Off cambiar por:

short_open_tag = On

¡Cuidado! Es posible que no residas en España. Sí fuera así no hay ninguna razón para que configures la zona horaria española en tu servidor. ¿Qué como configuras la de tu pais? Mira aquí seguro que encuentras el valor que necesitas para tu configuración personalizada.

Comprobación de PHP

Una vez que hayamos modificado los apartados anteriores y guardado el fichero con el nombre php.ini en el directorio C:/ServidoresLocales/php53 llega el momento de comprobar si PHP funciona correctamente.

Un script de prueba

Para hacer esta comprobación deberemos escribir nuestro primer script PHP. Abriremos nuestro editor –Notepad++– y escribiremosexactamente esto:

<?php phpinfo(); ?>

Ahora lo guardaremos en C:/ServidoresLocales/Apache/htdocs –recuerda que este es el que hemos configurado como directorio raíz de servidor– con el nombre info.php

Recuerda también que es probable que el bloc de notas haya añadido la extensión .txt y que el fichero puede haber sido guardado como info.php.txt. Lo comprobaremos mirando el directorio htdocs y cambiando el nombre si fuera necesario.

Probando el primer script

Una vez instalados y configurados Apache y PHP y creado el fichero info.php, ha llegado el momento de comprobar si hemos hecho correctamente las configuraciones y si todo funciona bien.

Arrancaremos el servidor Apache y una vez que tengamos la ventana de MS-DOS abierta, deberemos visualizarla sin ningún mensaje (completamente en negro). Eso significaría que Apache está funcionando correctamente con el módulo PHP. Si apareciera algún mensaje de error sería necesario corregir la línea del fichero de configuración a la que se aluda en el propio mensaje.

Solo faltaría abrir el navegador y escribir una de estas dos direcciones: http://localhost/info.php o http://127.0.0.1/info.php y aparecería en pantalla una página como esta:

Page 15: Manual PHP

De ser así, el proceso de instalación y configuración habría terminado y esa página nos estaría facilitando toda la información relativa a la configuración actual de nuestro PHP.

¡Cuidado! En la configuración de PHP (php.ini) –bajo Windows– debemos usar siempre la barra invertida ( \ ) a la hora de escribir los paths. Cuando tratamos la configuración de Apache (httpd.conf) –también bajo Windows– lo hemos hecho al revés, hemos escrito todos los paths utilizando la barra normal ( / ). Ten muy presente que estas sintaxis son distintas y cuando efectúes modificaciones de configuración utiliza la adecuada a cada uno de los ficheros.

¡Cuidado! Aunque nuestra configuración de short_open_tag = On nos permite utilizar como delimitadores del código PHP tanto<?php ?> como <? ?> resulta más aconsejable inclinarse por la primera opción ya que nos garantizaría que nuestros scripts podrán ejecutarse sea cual fuere la configuración de esta directiva. Hemos de tener en cuenta que en nuestro servidor de pruebas tenemos posibilidades de modificar la configuración pero puede haber casos en los que no tengamos acceso a esas opciones (servicios de hosting ajenos, por citar un ejemplo) y nos surjan dificultades que nos obliguen a modificar todo nuestro código. Puede resultar un poco más incómodo agregar la «coletilla» php pero seguramente es lo más aconsejable.

Instalar MySQL 5.1.56 y/o MariaDB

Proceso de instalación ySQL

En esta página podrás encontrar el enlace de descarga del fichero mysql-essential-5.1.56-win32.msi que contiene el fichero de instalación de MySQL. Para descargarlo va a ser necesario que te registres como usuario. Al hacerlo te enviarán a la dirección de correo que facilites una contraseña que te permitirá acceder a la descarga. Tal como hemos comentado en el caso de programas anteriores es posible que la versión que encuentres allí sea más reciente que la que he incluido en el autoinstalador. Las versiones más antiguas de MySQL puedes encontrarlas en este enlace.

Al hacer doble clic sobre su icono comienza el proceso de instalación cuya secuencia de pantallas sucesivas puedes ir viendo en la imagen que tienes aquí debajo.

Page 16: Manual PHP

Usaremos el directorio C:/ServidoresLocales/mysql para hacer la instalación del servidor. Configuraremos el directorio que ha de contener las bases de datos como un subdirectorio (con nombre data) del anterior.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Arrancar MySQL

Puesta en servicio desde la consola de MS-DOS

Una vez finalizado el proceso de instalación ya podremos poner en marcha el servidor. Lo haremos desde el símbolo del sistema usando los comandos de MS-DOS. Para poner en marcha MySQL basta con situarnos en C:/ServidoresLocales/mysql/bin (subdirectorio bindel directorio en el que se efectuó la instalación de MySQL) mediante el comando: CD C:/ServidoresLocales/mysql/bin (subdirectorio bin y una vez allí ejecutar:

A partir de ese momento el servidor MySQL ya estará activo y ya podríamos gestionarlo tanto desde PHP como desde la propia consola MS-DOS.

¡Cuidado! En algunas versiones de Windows es posible que, al arrancar el servidor, se quede el cursor intermitente en la ventana de MS-DOS sin que regrese al prompt. Basta con cerrar la ventana –Símbolo del Sistema– y reabrirla. El servidor seguirá activo y ya será posible la ejecución de comandos desde esta consola.

Apagar el servidor MySQL

Para poder apagar el servidor es necesario utilizar un nombre de usuario. En este caso utilizaremos el nombre de usuario root (el usuario que se crea por defecto). La sintaxis sería la siguiente:

Acceso a las bases de datos

Page 17: Manual PHP

Al instalar el programa se crea –dentro del directorio mysql– un subdirectorio llamado bin que es el que contiene los programas que gestionan las bases de datos en MySQL. Otro subdirectorio, denominado data, es destinado a contener todas las bases de datos. Cada una de ellas estará contenida en un subdirectorio diferente que tendrá el mismo nombre que de la base que contiene.

El instalador de MySQL crea de forma automática dos bases de datos con los nombres: mysql ytest. ¡No debes borrarlas! La denominada test es una base de datos que permite chequear la instalación y la configuración de MySQL mientras que la denominada mysql contiene, entre otros, los datos relativos a usuarios y si no está presente MySQL no funcionará.

El acceso a las bases de datos MySQL requiere que los usuarios estén identificados mediante un nombre (login) y –opcionalmente– una contraseña (password) de acceso. El propio instalador de MySQL incluye, por defecto, un usuario con nombre root, que utiliza como password una cadena vacía.

A cada usuario se le pueden asignar privilegios de modo que, por ejemplo, solo pueda realizar consultas, o acceder a tablas concretas. El usuario root goza de todos los privilegios posibles y podría ser usado para todos los supuestos. No obstante, como en situaciones reales es un usuario desaconsejable por el riesgo que entraña utilizar usuarios por defecto, vamos a crear un nuevo usuario –con contraseña y con todos los privilegios– que será el que utilizaremos en los ejemplos relativos a MySQL.

Creación de un nuevo usuario

Al nuevo usuario al que aludimos en el párrafo anterior vamos a darle los máximos privilegios (ALL PRIVILEGES) de modo que pueda gestionar cualquier base de datos, tabla y que además pueda crearlas, borrarlas o modificarlas sin restricción alguna.

Es importante que lo creemos con la sintaxis exacta, ya que los ejemplos de los temas relacionados con MySQL están desarrollados considerando el usuario pepe con contraseña pepa (ambas en minúsculas).

Ejecutar instrucciones MySQL

El primer paso para poder ejecutar sentencias MySQL será tener el servidor activo. Para ello habrá que seguir el proceso descrito anteriormente. El paso siguiente sería especificar qué usuario va a manejar MySQL. Dependiendo de sus privilegios tendrá accesos a parte o a todas las funciones. Para especificar el usuario root debemos escribir lo siguiente:

Si se tratara de un usuario con una contraseña distinta de la cadena vacía habría que añadir –p detrás del nombre de usuario tal como puedes ver en esta imagen. En este caso el sistema nos pedirá que escribamos la contraseña del usuario en cuestión.

Este mensaje de bienvenida y el cambio de directorio (observa que ahora el prompt ha cambiado y apunta hacia mysql) nos indican que MySQL está listo para recibir instrucciones.

Lo único que haremos desde aquí será crear un usuario. En adelante nos comunicaremos con MySQL a través de la web y usando como herramienta de comunicación el PHP.

Page 18: Manual PHP

Creación del usuario pepe

Para crear el nuevo usuario utiliza exactamente la sintaxis que ves en la imagen.

¡Cuidado! El pulsar Enter en MySQL no significa –como ocurre en DOS– que se vaya ejecutar el comando. Si observas la imagen, hemos pulsado Enter detrás de la palabra localhost de la primera línea y lo que ha ocurrido es que el cursor ha saltado hasta la segunda incluyendo automáticamente –> que significa que continúa la instrucción anterior. En MySQL la ejecución de una instrucción requiere que se haya tecleado ; inmediatamente antes del pulsar Enter.

Salir del interface de usuario

Para abandonar la interface de usuario basta con escribir exit tal como ves en la imagen. El sistema escribirá su mensaje de despedida de forma automática y el prompt regresará a C:/ServidoresLocales/mysql/bin.

¡Cuidado! Independientemente de que puedas crear otros usuarios con otras contraseñas es imprescindible crear el usuariopepe con contraseña pepa. Todos los ejemplos que incluimos requieren este usuario.

Desinstalación de MySQL

MySQL se desinstala desde la opción Agregar o quitar programas como cualquier otro programa de Windows. El proceso de desinstalación no elimina ni los ficheros ini ni tampoco el subdirectorio data. Este último se conserva como medida de seguridad ya que contiene todas las bases de datos y de eliminarlo se perdería la información. Si en algún momento tratas de desinstalar para hacer una nueva instalación, lo aconsejable sería mantener el directorio data y buscar y eliminar todos ficheros my.* (los ini de la instalación anterior) antes de realizar la nueva instalación.

Instalación de MariaDB

Según su página web oficial «MariaDB es un servidor de base de datos que ofrece una alternativa funcional de reemplazo para MySQL. MariaDB está construido por algunos de los autores originales de MySQL, con la ayuda de la comunidad más amplia de desarrolladores de software libre y de código abierto. Además de la funcionalidad principal de MySQL, MariaDB ofrece un rico conjunto de funciones mejoradas, incluyendo motores de almacenamiento alternativo, optimización del servidor, y los parches. »

Desde el punto de vista operativo podríamos decir que MariaDB es a MySQL lo que LibreOffice es a OpenOffice. Es decir, son «forks» o derivaciones de aquellas, bajo licencia GPL (General Public License), cuyo nacimiento se justifica en los temores de lo que en un futuro pueda derivarse de las adquisión por parte de Oracle® de Sun Microsystems® que a su vez en propietaria de MySQL.

Aunque se ofrece actualmente bajo la GNU GPL para cualquier uso compatible con esta licencia, MySQL es patrocinado por una empresa privada, que posee el copyright de la mayor parte del código. Esa circunstancia es radicalmente distinta a lo que ocurre con otros proyectos como Apache, donde el software es desarrollado por una comunidad pública y los derechos de autor están fragmentados ya que cada porción del código es propiedad de su autor individual.

Page 19: Manual PHP

Especulaciones futuristas aparte, MariaDB está disponible en este enlace en su versión 5.3. Entre las múltiples opciones de descarga disponibles sugerimos «marcar» las casillas de verificación correspondientes a Windows y a zip-file ya que no van a requerir ningún proceso de instalación y, además, van a permitirnos mantener en un mismo equipo una versión de MariaDB junto con otra de MySQL.

Una vez hayamos descargado el fichero .zip bastaría con descomprimirlo en cualquier lugar del equipo y ya estaría disponible para su uso. Bastaría con arrancarlo y pararlo utilizando exactamente los mismos comandos ya descritos para MySQL. La única diferencia estaría en que antes de ejecutar el comando MS-DOS mysqld de puesta en marcha de servidor habría que situar el prompt apuntando a subdirectorio bin del directorio en el que hayamos descomprimido MariaDB.

El uso de una u otra base de datos sólo requeriría elegir el directorio bin de una u otra para ejecutar desde él los comando de puesta en marcha y parada. Respecto a la utilización desde PHP no hemos podido constatar diferencia funcional alguna. Es decir, todo lo descrito en estas «Memorias» para MySQL es absolutamente válido, sin modificación alguna, para el caso de que optes por usar la base de datos MariaDB.

¡Cuidado! Puede ocurrir que en un momento determinado no funcione correctamente MySQL (o MariaDB) y que al intentar acceder a la dirección http://localhost/phpMyAdmin/ lees un mensaje de error similar a este: #2002 - El servidor no está respondiendo (o el socket del servidor MySQL local no está configurado correctamente). Algunas veces el problema puede producirlo un «exceso de limpieza». Algunas aplicaciones del tipo Ccleaner o similares pueden estar configuradas para que al ejecutarlas se borren los ficheros cuyo tamaño sea cero bits. Esa acción podría eliminar algunos ficheros de ese tamaño contenidos el subdirectorio /data/mysql y su inexistencia puede ser la causa del problema. Para comprobarlo podemos editar un fichero con extensión .err que suele tener el mismo nombre que el equipo y que está en el subdirectorio /data de mysql. Se identifica facilmente porque es el único con esa extensión. Si vemos que contiene líneas similares a estas: [ERROR] Fatal error: Can't open and lock privilege tables: File '.\mysql\host.MYD' not found (Errcode: 2)n> File '.\mysql\plugin.MYD' not found (Errcode: 2 File '.\mysql\time_zone_leap_second.MYD not found (Errcode: 2) File '.\mysql\servers.MYD' not found (Errcode: 2) bastaría con crear ficheros nuevos (desde el mismo bloc de notas) en blanco (sin ningún contenido) y guardarlos con esos nombres en el subdirectorio /data/mysql. Reiniciaríamos el servidor y MySQL (o MariaDB) volvería a funcionar correctamente.

Instalando phpMyAdmin

¿Qué es PHPMyAdmin?

PHPmyAdmin es simplemente un conjunto de utilidades y scripts escritos en lenguaje PHP que permiten gestionar bases de datosMySQL a través de una página web.

Mediante esta herramienta, sin conocer el lenguaje MySQL, podremos modificar, consultar, crear y borrar tanto bases de datos como tablas y registros contenidos en ellas incluyendo la gestión de usuarios –recuerda que MySQL requiere claves y contraseñas– y susprivilegios de acceso.

La versión de MySQL para Windows no dispone de una interface propia que permita gestionar sus bases de datos a través de Windows. La única posibilidad de gestión es a través de MS-DOS y eso requiere que el usuario sepa utilizar los comandos propios de las funciones MySQL.

Sin esos conocimientos de SQL, tendríamos como recurso la posibilidad de gestionar las bases de datos a través de nuestros propios scripts PHP, pero eso nos exigiría conocer con una cierta profundidad –a medida que avancemos lo iremos logrando– las funciones que PHP posee para este tipo de labores. Es por eso que este conjunto de herramientas resulta muy cómodo y fácil de utilizar y está convirtiéndose –de hecho ya lo es– en el soporte estándar que la mayoría de los hosting facilitan a sus usuarios para gestionar las bases de datos alojadas en sus servidores.

Instalación de PhpMyAdmin

Page 20: Manual PHP

En el sitio http://www.phpmyadmin.net/ hay un enlace para la descarga del fichero phpMyAdmin-3.3.10-all-languages.zip. Tal como venimos comentando en páginas anteriores es posible que ya se hayan producido actualizaciones y la versiones que veas no coincidan exactamente con la que aquí te referimos. No suele tener mayor transcendencia. Las versiones históricas de phpMyAdmin puedes encontrarlas en este sitio.

El fichero zip obtenido en la descarga tenemos que descomprimir obligatoriamente (contiene scripts de PHP) en el directorioC:/ServidoresLocales/Apache/htdocs. Al hacerlo se creará un directorio llamado phpMyAdmin-3.3.10-all-languages al que vamos a cambiar el nombre por otro más cómodo y fácil, dado que al utilizar phpMyAdmin tendremos que escribir el nombre de ese directorio con bastante frecuencia.

Vamos a renombrarlo como phpMyAdmin.

Será necesario editar el fichero Config.class.php (contenido en el subdirectorio libraries de phpMyAdmin) y modificar la línea nº 25.

Dónde dice: var $default_source = './libraries/config.default.php'';

deberemos poner: var $default_source = './libraries/config.inc.php';

En el mismo subdirectorio libraries hay un fichero llamado config.default.php. Abrámoslo con nuestro editor Notepad ++, guardémoslo (sin hacer ninguna modificación) con el nombre config.inc.php y, ya en este último fichero, hagamos los cambios que se detallan en la tabla siguiente.

Fichero inicial config.default.php

Guardar como config.inc.php Modificaciones en el fichero config.inc.php

Línea Cambios

40

Donde dice:

$cfg['PmaAbsoluteUri'] = '': cambiar por:

$cfg['PmaAbsoluteUri'] = 'http://localhost/phpmyadmin/';

177

Donde dice:

$cfg['Servers'][$i]['auth_type'] = 'cookie'; cambiar por:

$cfg['Servers'][$i]['auth_type'] = 'config';

227

Donde dice:

$cfg['Servers'][$i]['nopassword'] = false; cambiar por:

$cfg['Servers'][$i]['nopassword'] = true;

363

Donde dice:

$cfg['Servers'][$i]['AllowNoPasswordRoot'] = false; cambiar por:

$cfg['Servers'][$i]['AllowNoPasswordRoot'] = true;

Hechas las modificaciones en ambos ficheros ya estaremos en condiciones de probar su funcionamiento.

Prueba de funcionamiento de phpMyAdmin

Arranquemos Apache –dejando minimizada su ventana MS-DOS– y arranquemos también MySQL. Con ambos servidores activos escribamos en el navegador la dirección: http://localhost/phpmyadmin/. Deberá abrirse una página como esta:

Page 21: Manual PHP

Explorando los enlaces de la parte izquierda de la pantalla -mysql(23)- podremos visualizar los contenidos de la tabla user que nos dará una imagen como esta:

dónde podemos ver la lista de usuarios actuales y sus privilegios. Vemos el archi mencionado usuario root y también al nuevo usuariopepe con su contraseña encriptada. Las columnas marcadas con Y/N contienen las tablas de privilegios de cada usuario. Observa que tantoroot como pepe tienen todos los privilegios, mientras que, el tercer usuario (con nombre en blanco y creado durante la instalación de MySQL) no tiene ninguno.

Instalación del servidor FileZilla FTP

¿Qué es un servidor FTP?

Conceptualmente un servidor FTP no difiere en nada de un servidor HTTP. Las diferencias entre los diversos tipos de servidores estriban en los programas que usan –software de servidor– en el tipo de petición que aceptan y en el protocolo que requieren las peticiones que atiende.

Page 22: Manual PHP

En el caso de Apache se trata de un servidor HTTP porque sólo acepta peticiones a través de ese protocolo. Al hablar de servidores FTP nos referimos a aquellos que únicamente aceptan peticiones realizadas por medio del protocolo conocido como FTP (File TransferProtocol). Como en todos los demás casos, la comunicación cliente-servidor requiere el uso por parte de ambos del software adecuado a su protocolo concreto.

Clientes FTP

Para hacer una petición FTP, igual que en cualquier otro caso de petición, necesitamos disponer del software adecuado para realizarla. Recuerda que, en realidad, un cliente no es otra cosa que el programa que se utiliza para realizar un determinado tipo petición a unservidor. Existen varios clientes FTP en el mercado. Si no dispones de ninguno descargar FileZilla Client desde el sitio de FileZilla.

Las versiones más modernas de algunos navegadores también permiten realizar peticiones mediante este protocolo. Si escribimos en la barra de direcciones del navegador –se requiere la versión 5 ó superior de Internet Explorer –:ftp://super:superi@localhost y un servidor FTP –con nombre localhost– está activo, veremos que aparecen en la ventana del navegador los iconos de los ficheros contenidos en el directorio root del servidor y que, a la vez, se nos ofrece la posibilidad de: borrar archivos; crear subdirectorios; copiar ficheros (desde el servidor a cualquier otro directorio de nuestro ordenador o viceversa) sin más que seguir métodos idénticos a los que se utilizan habitualmente en Windows.

El acceso a un servidor FTP –salvo que permita el acceso de forma anónima– requiere del cliente tres datos: nombre del servidor,nombre de usuario y contraseña, que son los que aparecen en azul un poco más arriba. En esa dirección aparecen –marcados en rojo–:(ftp://) que indica el tipo de protocolo que se utiliza en la petición; (:) cuya finalidad es la de actuar como separador entre el nombre de usuario y la contraseña; y, (@), que hace también función de separador, en este caso entre la contraseña y el nombre del servidor.

Aún a riesgo de parecer reiterativos, queremos insistir en que para que una petición, como la que comentamos, pueda ser atendida se requiere, de forma imprescindible, que exista un servidor FTP activo.

Hay una posibilidad añadida. Mediante funciones de PHP también es posible gestionar servidores FTP sin necesidad de recurrir a ningún cliente específico. Lo trataremos en los contenidos de programación relativos a las funciones FTP.

Instalación de un servidor FTP

Se trata del instalador de un servidor FTP, que puedes descargar desde FileZilla_Server-0_9_37.exe y que también puedes encontrar en esta página. Igual que en páginas anteriores las versiones más antiguas podemos encontrarlas aquí. Una vez descargado el instalador ya podemos seguir la secuencia de instalación que puedes ir viendo en esta imagen.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Arrancar y apagar el servidor

Después de instalar el servidor aparecerán en Programas un grupo con unos iconos como estos. Como resulta obvio, el señalado con Start permite la puesta en marcha del servidor y el señalado con Stopsirve para detener su funcionamiento.

La opción Server Interface –para usarla es necesario que previamente hayamos puesto en marcha el servidor– permite configurar del servidor y controlar su funcionamiento. Nos valdremos de él para llevar a cabo la configuración de nuestro servidor FTP y nos permitirá visualizar el estado del servidor y el registro todas las acciones que se realicen en él.

Si has optado por la instalación automática de todos los programas bastará con que pulses sobre el icono correspondiente al servidor FTP desde la ventana de Gestión de Servidores.

Page 23: Manual PHP

¡Cuidado! Sobre Windows Vista y/o Windows 7 es necesario ejecutar este programa con privilegios de administrador. En vez de doble clic típico pulsa con el botón derecho del ratón y elige la opción de ejecutar como Administrador.

Definición de los servicios

La imagen que tienes a la izquierda representa la forma más compleja de la configuración que pretendemos desarrollar en esta instalación. Empezaremos por su forma más sencilla. Para ello crearemos el directorio C:\servidorFTP que será el directorio raíz (o root) de nuestro servidor FTP. Igual que ocurría en el caso de Apache– podría ser un directorio cualquiera de los existentes en el ordenador en el que tengamos instalado el software de servidor.

Supongamos que nuestra intención ofrecer tres servicios que requieren crear los subdirectorios:C:\servidorFTP\Alumnos, C:\servidorFTP\Documentacion y C:\servidorFTP\Gestion.

Por el momento nos resultará suficiente con estos tres directorios. Más adelante, para una configuración más avanzada, deberíamos crear toda la estructura que puedes ver en la imagen con la excepción de juan (sombreado en rojo) que se generaría de forma automática.

Crearemos tres usuarios básicos. A uno de ellos le llamaremos super (usuario que administra el sistema) y permitiremos que pueda acceder sin restricción alguna a todo el contenido del directorioC:\ServidorFTP y de todos sus subdirectorios.

Un segundo usuario llamado webmaster habrá de tener iguales privilegios en el Document Root del servidor Apache (C:/ServidoresLocales/Apache/htdocs).

El tercer usuario, secre, podrá acceder –también sin restricciones– a los directoriosC:\ServidorFTP\Documentación y C:\ServidorFTP\Gestion.

Page 24: Manual PHP

Cada usuario ha de disponer de un directorio raíz (su root) que ha de contener todos los directorios a los que va a tener acceso aunque el hecho de que un directorio sea el root de un usuario no implica que pueda acceder a todos sus contenidos ya que para hacerlo es necesario que tenga -además- permisos de acceso. Tal es el caso del usuario secre cuyo root ha de ser C:\ServidorFTP\ pese a que no va a tener privilegios de acceso al directorio Alumnos.

Privilegios de los usuarios

Esta será la manera en la que asignaremos contraseñas y privilegios a cada uno de los usuarios.

� – Usuario super con contraseña superi. Sus privilegios en los diferentes directorios serán:

� – Todos los privilegios en C:\servidorFTP. Al ser el usuario super parece razonable concederte el mayor grado de libertad. � – Usuario webmaster con contraseña superi. Tendrá los privilegios siguientes:

� – Todos los privilegios en C:/ServidoresLocales/Apache/htdocs. Las razones son idénticas al caso anterior. � – Usuario secre con contraseña superi. Va a tener los siguientes privilegios:

� – List en c:\ServidorFTP. Al concederle este privilegio podría visualizar (al acceder a su root mediante un cliente FTP) la lista de directorios y documentos que contiene. Si no incluyéramos esta opción el cliente FTP nos daría un mensaje de error al acceder al root (no tendría permiso alguno) y obligaría al usuario secre a establecer como root alguno de los directorios para los que tiene permiso de acceso.

� – Todos los privilegios en Documentación. Se entiende que este usuario es quien realiza la gestión completa de este directorio. � – Todos los privilegios en Gestión. Las razones son idénticas a las del caso anterior.

En este enlace tienes el detalle y la justificación de los diferentes privilegios que hemos establecido para cada uno de los restantes usuarios y grupos de usuarios que conforman la opción más compleja de nuestra propuesta.

Configuración de FileZilla Server

Server Interface

Al activar el Server Interface por primera vez aparece una ventana como la que tienes aquí debajo. Si marcas la casilla de verificación y pulsas OK ya no aparecerá en posteriores arranques.

Aparecerá una nueva ventana como la que puedes ver en esta otra imagen.

Page 25: Manual PHP

El primero de los iconos permite arrancar y detener el servidor desde esta consola. El segundo (en forma de candado) permite bloquear y desbloquear el servidor sin necesidad de detenerlo. Desde el tercero podremos hacer modificaciones generales en la configuración. Optaremos por dejarlas con las opciones por defecto. Desde el cuarto, uno de los más interesantes para nuestros propósitos, podremos crear cuentas de usuarios así como realizar su configuración. El quinto de los iconos –muy similar al anterior– permite acceder a la configuración de grupos de usuarios.

Creación de cuentas de usuario

Empezaremos creando las tres cuentas de los usuarios básicos: super, webmaster y secre utilizando las contraseñas que hemos definido anteriormente.

La forma de hacerlo es la que ves en la imagen inferior. Se pulsa en el icono user de la ventana Server interface (el cuarto de los iconos de la imagen que tienes aquí arriba) y aparece la ventana que ves en la parte inferior. La secuencia de creación de un usuario es la siguiente:

� – Selecciona general (1).

� – Pulsa en el botón Add de la ventana de usuarios (2). Se abrirá una nueva ventana.

� – Se escribe -en la nueva ventana- el nombre de usuario(3).

� – Se deja la opción None (por defecto) en el menú de opciones User should.. (4) y se pulsa el botón OK (5).

� –Al pulsar ok se cierra y se la ventana Add user account.

� –Se marca las opción Enable account (6).

� –Se marca la casilla de verificación Password (7) y se escribe la contraseña de usuario (8).

Page 26: Manual PHP

Repetiremos el proceso hasta crear las cuentas de los dos usuarios indicados al comienzo de este párrafo y acabaremos pulsando el botón OK que está situado en la parte inferior izquierda de la ventana de usuarios.

Elección de los roots los directorios accesibles y privilegios de cada usuario

A definir los servicios hemos decidido que al usuario webmaster se le va a permitir únicamente el acceso aC:/ServidoresLocales/Apache/htdocs (para la gestión del servidor Apache) por tanto su root ha de ser el directorioC:/ServidoresLocales/Apache/htdocs.

Para el caso de super (el usuario que controla en su integridad el servidorFTP) hemos optado por c:\servidorFTP como root y cuando se trata del usuario secre hemos dicho que vamos a permitírle el acceso a c:\ServidorFTP\Documentacion y a c:\ServidorFTP\Gestiony que por ello hemos de establecer como root el directorio c:\ServidorFTP (nivel superior a ellos y que, por tanto, los contiene a ambos).

El caso del usuario secre no es tan particular como pueda parecer ya que es bastante frecuente el hecho de que un usuario no deba acceder a los contenidos de todos los subdirectorios de su root.

Cada uno de los directorios accesibles para un usuario (Shared folders) puede gozar de privilegios distintos. Al seleccionar un directorio se activan las casillas de verificación que ves en la imagen y desde ellas se pueden conceder privilegios a dos niveles: ficheros ydirectorios. A nivel de ficheros cuenta con las opciones Read (descargar), Write (escribir, subir), Delete (borrar) y Append (añadir a un fichero preexistente cuando la transferencia ha sido interrumpida). Cada una de ellas puede configurarse como opción permitida ó no permitida.

Cuando se trata de directorios las opciones (también puede configurarse cada una de ellas como permitida/no permitida) son: Create(Crear), Delete (borrar), List (ver una lista de los contenidos) y +SubDirs (Cuando está activado asigna automáticamente a todos los subdirectorios que contenga los mismos privilegios que al directorio actual).

El proceso de establecimiento de la accesibilidad de un directorio (y la concesión de privilegios) es la que se detalla en la imagen. Se accede desde el icono users de la ventana Server Interface siguiendo la siguiente secuencia:

� – Se elige Shared Folder en la ventana de la izquierda (1).

� – Se elige un usuario User en la ventana de la derecha (2).

� – Se pulsa en el botón Add en la parte central de la ventana (debajo de Shared Folders) (3) con lo que se abre automáticamente la ventana Buscar carpeta.

� – Se elige un directorio (4) y se pulsa sobre el botón Aceptar (5).

Page 27: Manual PHP

� – Se establecen los privilegios -marcando o desmarcando las casillas de verificación correspondientes- para el directorio elegido (6).

� – Se pulsa OK para guardar los cambios de configuración (7).

El proceso puede repetirse cuantas veces sea necesario.

Directorios accesibles por los usuarios super y webmaster

Esta es la configuración del usuario super.

Page 28: Manual PHP

Para el usuario webmaster procederíamos de forma idéntica. La única diferencia sería el directorio (C:/ServidoresLocales/Apache/htdocs) sobre el que estableceríamos los mismos privilegios que se ven en la imagen para el usuario super.

Directorios accesibles por del usuario secre

La razón de estas asignaciones es posibilitar la elección de uno de los dos directorios –Documentacion y Gestion– con todos los privilegios. Como quiera que existe otro directorio del mismo nivel (Alumnos) al permitirle únicamente listar el que los contiene este último le resultaría inaccesible.

Page 29: Manual PHP

Observarás que el directorio raíz está marcado con la letra H. Para cambiarlo bastará con seleccionar otro cualquiera y pulsar sobre el botón que ves en esta imagen.

Acceso de usuarios

La dirección localhost apunta siempre hacia el directorio root de usuario. Eso quiere decir que si –utilizando el FileZilla Client– escribimos esa dirección y nos identificamos como secre (indicando la contraseña de usuario) veríamos algo como esto:

que –como puedes observar– no es otra cosa que la visualización de los contenidos del directorio C:\ServidorFTP (el root de este usuario.

Si nuestro equipo estuviera conectado a una red de área local podríamos acceder al servidor FTP escribiendo en vez de localhost la dirección IP del equipo en el que tuvierámos instalado el servidor.

¡Cuidado! Es posible que el firewall de Windows nos bloquee el acceso al servidor FTP sin darnos ningún mensaje de advertencia. Lo más conveniente para evitar problemas de esta índole sería abrir Paneles de Control -> Firewall de Windows -> Excepciones y, una vez allí, pulsar el botón Agregar Programas y añadir Filezilla Server.exe

Utilización de alias

Este servidor también permite ser configurado para la utilización de Alias. Pulsando con el botón derecho del ratón sobre el nombre de uno de los directorios aparece un menú como este:

al elegir la opción Edit Aliases se abre una ventana como la que ves aquí debajo. Si introducimos en ella la ruta absoluta completareemplazando el nombre del último directorio por una palabra distinta (en el ejemplo hemos incluido web) estaremos creando un alias. Podremos crear tanto como deseemos, es cuestión de incluirlos (completos) uno a continuación de otro separados por el carácter |.

Page 30: Manual PHP

Una vez creados los alias podremos utilizarlos para acceder a los espacios (sustituyendo el nombre del directorio por el alias). Aquí tienes imágenes del ejemplo. Observarás que localhost/ServidoresLocales/Apache, localhost/servidor y localhost/indio nos conducen al mismo sitio.

Una configuración más compleja

Creación de grupos de usuarios

La configuración de grupos de usuarios es una opción que ofrece bastante interés. Mediante esta opción se pueden establecer privilegios comunes a una serie de usuarios. Ello facilita la configuración cuando se trata de grupos numerosos que van a compartir directorios y privilegios. Sigue un proceso muy similar al que hemos descrito para el caso de usuarios. Las diferencias más sustanciales son:

� – Se accede a través del icono groups (el quinto de la ventana Server Interface).

� – No requiere insertar contraseñas. Estas serán privilegio exclusivo de los usuarios del grupo.

� – Requiere activar la casilla de verificación Enable access for users inside group.

� – En nuestro ejemplo, crearemos cuatro grupos: ingles (grupo de los profesores de Inglés), infor (profesores de Informática), ciclo1(alumnos de primer ciclo) y ciclo2 alumnos de segundo ciclo.

Page 31: Manual PHP

Directorios accesibles y privilegios del grupo

La configuración de los directorios accesibles y de los privilegios en cada uno de ellos es idéntica a la descrita para el caso de usuarios no pertenecientes a un grupo. Al avanzar en esta configuración ya sería necesario tener disponible la estructura de directorios y subdirectorios que se refleja en la imagen que tienes un poco más arriba.

La única diferencia estriba en que añade una nueva e interesante posibilidad a través de la opción Autocreate.

Supongamos que pretendemos que cada uno de los alumnos de nuestro supuesto disponga de un subdirectorio propio para podersubir y gestionar sus propios trabajos de cada materia. Aparte de la ventaja de tener separadas sus actividades con la posibilidad de borrar,añadir, modificar, etcétera dentro de su propio espacio y a la vez impedir que pueda efectuar esos procesos en materiales ajenos.

Eso requeriría ir creando esos directorios para cada uno de los usuarios y esa podría ser una tarea lenta y pesada. Mediante la opciónAutocreate se puede configurar un directorio de forma que durante el primer acceso del usuario se cree de forma automática un subdirectorio con el mismo nombre que el del usuario.

El uso de esta opción requiere:

� – Incluir en Shared folders la ruta del directorio base (en el que pretenden crearse los subdirectorios de cada uno de los usuarios pertenecientes al grupo) acabada con /:u

Page 32: Manual PHP

Asignación de usuarios a un grupo

Una vez creado un grupo (o grupos) es necesario asignar los usuarios a ese grupo. El proceso es muy similar al de creación de usuarios no adscritos a ningún grupo. La única diferencia está en el punto (4) del proceso, ya que ahora hemos de elegir el grupo al que va a pertenecer el usuario (antes elegíamos none).

En el ejemplo hemos incluido dos usuarios por cada uno de los grupos:

� – profe_ingles1 y profe_ingles2 en el grupo ingles.

� – profe_infor1 y profe_infor2 en el grupo infor.

� – ciclo1_alumno1 y ciclo1_alumno2 en el grupo ciclo1.

� – ciclo2_alumno1 y ciclo2_alumno2 en el grupo ciclo2.

En todos los casos hemos incluido una contraseña idéntica al nombre de usuario.

Los miembros de un grupo recogen automáticamente todos los privilegios del Shared Folder del grupo al que pertenecen. No obstante, es posible añadir nuevos directorios y privilegios (añadidos a los específicos del grupo al que pertenecen) incluyéndolos en elShared Folder del usuario.

En este enlace tienes el detalle y la justificación de los diferentes privilegios que hemos establecido para cada uno de los grupos de usuarios.

¡Cuidado! En este fichero, servidorftp.rar, tienes la estructura de directorios aquí descrita. Bastará con que la descomprimas en el directorio raíz de tu equipo para que puedas utilizarla con los ejemplos aquí descritos.

Fichero de configuración de Filezilla Server

Dado que, por el número de usuarios y por la complejidad de la estructura, la labor de configuración y creación de los diferentes usuarios aquí propuestos puede resultarte lenta y tediosa hemos incluido un fichero que puedes obtener http://www.rinconastur.com/php/filezilla.zip

Si descomprimes su contenido -FileZilla Server.xml- en el directorio de instalación de Filezilla Server (por defecto sería un directorio con ese mismo nombre dentro de Archivos de programa) sobrescribiendo el existente ya dispondrás de todos los usuarios y grupos aquí descritos junto con sus configuraciones respectivas.

Page 33: Manual PHP

¡Cuidado! En el enlace que figura en el párrafo anterior y aquí tienes la configuración de los diferentes usuarios tal como se describen en esta página y en sus anexos.

Instalación de un servidor de correo

Instalación de Mercury Mail

El software de servidor de correo Mercury Mail está disponible en http://www.pmail.com donde puedes elegir la descarga de Mercury mail transport system for win32 and NetWare systems v.4.73 o bien directamente desde aquí.

El proceso de instalación es el que describen las imágenes siguientes:

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Los aspectos más significativos de este proceso son los siguientes:

� – Elegiremos como dirección local la IP 127.0.0.1 pensando exclusivamente en servidor para pruebas. Si quieras utilizarlo como servidor de correo en una red local habrás de sustituir la IP 127.0.0.1 por la correspondiente al ordenador en el que está instalado el servidor

� – Para poder trabajar con cuentas externas a nuestra instalación local hemos de tener en cuenta que en la ventana dónde dice:Address of host via which to send mail tenemos que incluir la dirección del servidor SMTP a través del que se enviarían los mensajes a direcciones de correo externas.

� – En las pruebas hemos utilizado varias (los SMTP de nuestras cuentas de correo) aunque, como ves en la imagen, la prueba definitiva la hemos hecho utilizando la dirección del servidor de usuarios de Educastur (smtp.educastur.princast.es), ya que su proceso de configuración tiene la forma más compleja dado que ese servidor requiere autentificación del usuario.

Configuración de PHP

Para que nuestro servidor de correo pueda ser utilizado mediante funciones de PHP es necesario que el fichero de configuración de PHP, (php.ini) contenga los cambios ya propuestos al hablar de la configuración de PHP. Allí habíamos modificado concretamente las dos directivas: SMTP = 127.0.0.1 y [email protected] . Como es lógico, en el caso de optar por la instalación de una red de área local la IP asignada a SMTP habría ser la del equipo en el que estuviera instalado el servidor.

Arrancar y parar el servidor

Para arrancar el servidor hay que ejecutar el programa Mercury Loader. Lo encontrarás en el directorio en que hayas instalado el servidor de correo (C:/ServidoresLocales/Mercury). Sabremos que está en marcha porque aparecerá una ventana nueva. Si la minimizamos aparecerá en la barra de tareas un icono como este.

Este icono solo aparece al minimizar la ventana. Si la cerramos se parará el servidor y desaparecerá el icono. Aparte de la opción anterior, desde el menú del servidor tienes acceso a esta opción que ve en la imagen.

Page 34: Manual PHP

Cuando está funcionando el servidor –para pararlo– aparece un texto como este. Si estuviera parado, se podría activar desde la misma opción. Ahora aparecería con el texto: Leave offline mode

Configuración del servidor de correo

Añadir usuarios y crear un dominio local

Esta imagen describe paso a paso el proceso de creación de usuarios y configuración de un dominio local. Crearemos dos dominios locales llamados localhost y mispruebas.as y añadiremos los usuarios juan, perico y andres a quienes pondremos contraseñas idénticas a los nombres respectivos.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Un script de prueba

Este es el código fuente de un script que nos permitirá comprobar si hemos configurado correctamente el servidor de correo. El contenido que aparezca en la página nos dirá lo que ha ocurrido. ¡No te preocupes si aún no entiendes el código! Ya hablaremos de él más adelante.

<?php if( mail("[email protected]", "Una prueba definitiva","Bienvenid@ a PHP", "From: Administrador de mispruebas.as <[email protected]> Reply-To: [email protected] Cc: [email protected] Bcc: [email protected] X-Mailer:PHP/" . phpversion())){ print "Mensajes enviados con exito"; }else{ print "Se ha producido un error"; } ?>

Configuración del cliente de correo

La configuración del cliente de correo –Outlook Express o similar– no plantea problemas. El proceso es muy similar a la configuración de cualquier otra cuenta de correo. Los únicos detalles a tener en cuenta son los relativos a la configuración de los servidores SMTP y POP3. En ambos casos se escribe la IP con la que hemos configurado el servidor, es decir: 127.0.0.1 o la del equipo que soporte el servidor en el caso de una red local. Como nombre de cuenta pondremos el mismo con el que las hemos creado (juan, perico, andres) sin añadir la @ ni el nombre del dominio.

Pruebas del servidor

Una vez configuradas las cuentas en el cliente de correo bastará con enviar mensajes a las direcciones de usuarios locales (con el servidor Mercury activo) y comprobar que son recibidos en las cuentas destinatarias.

Page 35: Manual PHP

La segunda de las pruebas –el funcionamiento a través de PHP– podremos hacerla arrancando ambos servidores (Apache y Mercury) pulsando en el enlace que tienes un poco más arriba y comprobando después, a través del cliente de correo, que se ha recibido el mensaje en las cuentas de los tres usuarios de pruebas.

Leer mensajes de cuentas externas

Puede resultarte interesante configurar el servidor de correo de forma que puedan recibirse en una cuenta local los mensajes enviados a una -o varias- cuenta externas. Se trata de el servidor local compruebe y descargue los eventuales mensajes que pudieran haberse recibido en una cuenta externa. En esta imagen tienes, a modo de ejemplo, la descripción de la configuración de este servidor para que el usuario juan pueda leer desde su cuenta local los mensajes recibidos en la de educastur.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Enviar mensajes a cuentas externas

Mercury permite enviar mensajes (a través del servidor configurado como Address of host via which to send mail ) a direcciones de correos correspondientes a dominios externos. La configuración de esa opción requiere el proceso que puedes ver aquí debajo. Allí tiene la forma de configurar el servidor para que permita el envío de mensajes a direcciones no locales a través del servidor smtp externo que hemos configurado al efectuar la instalación.

Al enviar mensajes desde una cuenta local hacia una cuenta externa la dirección de respuesta por defecto sería la local. Eso, obviamente, plantearía problemas al destinatario ya que sus respuestas no encontrarían ese dominio en la red. Bastaría con modificar la configuración del cliente de correo incluyendo como dirección de respuesta la de una cuenta externa.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso ¡Cuidado! La gestión de mensajes externos puede plantear problemas derivados de los filtros antispam de los servidores de correo de la red que pueden bloquear el envio o recepción de los mensajes enviados utilizando este servidor.

Desinstalación del servidor de correo

Durante la instalación de Mercury no se escribe nada en el registro de Windows. Por ello, el proceso de desinstalación no existe. Basta con borrar del directorio de instalación y habremos desinstalado el servidor.

Page 36: Manual PHP

Cifrado con clave simétrica

Comunicación segura

El objetivo de la comunicación segura es que la confidencialidad de información transmitida evitando también el riesgo de que pueda ser modificada o manipulada durante el proceso de intercambio. Por medio del protocolo SSL –Secure Socket Layer– (una de las opciones más populares) se puede ocultar a ojos de teceros la información que fluye, de forma bidireccional, entre el servidor y el cliente. Para lograr tales propósitos es necesario recurrir a alguno de procedimientos criptográficos que describiremos a continuación.

Cifrado simétrico

Supongamos que A y B convienen enviarse mensajes cifrados y acuerdan reemplazar las vocales por los números 1 al 5. De esa forma cuando uno de ellos pretenda escribir la palabra poetisa utilizará la grafía: p42t3s1. El receptor del mensaje podrá reemplazar los números por su vocal equivalente y recomponer el mensaje. Este sería un ejemplo muy simple de cifrado simétrico o con clave privada.

El grado de inseguridad de un sistema de cifrado simétrico está condicionado por dos factores. Uno de ellos, común a todos los sistemas de cifrado, es la mayor o menor resistencia que pueda presentar a los intentos de descubrir la clave a base de ensayo y error(«fuerza bruta»). Normalmente, un aumento del tamaño de la clave representa un incremento la seguridad.

El otro problema, el más grave, es la falta de certeza sobre la identidad de remitente. No hay nada que nos de garantías sobre quienha cifrado el documento. De todas formas veremos un poco más adelante como subsanar estos inconvenientes ya que este es el método de cifrado que se utiliza habitualmente cuando se navega por páginas seguras.

Existen varios algoritmos de cifrado simétrico. Uno de los más populares es el conocido como DES adoptado como un estándar, para comunicaciones no clasificadas, por el Gobierno de los EE.UU. en 1976. Su grado de eficiencia quedó muy cuestionado cuando, en 1998,Electronic Frontier Foundation logró fabricar una máquina capaz de descifrarlo en tres días. Esta circunstancia forzó la aparición de versiones mejoradas tales como el DES múltiple y otras que han logrado paliar en gran medida sus deficiencias iniciales y hacer de DES múltiple un algoritmo de uso bastante frecuente.

El algoritmo IDEA (International Data Encryption Algorithm) es otro de los que aperecen, en este caso en 1992, como opciones alternativas al uso de DES múltiple. No fué el único. En octubre de 2000 el National Institute for Standards and Technology (NIST) adoptó del algoritmo RIJNDAEL como nuevo Estándar Avanzado de Cifrado (AES) para su empleo en aplicaciones criptográficas no militares.

PHP5 dispone de funciones de cifrado para una gran variedad de algoritmos. Aquí tienes una lista de los algoritmos de cifrado que soporta actualmente.

Page 37: Manual PHP

Además de los distintos algoritmos de cifrado hay otro elemento diferenciador. Se trata de lo conocido como modo de cifrado. Los algoritmos de cifrado suelen trabajar con bloques de longitud fija. La cadena objeto del cifrado es descompuesta en bloques de igual tamaño y tratada de diferentes modos. En los llamados ECB (Electronic CodeBook) cada bloque es cifrado de forma independiente. En otros modos, por ejemplo el CBC (Cipher Block Chaining), a cada bloque de texto plano se le aplica una operación que requiere el uso del texto de bloques anteriores. En estos casos, para hacer cada mensaje único ha de utilizarse un vector de inicialización. Aquí tienes la lista de los modos de cifrado soportados por tu versión de PHP.

Funciones PHP de cifrado simétrico

Configuración de PHP

PHP dispone de funciones que permiten efectuar algunos tipos de cifrado. Antes de nada y como siempre en estos casos es conveniente comprobar en el fichero info.php si están activadas estas opciones.

En el caso de la versión PHP 5.3.6 para Windows esta opción viene activada por defecto. Igual ocurre en el caso de PHP 5.3 en Ubuntu 10.10. Por el contrario, en versiones anteriores de PHP es necesario, además de seguir el proceso de configuración que se indica en el enlace, descomentar de su fichero php.ini, quitando el punto y coma (;) que por defecto lleva delante la extension=php_mcrypt.dll.

Si en alguna versión de Ubuntu no viniera configurado por defecto podría instalarse ejecutando desde la consola el siguiente comando:

sudo apt-get install php5-mcrypt

Proceso de cifrado y descifrado

Los procesos de cifrado y descifrado mediante PHP son muy similares y requieren, en ambos casos, la siguiente secuencia de funciones:

$identificador=mcrypt_module_open(algoritmo, ubicacion, modo, directorio)

Abre un nuevo módulo de cifrado identificado por el valor la variable $identificador. Requiere estos parámetros: algoritmo es una cadena que ha de contener, entre comillas, el nombre del algoritmo que pretendemos utilizar. Será uno de los nombres de la lista antes mencionada y que puedes ver nuevamente desde este enlace. El parámetro ubicacion permite especificar el directorio con la eventual ubicación del algoritmo de cifrado en el caso de que se usara uno distinto a los incluidos en la distribución de PHP. Lo habitual será que pongamos como valor una cadena vacía de la forma "". El parámetro modo será una cadena en la que se especifique el modo de cifrado que pretendamos utilizar y habrá de contener uno de los valores (escrito entre comillas) que tenemos en este enlace. Por último, el parámetrodirectorio será habitualmente una cadena vacía "" ya que está definida para establecer el modo del directorio eventualmente establecido en el parámetro ubicacion.

Iniciado el módulo de cifrado y según el modo de cifrado establecido puede resultar necesario crear un vector de inicialización cuya longitud se determina por medio de la función:

$longitud=mcrypt_enc_get_iv_size($identificador)

Page 38: Manual PHP

en la que $identificador es el identificador de recurso establecido por la función anterior y $longitud la longitud de del mencionado vector de inicialización.

Conocida la dimensión $longitud del vector de inicialización (puede ser cero en el caso de que no sea requerido su uso) debemos crearlo invocando la función:

$vector=mcrypt_create_iv($longitud, constante)

donde $longitud es resultado de la función anterior y constante es la constante MCRYPT_RAND que debemos incluir sin comillas. El resultado, $vector, será el vector de inicialización.

El paso siguiente será iniciar todos los buffers necesarios para el posterior proceso de cifrado o de descifrado. De esa labor se encarga la función:

mcrypt_generic_init($identificador, clave, $vector)

en la que $identificador es el identificador del recurso, clave es una cadena que se usará como CLAVE DE CIFRADO y $identificador el identificador del recurso y $vector es el vector de inicialización.

El último paso ya será el cifrado o descifrado propiamente dichos. Si se trata de cifrar utilizaremos:

$cifrado=mcrypt_generic($identificador, texto)

donde texto es una cadena (o variable) que contiene el texto que pretendemos cifrar y donde $identificador sigue siendo el identificador del recurso. El resultado del cifrado es recogido en la variable $cifrado

Si se trata de descifrar la función anterior debe ser sustituida por

$descifrado=mdecrypt_generic($identificador, $cifrado)

siendo $cifrado la variable (ocadena) cifrada que pretendemos desencriptar y $descifrado el resultado de la desencriptación. Llegados a este punto, solo nos restaría liberar los buffer reservados para el cifrado y cerrar el recurso mediante las funciones:

mcrypt_generic_deinit($identificador) y mcrypt_generic_close($identificador)

De esta forma se consigue un método de cifrado y descifrado aplicable con cualquiera de los algoritmos y modos de cifrado disponibles.

Ejemplos de cifrado y descifrado simétrico

Un ejemplo de como puede cifrarse con clave simétrica podría ser este:

<?php # establecemos la clave y la cadena a encriptar $clave = "clave"; $texto ="Esta es una cadena de prueba"; /*creamos un identificador de encriptado en el que indicamos el tipo de cifrador (cast-128) y el modo de cifrado (ecb) */ $ident = mcrypt_module_open('cast-128', '', 'ecb', ''); /* dado que algunas funciones requieren de un vector de inicializacion acorde con sus especificaciones esta función determina el tamaño de ese vector atendiendo al tipo de identificador */ $long_iniciador=mcrypt_enc_get_iv_size($ident); /* crea el vector de inicialización con valores aleatorios y dándole la dimensión precalculada en la función anterior */ $inicializador = mcrypt_create_iv ($long_iniciador, MCRYPT_RAND); /* hacemos algunas comprobaciones innecesarias para ejecutar el script. Simplemente son descriptores de algunas funciones complementarias */

Page 39: Manual PHP

/* comprobamos el tamaño maximo (en bytes) que puede tener la clave para este algoritmo de cifra*/ print "La clave no puede sobrepasar los "; print mcrypt_enc_get_key_size ($ident)." bytes<br>"; /* escribimos el tamaño del bloque del algoritmo que estamos usando*/ print "El tamaño del bloque de cifrado es "; print mcrypt_enc_get_block_size($ident)." bytes<br>"; print "El modo de cifrado es "; print mcrypt_enc_get_modes_name($ident)."<br>"; print "El algoritmo de cifrado es "; print mcrypt_enc_get_algorithms_name($ident)."<br>"; print "El tamaño del vector de inicialización es "; print mcrypt_enc_get_iv_size ($ident)."<br>"; /* Contimuamos la secuencia de encriptado incializando todos los buffer necesarios para llevar a cabo las labores de encriptado */ mcrypt_generic_init($ident, $clave, $inicializador); /* realiza el encriptado proopiamente dicho */ $texto_encriptado = mcrypt_generic($ident, $texto); /* libera los buffer pero no cierra el modulo */ mcrypt_generic_deinit($ident); /* esta instruccion es necesaria para cerrar el modulo de encriptado*/ mcrypt_module_close($ident); # imprimimos el resultado de la encriptación # en este caso añadimos una codificación de ese resultado en base 64 print "La cadena encriptada es: ".base64_encode ($texto_encriptado); /* guardamos la cadena encriptada en un fichero con nombre encriptado */ file_put_contents('encriptado',$texto_encriptado); print "<br> está codificada en base 64"; ?>

Ejecutar ejemplo de encriptado

El proceso inverso, la desencriptación de una cadena codificada puede hacerse de la forma que se indica en el siguiente ejemplo.

<?php /* hemos de usar la misma clave con la que ha sido encriptado */ $clave = "clave"; /* leemos el fichero encriptado creado por el script anterior */ $texto_encriptado =file_get_contents('encriptado'); /*creamos un identificador de encriptado que ha de ser el mismo con el que hemos realizado la encriptación */ $ident = mcrypt_module_open('cast-128', '', 'ecb', ''); /* dado que algunas funciones requieren de un vector de inicializacion acorde con sus especificaciones esta función determina el tamaño de ese vector atendiendo al tipo de identificador anterior*/ $long_iniciador=mcrypt_enc_get_iv_size($ident); /* crea el vector de inicialización con valores aleatorios y dándole la dimensión precalculada en la función anterior */ $inicializador = mcrypt_create_iv ($long_iniciador, MCRYPT_RAND); /* incializa todos los buffer necesarios para llevar a cabo las labores de encriptado */ mcrypt_generic_init($ident, $clave, $inicializador); /* realiza el desencriptado proopiamente dicho. Realmente es la unica diferencia básica entre este script y el ejemplo anterior */ $desencriptado = mdecrypt_generic($ident, $texto_encriptado); /* libera los buffer pero no cierra el modulo */ mcrypt_generic_deinit($ident);

Page 40: Manual PHP

/* esta instruccion es necesaria para cerrar el modulo de encriptado*/ mcrypt_module_close($ident); # imprimimos el resultado de la encriptación # en este caso añadimos una codificación de ese resultado en base 64 print $desencriptado; ?>

Ejecutar ejemplo de descifrado

¡Cuidado! Si al intentar ejecutar el ejemplo anterior sobre Ubuntu observas sólo una página en blanco comprueba que el directorio en el que estés ejecutando el script tenga permisos de lectura/escritura. De no disponer de ellos no se guardaría el fichero con los datos encriptados y por es misma razón tampoco se visualizaría.

El protocolo de Diffie-Hellman

Una de las debilidades del cifrado con clave simétrica reside en la necesidad de que ambas partes intercambien su clave de cifrado de forma confidencial. Esa confidencialidad puede conseguirse, incluso en comunicaciones de forma no segura, mediante una técnica conocida como protocolo de Diffie-Hellman.

Números y sus raíces primitivas

Este protocolo requiere la utilización de dos números que llamaremos p y g. El número p habrá de ser un número primo, por lo general muy grande, mientras que g ha de ser una raíz primitiva de p. Sin abundar en aspectos demasiado técnicos podemos decir que g es raiz primitiva de p si el conjunto de los restos de dividir entre p cada de las sucesivas potencias de g desde 1 hasta p-1 (g1, g1, g2, g3, ... gp-1) contiene a todos los naturales {1,2,3,4..., p-1}. Eso significaría que para cualquier entero b menor que p y una raíz primitiva g del número primo p, se puede encontrar un único exponente ital que b = gi (mod p) donde 0 ≤ i ≤ (p-1). El exponente i se conoce como el logaritmo discreto o índice de b para la base g, mod p. Este valor se representa como indg,p(b).

En la tabla que tienes a continuación puedes ver el proceso de comprobación de raices primitivas desarrollado para dos supuestos. Observa que en el caso de g=13 los restos de las divisiones se repiten (lo cual significa que hay valores que no aparecen) y por tanto 13 no es raíz primitiva de 23. Por el contrario, al comprobar el comportamiento de g=7 puedes ver los restos son, de forma no ordenada, los números naturales comprendidos entre 1 y 22. Por tanto g=7 si es raíz primitiva de 23.

Comprobación de las raíces primitivas

z p=23, g=7 (7 ES raíz primitiva de 23) p=23, g=13 (13 NO ES raíz primitiva de 23)

gz Cociente entero gz/p Resto gz/p gz Cociente entero gz/p Resto gz/p

1 7 0 7 13 0 13

2 49 46 3 169 161 8

3 343 322 21 2197 2185 12

4 2401 2392 9 28561 28543 18

5 16807 16790 17 371293 371289 4

6 117649 117645 4 4826809 4826803 6

7 823543 823538 5 62748517 62748508 9

8 5764801 5764789 12 815730721 815730719 2

9 40353607 40353592 15 10604499373 10604499370 3

10 282475249 282475236 13 137858491849 137858491833 16

11 1977326743 1977326721 22 1792160394037 1792160394036 1

12 13841287201 13841287185 16 23298085122481 23298085122468 13

13 96889010407 96889010387 20 302875106592253 302875106592245 8

14 678223072849 678223072847 2 3937376385699289 3937376385699277 12

15 4747561509943 4747561509929 14 51185893014090757 51185893014090739 18

16 33232930569601 33232930569595 6 665416609183179841 665416609183179837 4

17 232630513987207 232630513987188 19 8650415919381337933 8650415919381337927 6

Page 41: Manual PHP

18 1628413597910449 1628413597910431 18 112455406951957393129 112455406951957393120 9

19 11398895185373143 11398895185373132 11 1461920290375446110677 1461920290375446110675 2

20 79792266297612001 79792266297611993 8 19004963774880799438801 19004963774880799438798 3

21 558545864083284007 558545864083283997 10 247064529073450392704413 247064529073450392704397 16

22 3909821048582988049 3909821048582988048 1 3211838877954855105157369 3211838877954855105157368 1

Obtención e intercambio de claves

Intecambiados de forma pública dos números p y g tales que g es raíz primitiva de p, cada uno de los dos usuarios que pretenden encontrar una clave común y secreta genera sendos números XA=gx (mod p) y XB=gy (mod p) donde x e y son números cualesquiera (secretos, ya que no serán intercambiados), menores que p. Ambos usuarios intercambian sus números. «A» facilita a «B» su XA y «B» proporciona a «A» el valor XB. Cada usuario eleva a su clave secreta el valor público recibido del otro usuario y obtiene el resto de dividir el resultado entre el número p. Es decir, el usuario «A» efectúa la operación: KA=(XB)x (mod p) mientras que «B» calcula: Kb=(XA)y (mod p). Ambos resultados son iguales dado que se verifica que:

KA= (XB)x (mod p)= (gy)x (mod p)=(gyx)(mod p), y también que:

KB= (XA)y (mod p)= (gx)y (mod p)=(gxy)(mod p)

confirmádonse por tanto que ambos resultados han de ser iguales y que su valor será la clave simétrica buscada.

La práctica imposibilidad de que un tercero pueda obtener la clave radica en el hecho de que para calcularla es necesario conocer, al menos, uno de los números que hemos llamado secretos (x ó y) y aunque es cierto que pueden ser conocidos XA, XB, p y g (se transmiten o pueden transmitir de forma no segura) para romper la clave sería preciso buscar la solución a XA=gx (mod p) (logaritmo discreto de XA para la base g módulo p) tarea de muy alta dificultad (nada resulta imposible) cuando se trata de valores de p muy grandes. En esa dificultad radica precisamente la robustez de este protocolo desarrollado en 1976 por Whitfield Diffie y Martin Hellman.

Este es un ejemplo del procedimiento de cálculo de una clave de cifrado simétrico mediante el protocolo de Diffie-Hellman.

Obtención de una clave común y secreta para cifrado simétrico

Usuario «A» Usuario «B»

Acuerdan, de forma pública, un número y una de sus raíces primitivas clave y un generador. Establezcamos esos números como: p=23 y g=7

Elige un número natural secreto (puede hacerlo al azar) Elige un número natural secreto (puede hacerlo al azar)

9 3

Eleva g al numero elegido, lo divide entre p y determina el resto de esa división Eleva g al numero elegido, lo divide entre p y determina el resto de esa división

Resto de 79 entre 23 =15 Resto de 73 entre 23 =21

Envía el número anterior a «B» Envía el número anterior a «A»

Eleva el valor recibido (21) a su clave secreta y calcula el resto de dividir ese resultado entre p (23).

Eleva el valor recibido (15) a su clave secreta y calcula el resto de dividir ese resultado entre p (23)

Resto de 219 entre 23=17 Resto de 153 entre 23=17

Ambos obtienen el mismo resultado. Ese valor, solo conocido por ellos, será la clave de cifrado simétrico

La identidad del comunicante como problema de seguridad

Admitiendo la invulnerabilidad (que nunca lo es del todo) del protocolo de Diffie-Hellman y la robustez de los algoritmos de cifrado simétrico tendríamos una más que aceptable seguridad del carácter confidencial (e incluso la integridad) de la información intercambiada. Pero queda en el aire una pregunta muy importante. ¿Qué garantías nos ofrecen estas técnicas de que los comunicantes son los que dicen ser? ¿Quien nos garantiza que no han sido suplantadas sus identidades? ¿Podemos evitar que uno de los comunicantes pueda negar, aún habiéndolo hecho, haber enviado una determinada información?. Por el momento la respuesta es no. Serán necesarios otros recursos para solventar ese problema. Tenemos pendiente resolver la autentificación ( que cada parte de la comunicación pueda asegurarse de que la otra parte es realmente quien dice ser) y el no repudio (permite a cada uno de los comunicantes probar de forma fehaciente que el otro ha participado en la comunicación de forma que el remitente

Page 42: Manual PHP

del mensaje no pueda negar haberlo enviado o (caso de no repudio de destino), el destinatario del mensaje no puede negar haberlo recibido.

Para resolver esos problemas hemos de recurrir a procedimientos bastante similares a los de la vida cotidiana. Lo primero de todo sería firmar la información de la misma forma que se firma un cheque, una carta, un certificado o una solicitud. Esa acción no sería otra cosa que firmar digitalmente nuestros mensajes.

Ni en la comunicación cotidiana ni en la digital resulta de suficiente garantía la firma de un documento. Todos sabemos que puede ser más o menos hábilmente imitada. La firma manuscrita puede ser falsificada. Necesitamos algún tipo de garantía más.

Cuando en un centro de enseñanza se expide un certificado lo habitual es que vaya con la firma del secretario, el visto bueno del director y el sello del centro. Es decir, la firma del director es una garantía de la fidelidad de la del secretario.

Puede que no confiemos lo suficiente en la firma del director –Autoridad Certificadora– y que demandemos la legitimación notarial de la firma del secretario. La única diferencia sería el mayor rango de la Autoridad Certificadora. Desde luego siempre podemos seguir desconfiando. De estos asuntos trataremos en los temas siguientes.

Cifrado con clave asimétrica

Cifrado asimétrico

El protocolo de Diffie-Hellman que hemos visto en la página anterior es un claro ejemplo de lo que se conoce como cifrado asimétrico. A la hora de convenir la clave de cifrado cada uno de los intelocutores utilizaba dos claves. Una de carácter público (podía ser conocida sin riesgo) y otro de carácter privado dado que el usuario la mantenía en secreto. El primero de los números sería el conocido como clave pública y el segundo de ellos la clave privada. El cifrado asimétrico, también llamado cifrado de clave pública se fundamenta la utilización conjunta de esas dos claves.

En el esquema puedes ver las posibilidades que ofrece el uso de este algoritmo en sus dos opciones. Lo cifrado con la clave pública de un usuario puede ser descifrado con su clave privada. De igual forma, lo cifrado con la clave privada será descifrado con su clave pública.

Page 43: Manual PHP
Page 44: Manual PHP

A partir de esas premisas y tal como se ve en el esquema es factible establecer la estrategia para garantizar la confidencialidad y, en cierta medida (hablaremos de esto más adelante) la autenticidad del remitente.

El algoritmo RSA

El algoritmo de cifrado asimétrico más popular en este momento es el conocido como RSA (acrónimo de los apellidos de los criptólogos Ronald L. Rivest, Adi Shamir y Leonard Adleman que fueron quienes lo desarrollaron en 1977). La aplicación de este algoritmo requiere tres pasos:

Generación de claves

Es un proceso que sigue la siguiente secuencia:

� – Cada usuario elige aleatoriamente dos números primos distintos p y q, pero con longitudes en bits parecidas. La fortaleza de la clave está condiciona en gran medida por el tamaño de estos dos número que suelen ser muy grandes alcanzando actualmente el orden de 10200 y con previsión de aumento de forma simultánea a la capacidad de cálculo de los ordenadores.

� – Se calcula el valor n = p*q al que se conocerá como módulo.

� – Se calcula el valor Z=(p-1)*(q-1).

� – Se elige un entero positivo e conocido como exponente de la clave pública que ha de ser menor que n, que sea coprimo de él.

� – Se determina un valor d, al que se llama exponente de la clave privada, que cumpla a condición de que d*e (mod Z)=1.

Obtenidos esos valores, el par (n,e) será la clave pública y (n,d) la clave privada de carácter secreto. Los tamaños habituales de las claves son actualmente de 1024 ó 2048 bits de longitud.

En el cuadro que tienes a continuación intentamos ejemplificar, con valores minúsculos, los mecanismos de generación de claves utilizando este algoritmo.

Algoritmo asimétrico RSA

Número Ejemplo Proceso de cálculo

p 3 Primo muy grande. Aleatorio

q 11 Primo muy grande. Aleatorio

n=p*q 3*11=33 Se calcula el producto p*q

Z=(p-1)*(q-1) (3-1)*(11-1)=20 Producto Calculado

e 7 cumple la condición

Se calcula un número que sea: • - Primo • - Coprimo de Z. Es decir, MCD (e,Z)=1 • - Positivo • - <Z

d 3 cumple la condición Resto de (7*3)/20=1

Ha de cumplirse que • - d*e (mod Z)=1

Par (n,e) Par (33,7) Clave pública- Módulo y exponente

Par (n,d) Par (33,3) Clave privada- Módulo y exponente

Cifrado y descifrado

En este cuadro tienes un ejemplo de funcionamiento del cifrado/descifrado de un número. Hablamos de números porque cuando se utiliza este procedimiento las cadenas de caracteres se convierten en bloques numéricos (utilizando los códigos ASCII de los caracteres) agregando al resultado un método de camuflaje conocido como Paddings Schemes que consiste en añadir algunos bits al mensaje de forma que el proceso de cifrado incluya esos bits añadidos que minimizan riesgos de vulnerabilidad de propio proceso.

Page 45: Manual PHP

Cifrado con clave privada y descifrado con clave pública

(numero)d (mod n)

numero=5 53(mod 33)= Resto de (53)/33=26 cifrado=26

Para cifrar se eleva el número a cifrar al valor de la clave privada y se determina el resto de dividir el resultado entre n 53=125; cociente entero 125/33=3; resto de la división=26

ya que: 3*33+26=99+26=125

(cifrado)e (mod n)

cifrado=26 267(mod 33)= Resto (267)/33=5 numero=5

Para descifrar eleva el número cifrado al valor de la clave pública y se determina el resto de dividir el resultado entre n 26

7=8031810176; cociente entero 8031810176/33=243388187;

resto de la división=5

ya que: 243388187*33+5=8031810171+5=8031810176

Cifrado con clave pública y descifrado con clave privada

(numero)e (mod n)

numero=5 57(mod 33)= Resto de (57)/33=14 cifrado=14

Para cifrar se eleva el número a cifrar al valor de la clave pública y se determina el resto de dividir el resultado entre n 57=78125; cociente entero 78125/33=2367; resto de la división=14

ya que: 2367*33+26=78111+14=78125

(cifrado)d (mod n)

cifrado=14 143(mod 33)= Resto de (143)/33=5 numero=5

Para descifrar eleva el número cifrado al valor de la clave privada y se determina el resto de dividir el resultado entre n 14

3=2744; cociente entero 2744/33=83; resto de la división=5

ya que: 83*33+5=2739+5=2744

Obviamente los ejemplos anteriores no son otra cosa que muestras simplificadas de los procedimientos aritméticos de cifrado y descifrado.

Limitaciones del algoritmo RSA

El cifrado asimétrico presenta algunos problemas operativos tales como:

� – Para una misma longitud de clave y mensaje se necesita mayor tiempo de proceso.

� – Las claves deben ser de mayor tamaño que las simétricas

Tabla comparativa de tamaños de claves de similiar resistencia a ataques de fuerza bruta

Simétrica Asimétrica

64 bits 512 bits

80 bits 768 bits

112 bits 1792 bits

128 bits 2304 bits

Certificados digitales

Certificado digital

Cuando recibimos información cifrada con clave asimétrica pueden darse varias situaciones. Entre ellas estas:

Cifrado con nuestra clave pública

Page 46: Manual PHP

Solo nos garantiza la confidencialidad de la información dado que solo nosotros podremos descifrarlo pero nos oculta la verdadera identidad del remitente ya que cualquiera que haya tenido acceso a nuestra clave pública (cualquier usuario de la red podría haber tenido acceso a ella) pudo haberlo enviado.

Cifrado con clave privada

Tampoco nos garantiza la identidad del remitente. Sólo nos asegura que ha sido enviado por la misma persona que nos facilitó la clave pública. Eso no garantiza en ningún modo la identidad. Sólo prueba la concordancia entre la clave privada y la clave pública.

Es evidente que se requiere algo más y eso puede ser un certificado digital.

¿Qué es un certificado digital?

Es un documento digital mediante el cual un tercero confiable garantiza la vinculación entre la identidad de un sujeto o entidad y su clave pública. Podríamos considerarlo como algo muy similar al documento nacional de identidad español (en otros países seguramente existe un documento equivalente). El D.N.I. requiere que sea expedido por una entidad confiable (la Dirección General de la Policía) que garantiza que los datos que figuran en él corresponden a la persona física cuya fotografía, datos, firma y huella dactilar aparecen impresos.

Tipos de certificado digital

Dependiendo de la información que contiene cada uno y la finalidad con la que se emiten existen diferentes tipos de certificado digital. Para nuestro propósito resultan de particular interés:

Certificados personales

Acreditan la identidad del titular que ha de ser una persona física. Su finalidad es la firma electrónica de documentos con garantíade: la identidad del emisor, el no repudio de origen, la integridad y, si se requiere, la confidencialidad del contenido. También pueden ser usados para la autentificación de usuarios en sistemas que requieran un control de acceso sustituyendo los clásicos nombre de usuario y contraseña.

En España, las muestras más genuinas de este tipo de certificado son el el DNIe o el CERES - FNMT que emite de forma gratuita a petición de cualquier ciudadano español la Fábrica Nacional de Moneda y Timbre. En el primer caso –necesitamos un lector de tarjetas integrado en el propio teclado o accesible a través de una conexión USB externa– el certificado va incluido en el propio DNIE que tiene características de tarjeta criptográfica y en el segundo puede obtenerse mediante la descarga de un fichero a través de internet o adquiriendo en la FNMT una tarjeta criptográfica que lo contenga.

Certificado de servidor seguro

Son certificados destinados ser instalados en los servidores para asegurar su identidad frente a los usuarios que acceden.Garantizaría que el sitio web es el original, que no ha sido suplantado y que nadie ajeno al titular ha podido alterar la información publicada. Este tipo de certificados –no suelen ser gratuitos– son los emitidos por Autoridades Certificadoras (CA) tales como: Verisign, Thawte, RSA Security o GeoTrust por mencionar solo algunos de los más conocidos internacionalmente. Normalmente su precio varía dependiendo del plazo de validez y del prestigio de la entidad certificadora.

Certificado de autoridad certificadora

Una Autoridad Certificadora (CA) es una entidad en la que confían el emisor y el receptor de una comunicación. La confianza del emisor y del receptor en ella implica que ambos confían en los documentos firmados por la Autoridad Certificadora de su confianza. Los certificados de estas entidades llamados certificados raíz –las CA se certifican a si mismas– tienen por única finalidad permitir que sean verificables los certificado de terceros (servidores y/o clientes) expedidos por ella. Suelen tener carácter público son descargables como ocurre con el del DNIe.

Emisión de certificados

Técnicamente resulta sumamente fácil crear certificaciones de todo tipo y también convertirse en autoridades de certificación. El propio PHP dispone de funciones capaces de crear ese tipo de documentos y existen, además, aplicaciones gratuitas –OpenSSL, por citar la herramienta que nosotros vamos a utilizar– que realizan la misma función en cuestión de muy pocos segundos. El problema está en laconfianza.

Page 47: Manual PHP

Por poner un ejemplo. Cualquiera podría diseñar un bonito diploma académico y emitir títulos de doctor en Medicina en cualquier mercadillo callejero. Puede que incluso uno de esos títulos pudiera llegar a manos de un facultativo de acreditadísimo prestigio. El problema sería, desde el punto de vista de la certificación, que ningún hospital la aceptaría como válida. Estaría fracasando la confianza, por un NO reconocimiento de autoridad certificadora.

Pongamos un segundo ejemplo. Nadie en España ni en la Comunidad Europea pondría en cuestión nuestra identidad si mostranos nuestro DNI. La autoridad certificada de la Dirección General de la Policía sería reconocida en toda Europa pero ¿tendría la misma validez en China o en USA?. Seguramente no. Allí solo reconocerían el pasaporte como documento acreditativo de nuestra identidad. De igual forma, una autoridad certificadora (CA) podrá tener un mayor o menor reconocimiento (vinculado a las garantías de veracidad y seguridad que ofrece). De ese grado de reconocimiento, entre otras cosas, suele depender el precio.

Las CA deben prestar especial atención a garantizar que nadie más que ellas pueden realizar su firma. Esa es la razón por la que ha de convertirse en algo casi obsesivo la custodia e inaccesibilidad de su clave privada. Si un tercero pudiera hacerse con ella estaría en disposición de emitir certificaciones falsas con la misma calidad que si permitieran a un falsificador hacer uso de las instalaciones y materiales de la FNMT.

Proceso de firma de un certificado

Cuando un entidad certificadora recibe una petición de certificado lo procesa de la forma siguiente:

� – Elabora el certificado incluyendo la información relativa a solicitante y entidad certificadora.

� – Aplica un algoritmo de hash que no es otra cosa que una función matemática que procesa los datos contenidos en el documento y obtiene a partir de ellos una cadena corta de caracteres –huella digital– que tiene la peculiaridad de que su valor resultaría alterado por la más mínima modificación del documento original.

� – Cifra con su clave privada la huella digital y la incluye dentro del certificado del cliente.

Comprobación de un certificado

Para comprobar la validez de un certificado debemos seguir un proceso inverso al anterior.

� – Se separa la firma digital de la entidad certificadora del documento.

� – A partir de ahí, mediante la clave pública de la entidad certificadora, puede desencriptarse la huella digital contenida en la firma del certificado.

� – Paralelamente se aplica el algoritmo de hash al documento y se determina su huella digital.

� – Si su valor coincide con el extraido de la firma digital ya tendríamos la certeza de que el certificado no ha sufrido modificación alguna. Es un documento auténtico.

Page 48: Manual PHP

Por otra parte el descifrado con clave asimétrica de la huella incluida en el certificado nos garantiza que, además de no haber sido alterado, ha sido firmado realmente por la autoridad certificadora y no por otros. En la página siguiente veremos como configurar un servidor seguro y como establecer comunicaciones seguras utilizando este tipo de certificados.

Certificados auto firmados

Vamos a ver el procedimiento de creación de certificados digitales auto firmados. Para ello disponemos de una aplicación conocida como OpenSSL que nos va a permitir crearlos sin otra limitación que nuestro escaso prestigio como Autoridad Certificadora.

Configuración de OpenSSL

Tanto la versión de Apache que te he sugerido en páginas anteriores como la de nuestra instalación automática ya incluyen OpenSSL pero debemos configurar PHP para poder utilizarlo adecuadamente. Bastará con descomentar la línea 963 del fichero php.ini que tienes en el subdirectorio php53 y donde dice: ;extension=php_openssl.dll quitar el punto y coma, guardar los cambios y reiniciar el servidor Apache. Al acceder a http://localhost/info.php deberás visualizar algo como esto:

Si tu sistema operativo es Ubuntu deberás instalar primero y habilitar después el módulo SSL ejecutando desde la consola los siguientes comandos:

sudo apt-get install apache2 libapache-mod-ssl sudo a2enmod ssl

Si eres usuario de Windows habrás de hacer una modificación en las variables de entorno del sistema. Para ello debes acceder a: Inicio-> Configuración-> Paneles de control--> Sistema -> (configuración Avanzada*)-> Opciones avanzadas ->botón Variables de entorno

Page 49: Manual PHP

y comprobar si existe una variable del sistema (parte inferior de la ventana)con nombre OPENSSL_CONF y como valorC:/ServidoresLocales/Apache/conf/openssl.cnf.

En caso de que no existiera tendríamos que pulsar sobre la opción Nueva y rellenar una ventana como esta que ves en la imagen:

Una vez hayas pulsado Aceptar deberás reiniciar el equipo para que el cambio de configuración surta efectos.

¡Cuidado! El valor de la variable del sistema OPENSSL_CONF ha de ser la ruta absoluta hasta el fichero openssl.cnf que se incluye por defecto en directorio conf de Apache cuando se instala la versión que incluye openssl (en nuestro casohttpd-2.2.17-win32-x86-openssl-0.9.8o.msi). El fichero se identifica por su icono (no se visualiza su extensión que es .cnf)

Page 50: Manual PHP

Si eventualmente necesitásemos editarlo habríamos de recurrir al shell de la consola de windows y ejecutar el comanto: edit C:/ServidoresLocales/Apache/conf/openssl.cnf Creación de certificados digitales

Mediante la aplicación OpenSSL que suele venir configurada por defecto en la versiones de Ubuntu y cuya versión para Windows puede encontrarse en en este enlace podemos crear nuestros propios certificados digitales desde la consola de Ubuntu o desde el cmd.exe de windows siguiendo un proceso como el que se describe en las diferentes ventanas de esta imagen.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Siguiendo el proceso descrito en la imagen, y para facilitarte la labor hemos incluido aquí un fichero llamado certificados.zip que contiene, entre otros, los siguientes ficheros:

� – CA_privada.key, CA_solicitud.csr, CA_certificado.crt son la clave privada, la solicitud de certificado y el certificado digital autofirmado de una Autoridad Certificadora ficticia creada a modo de ejemplo.

� – Servidor_privada.key, Servidor_solicitud.csr, Servidor_certificado.crt son la clave privada, la solicitud de certificado y el certificado digital de un servidor ficticio. Han sido expedidos por la Autoridad Cerficadora ficticia mencionada en el párrafo anterior.

� – Cliente_privada.key, Cliente_solicitud.csr, Cliente_certificado.crt y Cliente_certificado.p12 son los nombres de los documentos correspondientes a la clave privada, solicitud y certificados digitales expedidos a un usuario ejemplo (cliente) por la Autoridad Cerficadora ficticia.

Todos ellos han sido creados siguiendo el proceso descrito gráficamente en las imágenes anteriores.

Manejo de certificados digitales de cliente

Una vez dispongamos de un certificado digital de cliente habremos de instalarlo en el navegador de nuestro equipo. En las imágenes se describen los pasos de las diferentes opciones de manejo de este tipo de certificados digitales.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso ¿Qué hay dentro de un certificado digital?

Si abrimos el fichero que contiene el certificado de cliente que hemos creado anteriormente nos encontraremos con una cadena de texto resultante de un proceso de codificación en base 64. Esa información es algo más transparente si recurrimos al uso de la función PHPopenssl_x509_parse tal como puedes ver en los ejemplo siguiente:

Certificado original Ver código fuente Contenido certificado Ver código fuente

Podrás observar que contiene (y se puede visualizar) información relativa al titular del certificado, período de validez, identidad del emisor, etcétera. Algo bastante parecido a un DNI tradicional. Hay entre toda la información un campo relevante. Observa que hay un valor llamado hash que es la huella digital resultante de aplicar la función hash durante proceso de emisión del certificado digital. Es un elemento decisivo para poder comprobar la autenticidad del certificado. Su verificación será realizada de forma automática por el navegador del cliente.

Servidor seguro en Windows

Comunicación en modo seguro

Una comunicación en modo seguro mediante el protocolo HTTPS se desencadena de la forma siguiente:

Page 51: Manual PHP

� – Al iniciarse, por parte del cliente, una sesión (una petición https://) el navegador envía al servidor un saludo (HELLO) compuesto por una cadena de texto generada de forma aleatoria y la información relativa a las versiones de SSL y algoritmos de cifrado que puede soportar.

� – El servidor responde informando de la versión de SSL y método de cifrado aceptados (el más robusto de los soportados por ambos) y envía otra cadena de texto también generada de forma aleatoria.

� – El servidor facilita al cliente su certificado digital y su clave pública de cifrado.

� – El cliente verifica el certificado digital del servidor y opta por aceptar o rechazar la comunicación.

� – Cuando el cliente trata de acceder a áreas en las que se exige disponer de algún tipo de certificado el servidor le solicita ese certificado digital.

� – Cuando el cliente, atendiendo a un requerimiento del servidor, facilita su certificado digital el servidor inicia un proceso de verificación de cuyo resultado depende la aceptación o rechazo la comunicación.

� – Finalizado de forma favorable el proceso de identificación el cliente genera una clave aleatoria, la encripta con la clave pública del servidor y se la envía.

� – El servidor recibe la clave, la desencripta con su llave privada (cifrado asimétrico). Si este proceso resulta fallido será síntoma de que existe discordancia entre la claves y que no hay certeza de que el servidor sea quien dice ser. Por el contrario, si todo va bien, se confirma la identidad de servidor y continúa el proceso

� – El cliente notifica al servidor que, en adelante, toda la información que facilite será encriptada con la clave acordada. Además, utilizando esa misma clave, encripta una hash (una huella digital) de toda conversación mantenida hasta el momento y la envía al servidor.

� – Si el servidor puede desencriptar el hash anterior es síntoma de que la clave es correcta y si, además, el valor de ese hashcoincide con el que crea el servidor con los mismos datos (la información intercambiada hasta el momento) se confirma que todo funciona correctamente.

� – Como última comprobación el servidor envía, también encriptado con la clave acordada, su hash junto con el aviso de cambio a modo cifrado.

� – El cliente desencripta, compara ambos hash y de resultar coincidentes confirma de forma definitiva que ambos comparten la misma clave (simétrica) y que están en condiciones de comunicarse en forma segura.

� – Al finalizar la sesión segura se destruye la clave que ambos compartían.

Configuración del servidor Apache en modo seguro

Area segura del servidor

Es bastante frecuente que los servidores dispongan de dos áreas. La zona no segura a la que se accede mediante el protocolo HTTPy la zona segura en la que será imprescindible utilizar el protocolo HTTPS.

Nuestro propósito es que el servidor web tenga una configuración tradicional –no segura– en la que el directorio raíz seaC:/ServidoresLocales/Apache/htdocs y restringir únicamente a modo seguro el acceso a uno de sus subdirectorios.

Para ello, crearemos un subdirectorio de C:/ServidoresLocales/Apache/htdocs poniéndole como nombre zona_segura. El paso siguiente será acceder a http://localhost/info.php y comprobar que tenemos activo el módulo SSL. Deberemos ver que OpenSSL supportaparece en modo enabled.

Si no lo estuviera deberemos activar ese módulo tal como se describe en esta página.

Certificados de servidor

Page 52: Manual PHP

El primer paso mantener una comunicación segura es tener la certeza de que realmente estamos en comunicación con quien pensamos que lo estamos haciendo. Como hemos descrito más arriba, el protocolo HTTPS requiere con carácter ineludible que el servidor disponga de un certificado digital. Estos certificados puedes extraerlos del fichero certificados.zip o crearlos siguiendo el proceso descrito enesta página

Una vez dispongamos de los certificados digitales hemos de colocarlos en algún lugar del equipo que alberga el servidor Apache sin que sea en ningún caso necesario ubicarlos dentro del root de servidor. Crearemos un subdirectorio con nombre certificados enC:/ServidoresLocales/Apache/ siendo, por tanto, su ruta absoluta C:/ServidoresLocales/Apache/certificados e incluiremos en él los documentos servidor_certificado.crt (certificado digital del servidor) y Servidor_privada.key (clave privada).

Modificaciones en el fichero httpd.conf

Una vez hayamos creado los directorios zona_segura y certificados e incluido en este el certificado y la clave privada del servidor ya estaremos en disposición de poder modificar la configuración del servidor Apache para su uso en modo seguro. Las modificaciones requeridas son las siguientes:

Fichero inicial httpd.conf La copia de seguridad nos permitirá recuperar la configuración actual si es necesario.

Guardar como httpd_noseguro.conf

Abrir httpd.conf Modificaciones en el fichero inicial

Línea Cambios Justificación

46

Donde dice: Con esta modificación estamos indicando a Apache que escuche las peticiones recibidas tanto a través del puerto 80 (modo no seguro) como a través del puerto 443 (utilizado habitualmente para las peticiones en modo seguro).

Listen 80 # cambiar por:

Listen 80 Listen 443

120

Donde dice: Indicamos a Apache que debe cargar el módulo SSL que es el que permite tramitar las peticiones de comunicación en modo seguro.

#LoadModule ssl_module modules/mod_ssl.so cambiar por:

LoadModule ssl_module modules/mod_ssl.so

Final del documento

Agregar:

<Directory "C:/ServidoresLocales/Apache/htdocs/zona_segura"> SSLRequireSSL </Directory>

SSLRequireSSL establece el modo seguro como único modo de acceso al directorio zona_segura. Cualquier petición no realizada mediante el protocolo https será rechazada.

NameVirtualHost *:443

NameVirtualHost es una directiva de Apache que permite especificar la IP a través de la que un host virtual puede recibir las peticiones. Al indicar *:443 estamos señalando como válida cualquier IP siempre que utilice el puerto 443 que es el reservado para conexiones seguras.

<VirtualHost *:443> SSLEngine On SSLOptions +StdEnvVars +ExportCertData SSLCertificateFile "C:/ServidoresLocales/Apache/certificados/Servidor_certificado.crt" SSLCertificateKeyFile "C:/ServidoresLocales/Apache/certificados/Servidor_privada.key" DocumentRoot "C:/ServidoresLocales/Apache/htdocs/zona_segura" ErrorLog C:/ServidoresLocales/Apache/logs/error.log LogLevel warn </VirtualHost>

Comentamos en la parte inferior de esta tabla los contenidos de estas líneas

Lo contenido entre <VirtualHost> y </VirtualHost> son las directivas que se aplicarán solo al host virtual al que acabamos de aludir. Hemos incluido las siguientes:

– SSLEngine On que es la directiva que habilita el uso del motor SSL del servidor.

– SSLOptions es una directiva a través de la que pueden establecerse condiciones específicas en los directorios a los que precede. Esas condiciones pueden ir

Page 53: Manual PHP

precedidas de un signo + o de un signo -. En el primer caso se entiende que la nueva condición se agrega a las preexistentes. En el segundo caso, signo menos-, estaríamos indicando que se desactive esa condición manteniendo activas las demás. Activaremos dos de ellas: +StdEnvVars y +ExportCertData. De esta forma podremos leer y utilizar desde PHP la información contenida en las variables de entorno del servidor y en los certificados del cliente y del servidor.

– SSLCertificateFile es una directiva imprescindible para poder indicar la ruta absoluta hasta el certificado del servidor.

– SSLCertificateKeyFile igual que el anterior, indicando esta la ruta absoluta hasta la clave privada de cifrado del servidor.

– DocumentRoot especifica la ruta absoluta hasta el directorio que será considerado como DocumenRoot de este servidor virtual que trabajará en modo seguro.

– ErrLog especifica la ruta absoluta hasta el fichero que ha de recoger el informes sobre errores producidos durante el funcionamiento del servidor.

– LogLevel establece los tipos de eventos que van a registrarse como errores. En este caso se registrarán desde advertencias (warn) y/o los errores de mayor gravedad que estas

Una página de prueba

Antes de reiniciar el servidor con la nueva configuración crearemos una página de prueba que puede tener unas características similares a esta.

<?php print "Estás en la zona segura del servidor"; ?>

La guardaremos con nombre index.php en el directorio zona_segura del servidor Apache.

Comprobaremos los resultados de intentar acceder a estas direcciones: https://localhost, http://localhost y http://localhost/zona_segura/

En le primer caso (https) estaremos accediendo en modo seguro y por lo tanto seremos dirigidos a la página index.php que hemos incluido en el directorio zona_segura que es el DocumentRoot del servidor cuando trabaja en ese modo.

En el segundo caso (http) estaremos accediendo en modo no seguro y visualizaremos la página index alojada en htdocs (directorio raíz del servidor).

El tercer supuesto, intento de acceder en modo no seguro (http) a un directorio seguro (zona_segura) nos dará un mensaje de error y nos será impedido el acceso.

Mensajes de advertencia en los navegadores

Hechas las modificaciones en la configuración y reiniciado el servidor los intentos de acceso en modo seguro (requiere escribirhttps:// en vez del clásico http:// que los navegadores añaden por defecto) van a aparecernos algunos mensajes de advertencia tales como los que puedes ver en los diferentes apartados de este gráfico.

Pulsando sobre la flechas de la imagen podrás visualizar los diferentes pasos del proceso

Formatos de los certificados

Los certificados digitales pueden tener diferentes formatos. Los más comunes son el formato DER (un formato binario utilizado básicamente por Java y por Macintosh) y el formato PEM (una representación de los certificados DER en base64, con marcas de inicio y final). El primero es el formato en el que el Cuerpo Nacional de Policía nos facilita los certificados raíz del DNIe cuyos ficheros disponibles puedesver en este enlace de dónde podremos descargar el fichero ACRAIZ-SHA1.zip que vamos a utilizar para la configuración de nuestro servidor.

La FNMT nos permite descargar su certificado raíz FNMTClase2CA.cer en ese mismo formato DER.

El servidor Apache requiere que los certificados tengan formato PEM (que coincide coun el certificados autofirmados que hemos creado en páginas anteriores. Por tanto nuestros certificados van a poder ser utilizados sin ninguna modificación por el servidor Apache.

Page 54: Manual PHP

Los certificados emitidos por la Dirección General de la Policía y por la FNMT tienen formato DER y por tanto es preciso hacer un cambio de formato (de DER a PEM) para poder utilizarlos en Apache. La transformación puede hacerse mediante OpenSSL ejecutando el siguiente comando:

openssl x509 -in nombre_del fichero_der -inform DER -out nombre_del_fichero_pem -outform PEM

Para facilitarte la tarea hemos incluido en el fichero certificados.zip los certificados raíz del DNIe y de la FNMT transformados a formatoPEM por medio de los siguientes comandos:

openssl x509 -in ACRAIZ-SHA1.crt -inform DER -out RaizDNIe.crt -outform PEM

openssl x509 -in FNMTClase2CA.cer -inform DER -out RaizFNMT.crt -outform PEM

Tenemos por tanto los certificados raíz de tres entidades certificadoras: nuestra propia CA (CA_certificado.crt, en formato PEM), elDNIe (ACRAIZ-SHA1.crt, en formato DER, y su equivalente en formato PEM, RaizDNIe.crt) y la FNMT (FNMTClase2CA.cer en formato DER y su equivalente en formato PEM, RaizFNMT.crt)

Accesos restringidos

Podemos establecer conexiones en modo seguro con un servidor si que se nos exija cumplir ningún requisito. El concepto seguro significaría que el servidor posee un certificado digital y que la información circula encriptada mediante una clave simétrica pero desconociendo la identidad del cliente.

Son habituales las webs que requieren un registro previo del usuario en el que se le facilita un nombre de usuario y contraseña que le serán requeridas siempre que intente acceder a un área restringida del servidor. Disponiendo de un servidor seguro es posible exigir al cliente que disponga de un certificado digital (certificado de cliente) para acceder al área segura del servidor o algunos de sus directorios. Veamos como hacerlo.

Empezaremos creando un subdirectorio de zona_segura al que pondremos el nombre de restringido e incluiremos en él una página, a la que llamaremos index.php, que nos permita hacer la comprobación de funcionamiento y que puede contener algo como esto:

<?php print "Aquí solo acceden los usuarios autorizados"; ?>

Nuestro propósito es que sólo puedan acceder acceder al directorio restringido los cliente que dispongan de: nuestro certificado autofirmado de pruebas, de un DNIe o de un certificado de la FNMT.

Establecer esa exigencia nos obliga a configurar Apache de forma que sepa qué ha de requerir un certificado de cliente y, además,qué tipo o tipos de certificados debe considerar válidos. Esto se logra a través de la directiva SSLCACertificateFile que debe ir seguida de la ruta absoluta hasta un único fichero que ha de contener el certificado o certificados raíz de las entidades certificadoras a cuyos usuarios se va a permitir el acceso.

En el epígrafe anterior hemos visto como obtener, en formato PEM, los tres certificados (CA_certificado.crt, RaizDNIe.crt yRaizFNMT.crt). Hemos de juntarlos en un único fichero. Para ello podemos crear un fichero nuevo en blanco (con Notepad++, el bloc de notas o cualquier otro editor de tu elección) e ir incluyendo en él, uno tras de otro, los contenidos de los tres ficheros anteriores. Una vez guardemos el fichero resultante –podemos ponerle como nombre todos.crt– en el almacén de certificados del servidor (C:/ServidoresLocales/Apache/certificados) ya solo nos faltaría modificar el fichero de configuración de Apache para que contemple la nueva situación. La modificaciones a realizar son las que puedes ver en esta tabla

Fichero inicial httpd.conf

Guardar como http_seguroparcial.conf Nos permitirá recuperar la configuración actual si es necesario

Abrir httpd.conf Trabajaremos sobre la configuración previa una vez asegurada una copia de ella

Modificaciones en el fichero inicial

Donde dice:

SSLCertificateKeyFile "C:/ServidoresLocales/Apache/certificados/Servidor_privada.key"" insertar inmediatamente después

SSLCACertificateFile "C:/ServidoresLocales/Apache/certificados/todos.crt"

Page 55: Manual PHP

Final del documento

Agregar inmediatamente antes de la etiqueta de cierre </VirtualHost> que hay al final del fichero :

<Directory "C:/ServidoresLocales/Apache/htdocs/zona_segura/restringido"> SSLVerifyClient require SSLVerifyDepth 2 SSLRequire (%{SSL_CLIENT_V_REMAIN} >= "0") </Directory>

Se trata de señalar la existencia un fichero con los certificados raíz de las entidades certificadoras autorizadas mediante la directivaSSLCACertificateFile seguida de la ruta absoluta hasta el fichero que contiene tales certificados y de indicar qué directorio debe restringirse y cuales son las condiciones de acceso. Esto se indica en el contexto de las etiquetas <Directory > y </Directory >. Dentro de la etiqueta de apertura indicamos la ruta absoluta del directorio objeto de la restricción (en este caso será restringido) y entre la etiqueta de apertura y cierre incluimos las restantes directivas.

Mediante el valor require de la directiva SSLVerifyClient Apache interpretará que debe exigir uno de los certificados de usuario permitidos para acceder al directorio en el que se incluye tal directiva. Si esta directiva no se incluyera entre las etiquetas <Directory></Directory> se entendería que la directiva habría de afectar a todo el espacio del servidor seguro.

Con las modificaciones anteriores el servidor ya debería funcionar de acuerdo con nuestros propósitos. Sin embargo, si observas con detenimiento las propuestas de modificaciones, verás que hemos incluido la directiva SSLVerifyDepth 2.

Esto es consecuencia de la inclusión de la opción DNIe. La Autoridad de Certificación de Primer Nivel de la Dirección General de la Policía sólo emite certificados para sí misma y para sus tres autoridades certificadoras subordinadas. Por tanto, ninguno de los certificados incluidos en los DNie es emitido por la Autoridad Certificadora de primer nivel. Quien certifica nuestro DNIe es una de las tres entidades subordinadas (segundo nivel) que dependen de aquella. Por medio de la directiva SSLVerifyDepth 2 lo que hacemos es indicarle a Apache que, si es necesario, profundice hasta ese segundo nivel para comprobar la validez del certificado del cliente.

Aunque es un aspecto que será comentado con un poco más de profundidad en párrafos suguientes, la línea dónde dice SSLRequire (%{SSL_CLIENT_V_REMAIN} >= "0") tiene como finalidad que Apache impida el acceso a usuarios cuyos certificados del cliente han superado su período de validez (qué «han caducado»).

Hechas estas modificaciones y reiniciado Apache podremos comprobar que para acceder a la dirección https://localhost" no es preciso disponer de ningún certificado. Por el contrario, si pretendemos acceder a https://localhost/restringido/ nos será requerido uno cualquiera de estos tres certificados: nuestro certificado autofirmado, un certificado de la FNMT ó el DNIe.

Condicionando las restricciones

En la configuración anterior establecimos como única condición para el acceso al directorio restringido disponer de uno de los certificados permitidos y que este no hubiera expirado (un poco más abajo comentamos la directiva SSLRequire que es la que hace esta comprobación).

Vamos a crear ahora dos nuevos subdirectorios en la zona_segura. A uno le llamaremos dnie y al otro certificado. Incluiremos en cada uno de ellos, una página con nombre index.php para hacer las pruebas de funcionamiento. El acceso al primero –dnie– lo limitaremos a los usuarios que dispongan de un DNIe o un certificado de la FNMT e impediremos que pueda hacerlo quien sólo disponga del certificado autofirmado creado para nuestros ejemplos.

Para el acceso al directorio certificado invertiremos las condiciones. Sólo estará permitido el acceso autentificándose mediante el certificado autofirmado. Dado que en el fichero todos.crt están incluidos los certificados raíz de las tres entidades certificadoras permitidas, las nuevas limitaciones requieren hacer uso de la directiva SSLRequire. Veamos que ocurre con ella.

Cuando hacemos una petición en modo seguro y la directiva SSLOptions está configurada +StdEnvVars se transfieren al servidor una serie de variables de entorno cuyos nombres y valores para cada uno de los tres tipos de certificado (lógicamente con datos ficticios) puedes ver en este enlace:

Variables de entorno

Los valores que se visualizan en este ejemplo son el resumen de la comprobación de los tres tipos de certificados digitales y han sido obtenidos desde PHP mediante funciones similares a esta: <?php print getenv('SSL_CLIENT_S_DN');?> dónde a la función getenv se el incluye como parámetro el nombre de una variable de entorno. Aquí tienes el código fuente del script que hemos utilizado para leer todas esas variables (https) y la lista de sus nombres.

Page 56: Manual PHP

Si observamos con un poco de detenimiento la tabla del enlace anterior podremos ver que hay una variable de entorno llamadaSSL_CLIENT_V_REMAIN que recoge los días que faltan hasta la fecha de caducidad de cada uno de los certificados. También podemos ver como SSL_CLIENT_I_DN_OU recoge los valores: certNORA, FNMT Clase 2 CA y DNIE y que en SSL_CLIENT_I_DN_Ose incluyen: ACREDITACIONES DEL NORA, S.L.U., FNMT y DIRECCION GENERAL DE LA POLICIA.

Mediante la directiva SSLRequire y manejando esas variables, a las que se alude mediante la sintaxis %{nombre de la variable} , los operadores lógicos and y/o or (en minúsculas porque SSLRequire es sensible a mayúsculas/minúsculas) y los operadores de comparación== (o su equivalente eq), <, > podemos establecer las condiciones requeridas para el acceso a un directorio determinado. Analicemos este ejemplo.

SSLRequire (%{SSL_CLIENT_V_REMAIN} >= "0" \

and (%{SSL_CLIENT_I_DN_O} eq "FNMT" or %{SSL_CLIENT_I_DN_OU} == "DNIE"))

SSLRequire() incluye, dentro del paréntesis, las condiciones restriccitivas que son las siguientes: %{SSL_CLIENT_V_REMAIN} >= "0"que exige que el certificado no haya expirado con anterioridad a la fecha (le quede un número de días de vigencia mayor o igual que cero). Aunque se trate de un valor númerico el cero lo pondremos entre comillas.

La barra invertida (\) es un carácter imprescindible para indicar que el condicionado continúa en la línea siguiente y el operador andagrega como nueva condición que %{SSL_CLIENT_I_DN_O} eq "FNMT" or %{SSL_CLIENT_I_DN_OU}=="DNIE" lo cual significa la exigencia de que solo serán admisibles certificados en los que SSL_CLIENT_I_DN_O="FNMT" o aquellos en los que SSL_CLIENT_I_DN_OU="DNIE".

De esta forma, las últimas modificaciones de la configuración de Apache, serían las siguientes:

Fichero inicial httpd.conf

Guardar como httpd_seguro_parcial_1.conf Nos permitirá recuperar la configuración actual si es necesario

Abrir httpd.conf Trabajaremos sobre la configuración previa una vez asegurada una copia de ella

Modificaciones en el fichero inicial

Final del documento

Agregar inmediatamente antes de la etiqueta de cierre </VirtualHost> que hay al final del fichero :

<Directory "C:/ServidoresLocales/Apache/htdocs/zona_segura/dnie"> SSLVerifyClient require SSLVerifyDepth 2 SSLRequire (%{SSL_CLIENT_V_REMAIN} >= "0" \ and (%{SSL_CLIENT_I_DN_O} eq "FNMT" \ or %{SSL_CLIENT_I_DN_OU} == "DNIE")) ErrorDocument 403 https://localhost/error_dnie.html </Directory> <Directory "C:/ServidoresLocales/Apache/htdocs/zona_segura/certificado"> SSLVerifyClient require SSLVerifyDepth 2 SSLRequire (%{SSL_CLIENT_V_REMAIN} >= "0" \ and %{SSL_CLIENT_I_DN_OU} =="certNORA") ErrorDocument 403 https://localhost/error_certificado.html </Directory>

Las líneas ErrorDocument tienen por finalidad redirigir el navegador hacia una página de error cuando intentemos acceder a un directorio restringido sin cumplir los requisitos exigidos para ello.

Una vez hayas reiniciado el servidor después de finalizar la configuración podrás comprobar el funcionamiento accediendo a las direcciones: https://localhost, http://localhost y http://localhost/zona_segura/

Para acceder la primera de ellas necesitarás uno de los tres certificados. El segundo requiere disponer de DNIe o un certificado de la FNMT y al tercero podrás acceder si tienes instalado en tu navegador el certificado auto firmado de cliente.

Page 57: Manual PHP

Extraer información de certificados

Validez de los certificados

Las comprobaciones que se realizan sobre los certificados de usuarios presentan una laguna importante. La validez nos indica que un certificado de DNIe no ha sido manipulado y que ha sido emitido por la policía pero no podemos saber si está siendo usado de modo indebido por el titular o por una tercera persona.

Supongamos que perdemos nuestro DNIe, o que simulamos su extravío. En el momento de formular la denuncia, la autoridad gubernativa procede a revocar nuestro certificado digital incorporándolo a la base de datos pública de documentos revocados. Podríamos entender que a partir de ese momento no somos responsables del uso que pueda hacerse del mismo.

Para consultar esa base de datos existe un protocolo conocido como OCSP (Online Certificate Status Protocol) mediante el cual podemos conocer la validez actual de un certificado. Estos servicios de validación del DNIe, según informan en su web, son prestados de forma ininterrumpida todos los días del año por la «Fábrica Nacional de Moneda y Timbre – Real Casa de la Moneda, que prestará sus servicios de validación con carácter universal: ciudadanos, empresas ...»

No ocurre lo mismo con los certificados FNMT ya que en ese caso el acceso al «OCSP responder» no tiene carácter gratuito.

Más abajo tienes un ejemplo de la forma de efectuar la comprobación de la validez del DNIe y, además, podrás ver la forma en la que pueden extraerse los datos personales: nombre, apellidos, nº, periodo de validez e incluso la fecha de nacimiento del titular. Da la impresión de que en un futuro no muy lejano la autentificación, e incluso la restricción de accesos a menores, acabará haciéndose mediante este documento.

Lectura de certificados de cliente

PHP dispone de la función openssl_x509_parse(certificado, booleano) que permite leer los contenidos de un certificado en formatoPEM y recoger los resultados en un array. Cuando el parámetro booleano es TRUE -valor por defecto- utilizará nombres cortos para los índices de los arrays. Si a ese parámetro le asignamos el valor FALSE (0) usaría nombres largos. La diferencia puedes verla en estos ejemplos. Estos primeros utilizan la opción FALSE

lee certNORA leee FNMT lee DNIe

mientras que estos otros utilizan la opción TRUE. En ambos caso estamos visualizando copias de los tres tipos de certificados: nuestros certificados de prueba, un certificado de la FNMT y otro de DNIe.

lee certNORA leee FNMT lee DNIe

Para que puedas visualizar estos certificados ejemplo –tienen carácter público y no pueden ser utilizados sin su clave privada– hemos guardado aquí una copia de ellos.

Cuando intentamos acceder, mediante el protocolo HTTPS a un directorio configurado como SSLVerifyClient require y, además se incluye la opción SSLOptions +ExportCertData los datos de los certificados de servidor y cliente son recogidos en dos variables superglobales. La variable $_SERVER['SSL_SERVER_CERT'] contendrá una cadena con el certificado del servidor en formato PEM mientras que$_SERVER['SSL_CLIENT_CERT'] tendrá idéntica información pero referente al certificado del cliente.

Este es el código fuente del script utilizado en los ejemplos anteriores.

ver código fuente

Verificación de la validez de un DNIe

OpenSSL dispone de una utilidad que permite verificar la validez de un DNIe. Se trataría de ejecutar desde la línea de comando lo siguiente:

openssl ocsp -CAfile cert_raiz_dnie -issuer emisor -cert cliente -url direccion cuyos parámetros son los siguientes:

Page 58: Manual PHP

� – cert_raiz_dnie es la ruta completa y el nombre del fichero (en formato PEM) que contiene el certificado raíz del DNIe.

� – emisor es la ruta completa y el nombre de un fichero que contiene los datos del emisor del certificado de DNIe. Se extrae de este documento.

� – cliente es la ruta completa y el nombre de un fichero que contiene los datos del cliente extraidos del DNIe (se extrae de este documento.

� – direccion es la dirección que permite hacer la comprobación. En el caso del DNIe sería http://ocsp.dnie.es

Este comando puede ser ejecutado desde PHP incluyéndolo en una llamada a la funcion shell_exec() que es el procedimiento que seguimos en el ejemplo que tienes a continuación.

Al ver el código fuente podrás observar que al script le hemos incluido una opción para simular la lectura de un DNIe sin necesidad de que configures tu servidor en modo seguro y, a la vez, para intentar ilustrar las posibilidades del DNIe y el certificado de la FNMT en el caso en que no dispongas de ellos o no te apetezca configurar tu servidor para trabajar en modo seguro.

Para utilizar este script con tu propio DNIe bastará con que lo copies al directorio restringido de la configuración que hemos propuesto para el servidor seguro (obviamente es imprescindible esa configuración) y que accedas a él a través de la dirección:https://localhost/dnie/test_dnie.php.

Verificar DNIe Ver código fuente

¡Cuidado! Si has hecho la verificación del DNIe del ejemplo anterior habrás podido ver que el certificado está revocado. Eso es cierto. El certificado que hemos utilizado corresponde a un DNIe que ya fué renovado y como consecuencia de esa renovación ha sido emitido un nuevo certificado con la consiguiente revocación del anterior.

¿Cómo utilizar el DNIe?

La utilización del DNIe electrónico requiere, tal como se explica en su página oficial, disponer de un lector de tarjetas inteligentes que cumpla el estándar ISO-7816. Existen distintas implementaciones, bien integrados en el teclado, bien externos (conectados vía USB) o bien a través de una interfaz PCMCIA el uso de un lector.

Además del dispositivo de hardware es necesario realizar algunas instalaciones de certificados y modificar algunas configuraciones de los navegadores. Este proceso puede hacerse basándose en la información de la web oficial del DNIe o recurriendo a algunas herramientas que permiten efectuar esa instalación y configuración de forma automática. Una de ellas, https://zonatic.usatudni.es nos permite descargar paquetes autoinstaladores tanto para las diferentes versiones de Windows como para Ubuntu.

Para este último caso, en enlace anterior aún no dispone en esta fecha (03.04.2012) de una versión que permita realizar la instalación sobre Ubuntu 12.04. Para quien necesite utilizar esta versión del sistema operativo hay una excelente página http://bitplanet.es/manuales/3-linux/ dónde se describe de forma minuciosa todo el proceso de instalación y configuración en este supuesto.

Páginas WEB dinámicas

Servidores y clientes

Es frecuente observar, en la calle, que son muchas las personas que cuando se refieren a los servidores lo hacen como sí se tratara de máquinas complejísimas, misteriosas, lejanas y enormes que, bajo esa aureola de cripticismo, parecen totalmente distintas al ordenador que usamos habitualmente. ¡Nada más lejos de la realidad!

Vamos a intentar aclarar algunos conceptos con ejemplos cotidianos. Pensemos en esos ordenadores remotos (también llamados host) como si se tratara de uno esos sitios desde los que se sirven comidas a domicilio.

Page 59: Manual PHP

Quizá lo primero en lo que se te ocurra pensar sea en una pizza, no porque desconozcas que también es posible comprar otras cosas sino por la popularidad de ese tipo de servicio. Algo similar ocurre con los host. La frecuencia con la que accedemos a ellos en demanda de páginas web hace que tendamos a identificarlos con ellas, pero también los host ofrecen –o pueden ofrecer– más servicios. Sigamos con las comidas a domicilio.

Cada una de esas empresas puede atender peticiones de uno solo o de varios servicios distintos (pizzas, helados, o platos regionales, por citar algunos ejemplos), pero la oferta de cada uno de esos servicios requiere una infraestructura adecuada a cada caso. La oferta de pizzas exigirá disponer de un horno, y la de helados necesitará de una instalación frigorífica.

Pues bien, algo muy similar ocurre con los host. También éstos pueden ofrecer uno o varios servicios (páginas web, correo electrónico, transferencias FTP, noticias, etcétera) y también es necesario que cada servicio disponga de su propia infraestructura, que en este caso sería un programa distinto (software de servidor) para cada uno de ellos.

Como puedes ver, no basta con hablar de servidores. Es necesario especificar también qué es lo que sirven.Habría que decir: servidor de páginas web, servidor de correo, etcétera y tener presente que –aunque convivan en la misma máquina– cada uno de ellos requiere su propio software y su propia configuración.

Resumiendo, cuando en lenguaje coloquial hablamos de un servidor estamos aludiendo un host (ordenador remoto) –el tamaño y la lejanía carecen de importancia– provisto de programas (software de servidor) que, cuando está accesible (conectado a Internet) y con el software activo (servidor en funcionamiento) es capaz de atender peticiones y devolver a los clientes los documentos solicitados, o un mensaje de error, en el caso de que no estuvieran disponibles.

Veamos un ejemplo de cómo se desarrolla ese proceso de petición–respuesta. Para leer el correo electrónico necesitas disponer de un programa –supongamos que es Outlook Express– instalado en tu ordenador y hacer, a través de él, una petición a un ordenador remoto (host). Si quisieras visualizar páginas web tendrías que utilizar un programa distinto –Firefox o Internet Explorer, por ejemplo– capaz de realizar esta otra tarea. Al programa que utilizamos para realizar cada petición le llamaremos cliente.

¿Qué es una petición?

Una petición es un conjunto de datos que un cliente (recuerda que el cliente siempre es uno de los programas instalados en tu ordenador) envía a través de Internet solicitando una respuesta determinada por parte de un servidor (ordenador remoto).

¿Qué contendría esa petición?

Cada tipo de petición tendrá contenidos distintos. Por ejemplo, cuando se trata de leer mensajes de correo, la petición realizada por el cliente (Outlook Express) contendría, entre otros, muchos de los datos de la configuración de la cuenta, tales como: el protocolo (forma de comunicación) –en el caso del correo lo habitual sería el protocolo POP (Post Office Protocol)–, el nombre de host donde está alojado el buzón (servidor POP ó servidor de correo entrante), el nombre de la cuenta, la contraseña de acceso, y algunas otras informaciones relativas a la gestión de esa cuenta tales como si deben conservarse o no los mensajes en el servidor, etcétera.

¿Qué ocurre con esa petición?

Cualquier petición pasa en primera instancia por un servidor de nombres de dominio (Domain Name Server) DNS, una especie de guía telefónica que contiene los nombres de los servidores y las direcciones IP a través de las cuales están conectados a Internet. Podría decirnos –los datos son ficticios– que olmo.cnice.mecd.es es el nombre de un host que está conectado a Internet a través de la dirección IP 111.112.113.114

Una vez resuelta esa petición por el servidor DNS (direccionamiento de la petición a la IP correspondiente) se comprobará si esa IP está activa (si efectivamente hay un ordenador conectado a través de ella) y, en caso de estarlo, se determinará si ese ordenador al que estamos accediendo es capaz de atender la petición.

¿Qué tiene que ocurrir para que pueda atenderse una petición?

Es necesario que el ordenador remoto tenga instalado y funcionando el software de servidor adecuado al protocolo de nuestra petición. Ello quiere decir –siguiendo con el ejemplo– que el ordenador remoto debe tener instalado y funcionando un software específico de servidor de correo capaz de interpretar el protocolo POP3 especificado en la petición.

¡Cuidado!

Page 60: Manual PHP

El ordenador remoto debe tener instalado y funcionando el software adecuado a cada tipo de petición (servicio) que deba atender. No basta con decir servidor, es preciso conocer los servicios que presta y es factible que un mismo ordenador preste –simultáneamente– varios servicios, siempre que tenga instalado y activo el software específico para cada uno de esos servicios.

Cuando el ordenador remoto acepta la petición el software de servidor y/o las aplicaciones del lado del servidor (software instalado en el ordenador remoto y vinculado con el software de servidor) resuelven la petición (comprobar que el nombre de la cuenta y la contraseña son correctas, comprobar si existen mensajes, borrarlos del buzón si así lo especifica la petición, etc.) y devuelven al cliente (recuerda que el cliente era nuestro Outlook Express) la información requerida.

Solo falta que una vez recibida la respuesta Outlook Express (cliente) interprete la información recibida y nos permita visualizar o imprimir el contenido de los mensajes descargados del servidor.

Servidor y cliente en una misma máquina

Hasta ahora –al referirnos a servidores y clientes– hemos hecho alusión a dos máquinas: nuestro propio ordenador (ordenador local) en el que estarían instaladas las aplicaciones cliente y un ordenador remoto en el que se alojarían las aplicaciones de servidor. Eso es lo más habitual, pero no es la única posibilidad. Dado que servidor y cliente son únicamente aplicaciones, es perfectamente posible que ambas convivan dentro de la misma máquina.

La diferencia sustancial sería que ahora no es necesario el servidor de DNS para buscar la dirección IP. Utilizaríamos una IP (habitualmente la 127.0.0.1) reservada para estos casos –preestablecida en la configuración del servidor– y a través de ella se canalizarían las peticiones a nuestro propio servidor. Ya hablaremos más adelante de esta IP.

Tipos de páginas web

Una de las clasificaciones más simples de las páginas web permitiría agruparlas en dos tipos: estáticas y dinámicas.

Páginas web estáticas

Diremos que una página web es estática cuando sus contenidos no son susceptibles de ser modificados ni por intervención del usuario ni por una acción automática del servidor (ordenador remoto) ni del cliente (navegador).

Un ejemplo de página estática

Cualquier usuario que acceda a esta página que incluimos como ejemplo –ya sea en modo local, o a través de un servidor remoto– visualizará siempre la misma fecha: 22 de setiembre de 2011.

<html> <head> </head> <body> Hoy es 22-09-2011 y son las 11:23:37 horas </body> </html>

ejemplo1.html

Las peticiones de páginas estáticas se realizan de la forma que puedes ver en este esquema.

Page 61: Manual PHP

Si observas con detenimiento el esquema de la parte superior es posible que encuentres algo que no te cuadre... porque en el esquema hay un servidor que parece imprescindible para atender las peticiones y sin embargo tú –sin tener instalado ningún servidor– eres capaz de visualizar tus propias páginas web sin más hacer un doble clic sobre su icono. Eso es cierto, pero fíjate en las dos direcciones que aparecen en esta otra imagen.

La de la izquierda –consecuencia de haber hecho doble clic sobre el icono del documento– contiene como dirección una ruta (el path que conduce hasta el documento) mientras que en la de la derecha aparece el sintagma http al principio de la dirección.

En el primer caso no hemos hecho ninguna petición de página web sino que hemos abierto un documento cuya extensión (html) está asociada en nuestra configuración de Windows con Firefox, Internet Explorer, Opera o cualquier otro navegador que

Page 62: Manual PHP

tengamos instalado en nuestro equipo. El proceso ha sido exactamente el mismo que si hubiéramos hecho doble clic sobre el icono de un documento con extensióntxt, con la única salvedad de que en este último caso se habría abierto el bloc de notas (por la asociación de extensiones y aplicaciones en la configuración de Windows).

En el segundo caso las cosas son distintas. Se incluye el sintagma http – acrónimo de HiperText Transfer Protocol– para indicar que ese es el protocolo que debe ser utilizado y que será preciso que el servidor que reciba la petición sea capaz de interpretarlo. Por eso a los servidores que alojan páginas web se les suele llamar servidores web o servidores HTTP dado que se les requiere que soporten este protocolo.

Páginas dinámicas

Llamaremos dinámicas a las páginas cuyos contenidos sí pueden ser modificados –de forma automática o mediante la intervención de un usuario– bien sea desde el cliente y/o desde el servidor.

Para que una modificación de este tipo pueda producirse es necesario que algo o alguien especifique: qué, cómo, cuándo, dónde y de qué forma debe hacerse el cambio, y que exista otro algo o alguien capaz de: acceder, interpretar y realizar, en el momento preciso, las instrucciones de modificación. Igual que ocurre en el contexto de la vida cotidiana, las especificaciones y las instrucciones precisan de un lenguaje para poder definirlas, un soporte para almacenarlas, y un intérprete capaz de ejecutarlas.

Somos capaces de entender unas instrucciones escritas en castellano pero si estuvieran escritas en búlgaro las cosas seguramente serían bastante distintas, y, por supuesto, a un búlgar@ le pasaría justamente lo contrario. Igual ocurre con los programas intérpretes de los lenguajes de script. Ellos también requieren órdenes escritas en su propio idioma.

Scripts

Se llama script a un conjunto de instrucciones, escritas en un lenguaje determinado, que van incrustadas dentro de una página WEB de modo que su intérprete pueda acceder a ellas en el momento en el que se requiera su ejecución.

Cuando se incrustan scripts en una página WEB empiezan a convivir dentro de un mismo documento informaciones destinadas a distintos intérpretes. Por una parte, el código HTML que ha de ser interpretado por el navegador, y por la otra, los scripts que han de ser ejecutados por el intérprete propio del lenguaje en el que hayan sido escritos.

La manera de diferenciar los contenidos es delimitar los scripts marcando su comienzo con una etiqueta de apertura <script> y señalando el final con una etiqueta de cierre </script>.

Lo que no está contenido entre esas etiquetas será considerado código HTML.

La posibilidad de insertar en un mismo documento scripts escritos en distintos lenguajes obliga a especificar cuál se ha utilizado en cada caso, para que en el momento en el que vayan a ser ejecutados se invoque el intérprete adecuado.

Para ello, dentro de la propia etiqueta de apertura (<script>) se inserta una referencia al tipo de lenguaje con esta sintaxis:language="nombre"

Por ejemplo: <script language="PHP"> ...... ...... instrucciones .. ...... </script> indicaría que las instrucciones están escritas con la sintaxis de PHP. Por el contrario, al escribir: <script language="JavaScript"> ...... ...... instrucciones .. ...... </script> estaríamos señalando que en las instrucciones contenidas en el script utilizan sintaxis de JavaScript. La alternativa más reciente (la anterior está obsoleta) sería:<script type="text/javascript">

Page 63: Manual PHP

Para el caso concreto de PHP, existe una sintaxis alternativa, mucho más cómoda y habitual. Es la siguiente: <?php ...... ......instrucciones.. ...... ?>

Aquí <?php hará la misma función que <script language="PHP"> y ?> será equivalente a </script>. Con la configuración adecuada también podríamos usar <? –en vez de <?php– como marca inicial.

Lenguajes de script

Hay múltiples posibilidades en cuanto a lenguajes de script. Pero antes de hacer mención a algunos de ellos es conveniente hacer una clasificación previa. Hablaremos de dos tipos:lenguajes del lado del cliente y lenguajes del lado del servidor.

Lenguajes del lado del cliente

Diremos que un lenguaje es del lado del cliente cuando el intérprete que ha de ejecutar sus scripts es accesible desde éste –el cliente– sin que sea necesaria ninguna intervención en este sentido por parte servidor.

Seguramente te ha ocurrido alguna vez que al intentar acceder a una página web ha aparecido un mensaje advirtiendo que para la correcta visualización de la página se requiere un plug-in determinado, y que, a la vez, se te haya ofrecido la posibilidad de descargarlo en ese momento. Eso ocurre porque cuando el navegador –que en el caso de las páginas web es el cliente– trata de interpretar la página, encuentra incrustado en ella algo (un fichero de sonido, una animación Flash, etcétera) que –de forma muy similar a lo que ocurre con los scripts– requiere un intérprete adecuado del que no dispone en ese momento. Cuando los scripts contenidos en un documento son de este tipo, el servidor lo entrega al cliente si efectuar ningún tipo de modificación.

Sin pretender hacer una enumeración exhaustiva, entre los lenguajes de script del lado del cliente los más populares son: DHTML,JavaScript y VBScript.

DHTML no es exactamente un lenguaje de programación. Se trata más bien de una serie de capacidades que se han ido añadiendo a los navegadores modernos mediante las cuales las páginas pueden contener hojas de estilo y/o organizarse en capas susceptibles de ser modificadas, redimensionadas, desplazadas y/o ocultadas.

JavaScript es uno de los lenguajes más populares. Cada navegador incluye su propio intérprete y es frecuente que los resultados de visualización sean algo distintos según el navegador y la versión que se utilice. Parece ser que las versiones más recientes de los distintos navegadores se aproximan a un estándar –ECMA Script-262– que ha sido desarrollado por la ECMA (Asociación Europea de Normalización de Sistemas de Información y Comunicación), lo que hace suponer que en un futuro muy próximo todos los navegadores se ajustarán a esa especificación y que, con ello, las páginas web ya se visualizarán de forma idéntica en todos ellos.

VBScript es un lenguaje de script derivado de VisualBasic y diseñado por Microsoft para Internet Explorer y los navegadores derivados o vinculados a este. Una petición de página en la que hay incrustados scripts escritos en lenguaje del lado del cliente se realizaría de una forma similar a esta:

Page 64: Manual PHP

Como puedes observar no requiere nada distinto a lo del supuesto anterior. La diferencia sería que en este caso se harían llamadas al intérprete de JavaScript –incluido en los navegadores, tal como comentamos al margen– y/o a eventuales plugins necesarios para interpretar otros tipos de script.

Aquí tienes dos ejemplos de páginas web dinámicas. Ambas utilizan los JavaScript que puedes ver en rojo en su código fuente. Si pulsas en el enlace del primero de estos dos ejemplos verás que la fecha que aparece en la página es la fecha actual de tu sistema, y además, cada vez que pulses el botón Actualizar de tu navegador comprobarás que esa intervención del usuario modifica los contenidos actualizando la hora que aparece en el documento.

<html> <head> <script type="text/javaScript"> var son= new Date(); var fecha=son.getDate()+" - "+(son.getMonth()+1)+" - "+son.getFullYear(); var hora=son.getHours()+":"+son.getMinutes()+":"+son.getSeconds(); document.write('Hoy es '+fecha+' y son las '+hora+' horas'); </script> </head> <body> </body> </html>

ejemplo2.html

En este otro ejemplo la modificación de los contenidos no requiere intervención alguna por parte del usuario. Cada 5 segundos (fíjate donde dice var frecuencia=5000). Cinco mil es el período de actualización, expresado en milisegundos) se rescribirán de forma automática la fecha y la hora. Tenemos por tanto una especie de cronómetro automático.

<html> <head> <script type="text/javaScript"> var reloj=0; var frecuencia=5000; function actualiza(){

Page 65: Manual PHP

var son= new Date(); var fecha=son.getDate()+" - "+(son.getMonth()+1)+" - "+son.getFullYear(); var hora=son.getHours()+":"+son.getMinutes()+":"+son.getSeconds(); var escribe='Hoy es '+fecha+' y son las '+hora+' horas'; var situa=document.getElementById('capa0'); situa.innerHTML=escribe; reloj=setTimeout("actualiza()",frecuencia); } </script> </head> <body onLoad="actualiza()";> <div id="capa0"> </div> </body> </html>

ejemplo3.html

Lenguajes del lado del servidor

Un lenguaje es del lado del servidor cuando la ejecución de sus scripts se efectúa, por instancia de este –el servidor–, antes de dar respuesta a la petición, de manera que el cliente no recibe el documento original sino el resultante de esa interpretación previa.

Cuando se usan estos tipos de lenguaje el cliente recibe un documento en el que cada script contenido en el original habrá sido sustituido por los resultados de su ejecución. Esto es algo a tener muy en cuenta, porque, en este caso, los usuarios no tendrán la posibilidad de visualizar el código fuente, mientras que cuando se trata de lenguajes del lado del cliente siempre es posible visualizar los scripts, bien sea de forma directa –mirando el código fuente de la página recibida– o leyendo el contenido de ficheros externos –vinculados a ella– que son bastante fáciles de encontrar en la caché del navegador. La utilización de este tipo de scripts requiere que el intérprete del lenguaje sea accesible –esté del lado– desde el propio servidor.

Entre los lenguajes del lado del servidor los más populares son: PHP, ASP, PERL y JSP. Cada uno de ellos tiene sus propias peculiaridades. No abundaremos en ellas. Nosotros trataremos aquí sobre PHP.

Las posibles dudas del servidor

Dado que en unos casos el servidor debe entregar el documento original –páginas estáticas o páginas dinámicas en las que se usan lenguajes del lado del cliente– mientras que en otros casos –páginas dinámicas usando lenguajes del lado del servidor– tiene que devolver el resultado de la ejecución de los scripts, es razonable que te preguntes: ¿cómo sabe el servidor lo que debe hacer en cada caso?

La respuesta es simple. Eso hay que decírselo. Y se le dice de una forma bastante simple. Se indica al poner la extensión al documento. Si en la petición se alude a un documento con extensión .htm o .html el servidor entenderá que esa página no requiere la intervención previa de ningún intérprete de su lado y entregará la página tal cual.

Si en esa petición se aludiera a una extensión distinta –.php, por ejemplo– el servidor entendería que antes de servir la página debe leerla y requerir al intérprete de PHP que ejecute los scripts desarrollados en ese lenguaje (en caso de que los contuviera) y devolvería al cliente el documento que resultara de las eventuales ejecuciones de tales scripts.

Aquí tienes el esquema de un ejemplo de convivencia en un mismo documento de varios scripts y varios tipos de lenguaje.

Page 66: Manual PHP

Aquí ya es preciso que, además de un servidor capaz de soportar el protocolo HTTP, esté instalado –del lado del servidor– un intérprete PHP, un servidor de bases de datos MySQL y que, además, estén configurados de modo que puedan interactuar entre ellos.

El lenguaje PHP dispone de funciones que le permiten acceder a muy diversos tipos de servidores de bases de datos pudiendo: crear, consultar, borrar y modificar tanto bases de datos como tablas y registros de las mismas. Nosotros vamos a utilizar MySQL, unos de los gestores más potentes y populares que existen en este momento.

Requisitos para el uso del Lenguaje PHP

De acuerdo a lo comentado en los párrafos anteriores el uso del lenguaje PHP requiere tener instalado y configurado:

� – Un software de servidor web –configurado para interactuar con el intérprete de PHP– que soporte el protocolo HTTP y que en nuestro caso será el denominado servidor Apache. � – El intérprete de PHP. � – Un software de servidor de bases de datos capaz de ser gestionado mediante funciones propias de PHP. Podemos utilizar el servidor de bases de datos MySQL o su «fork» MariaDB (¡me gustó eso del fork!) y también podrás encontrar referencias al uso deSQLite que en muchos casos puede resultar una alternativa más ligera y accesible.

PHP y HTML

Páginas PHP

Las páginas PHP pueden ser páginas web normales a las que únicamente se les cambia la extensión tradicional (.htm ó .html) , por la extensión .php. Observemos este código fuente. Como verás, se trata de una página web muy simple que no contiene ningún script PHP.

<html>

Page 67: Manual PHP

<head> <title>Aprendiendo PHP</title></head> <body> Esta es una página supersimple </body> </html>

Hemos guardado esa página con el nombre ejemplo6.html y luego la hemos vuelto a guardar –sin modificar nada en sus contenidos– como ejemplo6.php. Observa que al visualizarlas no hay diferencia alguna entre ellas.

Ver ejemplo6.html Ver ejemplo6.php

Un poco de sintaxis

En una página cuyo nombre tenga por extensión .php se pueden insertar instrucciones –escritas en lenguaje PHP– anteponiendo <?php a la primera instrucción y escribiendo ?> después de la última. A cada uno de estos bloques de instrucciones le llamaremos un script. No existe límite en cuanto al número de scripts distintos que pueden insertarse dentro de una página.

La primera instrucción PHP que conoceremos será esta: echo "un texto...";.

La instrucción echo seguida de un texto entrecomillado hará que el PHP escriba en la página web resultante lo contenido en esa cadena de texto. Al final de cada instrucción debemos insertar siempre un punto y coma (;). Este signo (;) indicará a PHP que lo que viene a continuación es una nueva instrucción.

Para facilitar la depuración de los scripts no suelen escribirse dos instrucciones en una misma línea.

print "un texto ...";

La instrucción print tiene una función similar –no es exactamente la misma– a la descrita para echo. Más adelante veremos algunas de sus diferencias.

print ("un texto...");

Esta es otra manera –la más habitual– de utilizar print. Ahora encerramos entre paréntesis la cadena que pretendemos que aparezca impresa en la página web. El hecho de que utilicemos paréntesis no evita la obligación de encerrar entre comillas el texto (texto) que deseamos imprimir .

Comillas dentro de comillas

Existen dos tipos de comillas: dobles « " » (SHIFT+2) y sencillas « ' » (tecla ? en minúsculas). Cuando es preciso anidar comillasdeben utilizarse tipos distintos para las exteriores y para las interiores.

Para que una instrucción echo o print interprete unas comillas como texto –y no como un delimitador de la cadena– es necesarioanteponerles un signo de barra invertida(\). En argot suele decirse escaparlas. En ningún caso –ni con echo ni con print– está permitido sustituir las comillas exteriores (las que encierran la cadena) por \". Esta sintaxis solo es válida para indicar a PHP que debe interpretar las comillas como un carácter más.

En la página siguiente veremos las diferentes opciones de las líneas de comentarios. Al realizar el ejercicio que te proponemos no es necesario que pongas los comentarios del ejemplo.

Los primeros script PHP

Editemos la página anterior (ejemplo6.php) y añadámosle nuestra primera etiqueta PHP guardándola como ejemplo7.php. Este sería el código fuente:

Page 68: Manual PHP

<html> <head> <title>Aprendiendo PHP</title></head> <body> Esta es una página supersimple <?php echo "¿Aparecerá esta linea?"; ?> </body> </html>

ejemplo7.php

Veamos ahora un ejemplo con las diferentes opciones de uso de las comillas

<html> <head> <title>Aprendiendo PHP</title></head> <body> <?php /* Las instrucciones PHP son las que aparecen en rojo. Las etiquetas en azul intenso son el código HTML. Todo lo que aparece en este color son líneas de comentario de las que hablaremos más adelante Cuando rescribas estos primeros scripts bastará que incluyas las instrucciones escritas en rojo */ /* ponemos <br> al final del texto para que cuando se ejecute cada una de las instrucciones echo se escriba -además del texto- un salto de linea HTML. De este modo, el resultado de cada ECHO aparecerá en una línea diferente */ # aquí utilizamos solo unas comillas echo "Este texto solo lleva las comillas de la instrucción<br>"; # aquí anidaremos comillas de distinto tipo echo "La palabra 'comillas' aparecerá entrecomillada<br>"; # esta es otra posibilidad invirtiendo el orden de las comillas echo 'La palabra "comillas" aparecerá entrecomillada<br>'; # una tercera posibilidad en la que utilizamos un mismo # tipo de comillas. Para diferenciar unas de otras anteponemos # la barra invertida, pero esta opción no podríamos utilizarla # al revés. # No podríamos poner \" en las comillas exteriores. echo "La palabra \"comillas\" usando la barra invertida<br>"; ?> </body> </html>

Ver ejemplo8.php

Líneas de comentario

¿Por qué usar líneas de comentario?

A primera vista pueden parecer inútiles. ¿Para qué recargar las páginas con contenidos que no se van a ver ni ejecutar?. Las líneas de comentario sirven para poder recordar en un futuro qué es lo que hemos hecho al escribir un script y por qué razón lo hemos hecho así.

Page 69: Manual PHP

A medida que vayamos avanzando verás que en muchos casos tendremos que aplicar estrategias individuales para resolver cada problema concreto. Cuando necesites hacer una corrección o una modificación al cabo de un tiempo verás que confiar en la memoria no es una buena opción. Es mucho mejor utilizar una línea de comentario que confiar en la memoria. ¡Palabra!

Comentarios

Para insertar comentarios en los scripts de PHP podemos optar entre varios métodos y varias posibilidades:

• Una sola linea

Basta colocar los símbolos // al comienzo de la línea o detrás del punto y coma que señala el final de una instrucción. También se puede usar el símbolo # en cualquiera de las dos posiciones.

• Varias líneas

Si un comentario va a ocupar más de una línea podremos escribir /* al comienzo de la primera de ellas y */ al final de la última. Las líneas intermedias no requieren de ningún tipo de marca. Los comentarios para los que usemos la forma /* ... */ no pueden anidarse. Si, por error, lo hiciéramos PHP nos dará un mensaje de error.

Ensayando líneas de comentario

En este ejemplo hemos incluido –marcados en rojo– algunos ejemplos de inserción de líneas de comentario.

<html> <head> <title>Ejemplo 9</title></head> <body> <?php // Este comentario no se verá en la página echo "Esto se leerá <BR> "; // Esto no se leerá /* Este es un comentario de múltiples líneas y no se acabará hasta que no cerremos así.... */ echo "Este es el segundo comentario que se leerá<BR>"; # Este es un comentario tipo shell que tampoco se leerá # Este, tampoco echo ("Aquí el tercer texto visible"); #comentario invisible /* Cuidado con anidar /* comentarios multilinea con estos*/ al PHP no le gustan */ ?> </body> </html>

Ver ejemplo9.php Ver ejemplo10.php

Ejecutemos los dos ejemplos. En el caso del ejemplo9 hemos mantenido el código exactamente como se muestra aquí arriba. Como consecuencia de haber anidado comentarios nos dará un error al ejecutarlo. Esto es una muestra de la importancia que tiene cuidar estrictamente ese aspecto.

Page 70: Manual PHP

En el ejemplo10 hemos quitado el /* que va delante de «comentarios» y el */ que va después de «multilínea con estos» (marcado en magenta en el código fuente). Ya no hay comentarios anidados y ya funciona correctamente.

¡Cuidado! Si tu sistema operativo es Ubuntu es muy probable que en vez de los mensajes de error comentados en los ejemplos anteriores solo visualices una página en blanco. Ello es debido a la configuración por defecto de la directiva display_errors de php.ini. En este enlace (http://www.rinconastur.com/php/errores.php) tienes información relativa a la configuración de las directivas de error.

Constantes

Definir constantes

Una constante es un valor –un número o una cadena– que no va a ser modificado a lo largo del proceso de ejecución de los scriptsque contiene un documento. Para mayor comodidad, a cada uno de esos valores se le asigna un nombre, de modo que cuando vaya a ser utilizado baste con invocar ese nombre.

Cuando ponemos nombre a una constante se dice que definimos esa constante. En PHP las constantes se definen mediante la siguiente instrucción:

define("nombre","valor");

dónde nombre es una cadena que contiene la palabra que pretendemos asignar como tal nombre y valor el contenido que pretendemos asignar a esa constante. El valor debe escribirse entre comillas, salvo que se trate de una constantes numérica que no las requiere.

Los valores asignados a las constantes se mantienen en todo el documento, incluso cuando son invocadas desde una función.

Si se realizan operaciones aritméticas con constantes tipo cadena, y su valor comienza por una letra, PHP les asigna valor cero.

Si una cadena empieza por uno o varios caracteres numéricos, al tratar de operarla aritméticamente PHP considerará únicamente el valor de los dígitos anteriores a la primera letra o carácter no numérico.

El punto entre caracteres numéricos es considerado como separador de parte decimal.

Tal como puedes ver en el código fuente del ejemplo, es posible definir constantes a las que se asigne como valor el resultado de una operación aritmética.

Ampliando echo y print

Mediante una sola instrucción echo pueden presentarse de forma simultánea (en la ventana del navegador del cliente) varias cadenas de caracteres y/o constantes y variables. Basta con ponerlas una a continuación de otra utilizando una coma como separador.

La forma anterior no es la única –ni la más habitual– de enlazar elementos mediante la instrucción echo. Si en vez de utilizar la una coma usáramos un un punto (el concatenador de cadenas) conseguiríamos el mismo resultado.

Cuando enlacemos elementos distintos –cadenas, constantes y/o números– hemos de tener muy en cuenta lo siguiente:

� – Cada una de las sucesivas cadenas debe ir encerrada entre sus propias comillas.

� – Los nombres de constantes nunca van entre comillas.

Page 71: Manual PHP

Las instrucciones print también permiten concatenar cadenas en una misma instrucción. En este caso solo es posible usar un puntocomo elemento de unión. Si pusiéramos comas –como podemos hacer con echo– PHP nos daría un error.

Un ejemplo con constantes

<html><head><title>Constantes</title></head> <body> <?php /* Definiremos la constante EurPta y le asignaremos el valor 166.386 */ define("EurPta",166.386); /* Definiremos la constante PtaEur asignándole el valor 1/166.386 En este caso el valor de la constante es el resultado de la operación aritmética dividir 1 entre 166.386*/ define("PtaEur",1/166.386); /* Definimos la constante Cadenas y le asignamos el valor: 12Esta constante es una cadena*/ define("Cadena","12Esta constante es una cadena"); /* Definimos la constante Cadena2 y le asignamos el valor: 12.54Constante con punto decimal*/ define("Cadena2","12.54Constante con punto decimal"); /* Comprobemos los valores. Observa la nueva forma en la que utilizamos echo Lo hacemos enlazando varias cadenas separadas con punto y/o coma, según se trate de echo o de print */ echo "Valor de la constante EurPta: ", EurPta, "<BR>"; echo "Valor de la constante PtaEur: ". PtaEur . "<BR>"; print "Valor de la constante Cadena: " . Cadena . "<BR>"; print "Valor de la constante Cadena x EurPta: " . Cadena*EurPta ."<br>"; print "Valor de la constante Cadena2 x EurPta: " . Cadena2*EurPta ."<br>"; echo "Con echo los números no necesitan ir entre comillas: " ,3,"<br>"; print "En el caso de print si son necesarias: " . "7" . "<br>"; print ("incluso entre paréntesis necesitan las comillas: "."45"."<br>"); print "Solo hay una excepción en el caso de print. "; print "Si los números van en un print independiente no necesitan comillas "; print 23; # Pondremos la etiqueta de cierre del script y escribiremos # una línea de código HTML ?> <br>Ahora veremos los mismos resultados usando la function prueba<br><br> <?php # Estamos dentro de un nuevo script abierto por el <? anterior /* Aunque aún no la hemos estudiado, escribiremos una función a la que (tenemos que ponerle siempre un nombre) vamos a llamar prueba() Lo señalado en rojo es la forma de indicar el comienzo y el final de la funcion Lo marcado en azul son las instrucciones que deben ejecutarse cuando la función prueba() sea invocada */ function prueba(){ echo "Valor de la constante EurPta: ". EurPta . "<BR>";

Page 72: Manual PHP

print "Valor de la constante PtaEur: ". PtaEur. "<BR>"; echo "Valor de la constante Cadena: ", Cadena , "<BR>"; print ("Valor de la constante Cadena x EurPta: " . Cadena*EurPta . "<br>"); print ("Valor de la constante Cadena2 x EurPta: " . Cadena2*EurPta . "<br>"); } # Las funciones solo se ejecutan cuando son invocadas /* La función anterior no se ejecutará hasta que escribamos una línea –como esta de abajo– en la que ponemos únicamente el nombre de la función: prueba() */ ?> <?php prueba(); ?> </body> </html>

Ver ejemplo11.php

¡Cuidado! Presta mucha atención a la sintaxis. Olvidar los «;» o no poner unas comillas suelen ser la causa de muchos mensajes de error.

Variables

¿Qué es una variable?

Podríamos decir que una variable es el espacio de la memoria RAM del ordenador que se reserva –a lo largo del tiempo de ejecución de un script– para almacenar un determinado tipo de datos cuyos valores son susceptibles de ser modificados por medio de las instrucciones contenidas en el propio programa.

En PHP todos los nombres de variable tienen que empezar por el símbolo $.y han de llevar una letra inmediatamente después del símbolo $ ($pepe1 es un nombre válido, pero $1pepe no lo es).

Para PHP las letras mayúsculas y las minúsculas son distintas. La variable $pepe es distinta de $Pepe.

Definición y tipos de variables

A diferencia de otros lenguajes de programación, PHP no requiere una definición previa de las variables. Se pueden definen en el momento en que son necesarias y para ello basta que se les asigne un valor por medio de una expresión como esta:

$variable=valor;

donde valor puede ser una cadena (texto, texto y números, o números que no requieren ser operados matemáticamente) o sólo unnúmero. En el primero de los casos habría que escribirlo entre comillas.

En PHP tampoco es necesario definir el tipo de variable, por lo tanto, una misma variable puede contener una cadena de caracteresen un momento del proceso y, posteriormente, un valor numérico susceptible de ser operado matemáticamente.

¡Cuidado! En algunas configuraciones de PHP el uso de variables no definidas previamente puede dar lugar a mensajes de error como el que puedes observar si ejecutas el ejemplo12.php desde PHP 5.3.6 utilizando la configuración explicada en

Page 73: Manual PHP

páginas anteriores. El mensaje se debe a la forma en la que está configuradas la directiva relativa a los mensajes de error cuya explicación puedes ver en este enlace. Ese mensaje de error puede evitarse de la forma que hemos hecho en el ejemplo14.php o modificando la directiva error_reporting o display_errors tal como se comenta aquí

Ámbito de las variables

Los valores de una variable definida en cualquier parte de un script –siempre que no sea dentro de una función– pueden ser utilizados desde cualquier otra parte de ese script, excepto desde dentro de las funciones que contuviera el propio script o desde las que pudieran estar contenidas en un fichero externo.

Si una variable es definida dentro de una función sólo podrá ser utilizada dentro esa función.

Si en una función aludimos a una variable externa a ella, PHP considerará esa llamada como si la variable tuviera valor cero (en caso de ser tratada como número) o una cadena vacía ("" es una cadena vacía).

Igual ocurriría si desde fuera de una función hiciéramos alusión a una variable definida en ella.

Si definimos dos variables con el mismo nombre, una dentro de una función y otra fuera, PHP las considerará distintas. La función utilizará –cuando sea ejecutada– sus propios valores sin que sus resultados modifiquen la variable externa.

Variables globales

Lo comentado anteriormente, admite algunas excepciones. Las funciones pueden utilizar valores de variables externas a ellas pero ello requiere incluir dentro de la propia función la siguiente instrucción:

global nombre de la variable;

Por ejemplo: global $a1; permitiría utilizar el valor preexistente de esa variable dentro de una función. Las eventuales modificaciones que pudiera producirle la función mantendrían su nuevo valor después de finalizar la ejecución de la función.

En una instrucción global pueden definirse como tales, de forma simultánea, varias variables. Basta con escribir los nombres de cada una de ellas separados por comas. Por ejemplo: global $a1, $a2, $a3;

Variables superglobales

A partir de la versión 4.1.0 de PHP se ha creado un nuevo tipo de variables capaces de comportarse como globales sin necesidad de que se definan como tales. Estas variables que no pueden ser creadas por usuario, recogen de forma automática información muy específicay tienen nombres preasignados que no pueden modificarse.

Las estudiaremos un poco más adelante. Por ahora, sólo citar los nombres de algunas de ellas: $_SERVER, $_POST, $_GET, $_ENVo $_SESSION son los de algunas de las más importantes.

Practicando con variables y sus ámbitos

El código fuente que ves aquí debajo corresponde al ejemplo12.php. Si pulsas en el enlace que hay debajo y ejecutas el script verás que aparece un mensaje de error que dice: Notice: Undefined variable: pepe ... que parece contradictorio con lo afirmado más arriba respecto a que PHP no requiere una definición previa de variables. Esa afirmación es cierta. Lo que ocurre es que PHP no está dando un mensaje de error propiamente dicho (NOTICE es una simple advertencia con un significado distinto a ERROR ) sino que advierte de que no se ha hecho algo habitual en una gran cantidad de lenguajes.

Hay dos formas de evitar ese mensaje. La primera de ellas la comentamos en el propio código fuente sería definir las variables antes de usarlas (siguiendo la metodología más generalizada de programación). La segunda de las opciones sería modificar el fichero php.ini cambiando el valor actual: error_reporting = E_ALL | E_STRICT por error_reporting = E_ALL &

Page 74: Manual PHP

~E_NOTICE. De esta forma, (al anteponer ~aE_NOTICE) estaremos indicando a PHP , tal como explicamos aquí, que desactive este tipo de mensajes de advertencia.

<html> <head> </head> <body> <?php /* En ejemplo12.php la linea siguiente a este comentario($pepe="" ) estará marcada con líneas de comentario mientras que en ejemplo13.php y ejemplo14.php aparecerá tal como la ves aquí */ $pepe=""; # Definimos las variables $Pepe y $Pepa (ojo con mayúsculas y minúsculas) $Pepe="Me llamo Pepe y soy serio y formal"; $Pepa="Me llamo Pepa y también soy seria y formal"; ?> <!-- esto es HTML, hemos cerrado el script --> <center><b>Vamos a ver el contenido de las variables</b></center> <!-- un nuevo script PHP --> <?php echo "<br> El valor de la variable $pepe es: ",$pepe; echo "<br> No ha puesto nada porque $pepe esta vacía"; echo "<br> El valor de la variable Pepe es: ",$Pepe; ?> <center><b><br>Invocando la variable desde una función</b></center> <?php /* Escribiremos una function llamada vervariable Observa la sintaxis. La palabra function delante y el () al final seguidos de la llave. Hasta que no cerremos la llave todas las líneas serán consideradas parte de la función */ function vervariable(){ // $Pepe=""; /* si no inicializamos la variable $Pepe nos puede aparecer el mensaje de error que comentamos al margen y que veremos al ejecutar este script tanto desde el enlace ejemplo12.php como desde el ejemplo13.php En el caso del ejemplo14.php, donde ya no aparece el error lo único que hemos hecho ha sido precisamente inicializar la variable $Pepe asignándole un valor nulo tal como puedes ver en la línea anterior (marcado en rojo) y comentada con // En el ejemplo14.php hemos descomentar esta línea */ echo "<br> Si invoco la variable Pepe desde una función"; echo "<br>me aparecerá en blanco"; echo "<br>El valor de la variable Pepe es: ",$Pepe; } /* esta llave de arriba señala el final de la función. Los contenidos que hay en adelante ya no pertenecen a ella */ /* Haremos una llamada a la funcion vervariable. Las funciones no se ejecutan hasta que no se les ordena y se hace de esta forma que ves aquí debajo: nombre de la funcion seguido de los famosos paréntesis */

Page 75: Manual PHP

vervariable(); ?> <!-- mas HTML puro --> <center><b><br>Ver la variable desde la función poniendo <i>global</i></b></center> <?php # una nueva funcion function ahorasi(){ # aqui definiremos a $Pepe como global # la función leerá su valor externo global $Pepe; echo "<br><br> Hemos asignado ámbito global a la variable"; echo "<br>ahora Pepe aparecerá"; echo "<br>El valor de la variable Pepe es: ", $Pepe; } # hemos cerrado ya la funcion con la llave. # Tendremos que invocarla para que se ejecute ahora ahorasi(); ?> <center><b><br>Un solo nombre y dos <i>variables distintas</i></b><br> Dentro de la función el valor de la variable es <br></center> <?php function cambiaPepa(){ $Pepa="Ahora voy a llamarme Luisa por un ratito"; echo "<br>",$Pepa; } cambiaPepa(); ?> <center>... pero después de salir de la función vuelvo al valor original...</center> <?php echo "<br>",$Pepa; ?> </body> </html>

Ver ejemplo12.php Ver ejemplo13.php Ver ejemplo14.php

Constantes predefinidas

Constantes predefinidas

PHP dispone de algunas constantes predefinidas que no requieren la instrucción: define("Nombre","Valor") ya que utilizan palabras reservadas y se asignan valor de forma automática. Algunas de ellas son estas:

__FILE__

Page 76: Manual PHP

Recoge el nombre del fichero que se está ejecutando y la ruta completa de su ubicación en el servidor. Su nombre lleva dos guiones bajos ( __ ) delante de FILE y otros dos detrás.

__LINE__

Recoge el número de la línea actual (incluidas las líneas en blanco) del fichero PHP cuyos scripts está interpretando. Puede resultar muy útil para depurar programas.

PHP_OS

Recoge información sobre el sistema operativo que utiliza el servidor en el que se está interpretando el fichero.

PHP_VERSION

Recoge la versión de PHP que está siendo utilizada por el servidor.

¡Cuidado! Por si existieran dudas –por problemas de visualización– tanto FILE como LINE tienen que llevar dos guiones bajosdelante y otras dos detrás.

Un ejemplo con constantes predefinidas

<html> <head> </head> <body> <?php # La constante del sistema __FILE__ nos devolverá echo "La ruta completa de este fichero es: "; echo __FILE__; # La constante del sistema __LINE__ nos devolverá # el número de línea que se está interpretando # también cuenta las líneas en blanco # cuenta las líneas y verás que devuelve ... 16 echo "<br>Esta es la línea: ",__LINE__, " del fichero"; echo "<br>Estamos utilizando la versión: ",PHP_VERSION, " de PHP"; echo "<br>El PHP se está ejecutando desde el sistema operativo: ",PHP_OS; ?> </body> </html>

Ver ejemplo15.php

Page 77: Manual PHP

Variables predefinidas

Las tablas de valores

En las tablas que hay al final de esta página podrás ver los valores actuales –clasificados por tipos– de algunas de las variables predefinidas de PHP. Esta información (variables y valores) está siendo extraida de la configuración de tu servidor y de este documento y se trata de variables de tipo superglobal tal como lo definíamos en esta página.

Probablemente te extrañará –justificadamente– la longitud y la estructura un tanto rara de estos nombres de variables. Cuando tratemos el tema de los arrays asociativos veremos que esa es la sintaxis habitual de ese tipo de variables.

Las nombres de las variables de cada uno de los tipos, sólo se diferencian en lo contenido entre los corchetes porque se trata dedistintos elementos del mismo array asociativo y –tal como veremos– esa es la sintaxis típica de los array.

No vamos a agobiarte con una enumeración de variables y contenidos, pero a poco que observes las tablas de valores te darás cuenta de que es muy abundante y muy importante la información que recogen. Si analizas las variables de servidor te darás cuenta de que aparece un montón de información relativa a su configuración: nombre, rutas, nombres de páginas, IP del servidor, etcétera. Con los demás tipos ocurre algo similar.

Los distintos tipos

Veamos los diferentes tipos de variables predefinidas que existen en PHP. Por ahora, no te preocupes demasiado sobre la forma de utilizarlas. Las incluimos aquí como una simple enumeración y con una breve descripción de su utilidad. En temas posteriores haremos referencia a ellas. Por el momento nos bastará con conocer su existencia.

Estamos viendo los valores de las variables de de entorno (ENV) y las de servidor (SERVER), pero, además de ellas, existen algunas otras cuyos nombres y utilidades vamos a comentarte.

Variables de sesión

Las identificaremos por el nombre $_SESSION.

Este tipo de variables las utilizaremos cuando hagamos mención al uso de sesiones. La utilización de sesiones –ya abundaremos en ello– es una forma de recoger, de forma temporal en un documento del mismo carácter, información específica generada a través de los accesos de cada uno de los usuarios. Por ejemplo, cuando accedes a una cuenta de correo web y escribes tu clave y contraseña se crea un documento temporal en el servidor de correo con un número único y exclusivo para ese acceso –identificador de sesión– que te permite acceder a diferentes apartados sin necesidad de que reescribas, en cada una de las páginas, esos mismos valores.

Habrás comprobado también, más de una vez, su carácter efímero cuando al actualizar una página te ha aparecido ese mensaje típico que dice la sesion ha caducado.

Variables de los métodos GET y POST

Las identificaremos por los nombres $_GET y $_POST

Este tipo de variables –que utilizaremos con frecuencia– recogen la información que se envía desde el cliente para ser utilizada por el servidor. Recuerda el carácter dinámico de PHP y que ese dinamismo (interacción cliente – servidor) requiere que el servidor guarde y/o interprete los datos remitidos por el cliente. La existencia de los dos tipos se justifica porque también existen dos tipos de métodos(maneras) de enviar datos desde el cliente hasta el servidor. Cuando el método de envío es el llamado GET los datos se recogen en variables de este tipo, y, por el contrario, si ese método envío fuera POST se recogerían en aquellas.

Variables de transferencia de ficheros

Las identificaremos por el nombre $_FILES. Cuando se trata de la transferencia de un fichero desde el cliente hasta el servidor –«subir un fichero»– es necesario utilizar un procedimiento de envío distinto de los comentados en el párrafo anterior. Será en este caso cuando se utilicen variables de este tipo.

El tipo GLOBALS

Page 78: Manual PHP

A diferencia de las anteriores, las variables de este tipo, $GLOBALS,utilizan una sintaxis algo distinta ya no llevan el guión bajo detrás de $. Su finalidad es recoger en una tabla los nombres de todas la variables establecidas como globales –en cada momento– así como sus valores. Si observas la tabla que tienes al final de esta página quizá te sorprenda leer nombre o página. ¿De donde han salido esos valores?. Bueno... en esta página utilizamos scripts PHP y esos son los nombres de unas variables que hemos incluido en ellos.

Conocida la existencia de los diferentes tipos de variables predefinidas y vista esta tabla –a modo de ejemplo de su utilidad– no será preciso que profundicemos más en el asunto. Lo trataremos en el momento en el que tengamos que hacer uso de cada una de ellas.

Variables de servidor

$_SERVER['UNIQUE_ID']

il4An0g0jIEAAHqC@fQAAAAO

$_SERVER['HTTP_HOST']

www.rinconastur.com

$_SERVER['HTTP_CONNECTION']

keep-alive

$_SERVER['HTTP_USER_AGENT']

Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5

$_SERVER['HTTP_ACCEPT']

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q= 0.8

$_SERVER['HTTP_REFERER']

http://www.rinconastur.com/php/php29.php

$_SERVER['HTTP_ACCEPT_ENCODING']

gzip,deflate,sdch

$_SERVER['HTTP_ACCEPT_LANGUAGE']

es-ES,es;q=0.8

$_SERVER['HTTP_ACCEPT_CHARSET']

ISO-8859-1,utf-8;q=0.7,*;q=0.3

$_SERVER['HTTP_COOKIE']

PHPSESSID=ed3f35bf913ecbae9d18c30daff4f6e8

$_SERVER['PATH']

/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin

$_SERVER['SERVER_SIGNATURE']

$_SERVER['SERVER_SOFTWARE']

Apache/2.0.63 (Unix) mod_ssl/2.0.63 OpenSSL/0.9.8e-fips-rhel 5 mod_bwlimited/1.4 PHP/5.2.11

$_SERVER['SERVER_NAME']

www.rinconastur.com

$_SERVER['SERVER_ADDR']

72.52.140.129

$_SERVER['SERVER_PORT']

80

$_SERVER['REMOTE_ADDR']

190.82.98.194

$_SERVER['DOCUMENT_ROOT']

/home/rinconas/public_html

$_SERVER['SERVER_ADMIN']

[email protected]

Page 79: Manual PHP

$_SERVER['SCRIPT_FILENAME']

/home/rinconas/public_html/php/php30.php

$_SERVER['REMOTE_PORT']

33367

$_SERVER['GATEWAY_INTERFACE']

CGI/1.1

$_SERVER['SERVER_PROTOCOL']

HTTP/1.1

$_SERVER['REQUEST_METHOD']

GET

$_SERVER['QUERY_STRING']

$_SERVER['REQUEST_URI']

/php/php30.php

$_SERVER['SCRIPT_NAME']

/php/php30.php

$_SERVER['PHP_SELF']

/php/php30.php

$_SERVER['REQUEST_TIME']

1338997030

$_SERVER['argv']

$_SERVER['argc']

Variables GLOBALES

$GLOBALS['GLOBALS']['GLOBALS']

Array

$GLOBALS['GLOBALS']['_ENV']

Array

$GLOBALS['GLOBALS']['HTTP_ENV_VARS']

Array

$GLOBALS['GLOBALS']['_POST']

Array

$GLOBALS['GLOBALS']['HTTP_POST_VARS']

Array

$GLOBALS['GLOBALS']['_GET']

Array

$GLOBALS['GLOBALS']['HTTP_GET_VARS']

Array

$GLOBALS['GLOBALS']['_COOKIE']

Array

$GLOBALS['GLOBALS']['HTTP_COOKIE_VARS']

Array

$GLOBALS['GLOBALS']['_SERVER']

Array

$GLOBALS['GLOBALS']['HTTP_SERVER_VARS']

Array

Page 80: Manual PHP

$GLOBALS['GLOBALS']['_FILES']

Array

$GLOBALS['GLOBALS']['HTTP_POST_FILES']

Array

$GLOBALS['GLOBALS']['_REQUEST']

Array

$GLOBALS['GLOBALS']['xyz_DOCUMENT_ROOT']

C:/ServidoresLocales/Apache/htdocs

$GLOBALS['GLOBALS']['mi_xyz']

$GLOBALS['GLOBALS']['xyz_SERVER_ROOT']

C:/ServidoresLocales/Apache/

$GLOBALS['GLOBALS']['xyz_INSTALL']

C:/ServidoresLocales/

$GLOBALS['GLOBALS']['xyz_DIR_INSTALL']

ServidoresLocales

$GLOBALS['GLOBALS']['ver']

ver.php?URL=

$GLOBALS['GLOBALS']['ver_extension']

.php

$GLOBALS['GLOBALS']['extension']

.php

$GLOBALS['GLOBALS']['prefijo']

$GLOBALS['GLOBALS']['incluye']

general.inc.php

$GLOBALS['GLOBALS']['prefx']

$GLOBALS['GLOBALS']['prefx1']

$GLOBALS['GLOBALS']['prefx0']

$GLOBALS['GLOBALS']['opcion']

I

$GLOBALS['GLOBALS']['anterior']

php29.php

$GLOBALS['GLOBALS']['siguiente']

php31.php

$GLOBALS['GLOBALS']['clave']

Array

$GLOBALS['GLOBALS']['nombre']

argc

$GLOBALS['GLOBALS']['nombre1']

GLOBALS

$GLOBALS['GLOBALS']['valor']

GLOBALS

$GLOBALS['GLOBALS']['nombre2']

valor

$GLOBALS['_ENV']['TERM']

vt100

Page 81: Manual PHP

$GLOBALS['_ENV']['SSH_CLIENT']

80.35.161.51 52129 22

$GLOBALS['_ENV']['CPANEL_IS_CRON']

0

$GLOBALS['_ENV']['LD_LIBRARY_PATH']

/usr/local/apache/lib:

$GLOBALS['_ENV']['PATH']

/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin

$GLOBALS['_ENV']['PWD']

/usr/local/cpanel/whostmgr/docroot

$GLOBALS['_ENV']['SHLVL']

1

$GLOBALS['_ENV']['RESTARTSRV']

1

$GLOBALS['_ENV']['_']

/usr/local/apache/bin/httpd

$GLOBALS['HTTP_ENV_VARS']['TERM']

vt100

$GLOBALS['HTTP_ENV_VARS']['SSH_CLIENT']

80.35.161.51 52129 22

$GLOBALS['HTTP_ENV_VARS']['CPANEL_IS_CRON']

0

$GLOBALS['HTTP_ENV_VARS']['LD_LIBRARY_PATH']

/usr/local/apache/lib:

$GLOBALS['HTTP_ENV_VARS']['PATH']

/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin

$GLOBALS['HTTP_ENV_VARS']['PWD']

/usr/local/cpanel/whostmgr/docroot

$GLOBALS['HTTP_ENV_VARS']['SHLVL']

1

$GLOBALS['HTTP_ENV_VARS']['RESTARTSRV']

1

$GLOBALS['HTTP_ENV_VARS']['_']

/usr/local/apache/bin/httpd

$GLOBALS['_COOKIE']['PHPSESSID']

ed3f35bf913ecbae9d18c30daff4f6e8

$GLOBALS['HTTP_COOKIE_VARS']['PHPSESSID']

ed3f35bf913ecbae9d18c30daff4f6e8

$GLOBALS['_SERVER']['UNIQUE_ID']

il4An0g0jIEAAHqC@fQAAAAO

$GLOBALS['_SERVER']['HTTP_HOST']

www.rinconastur.com

$GLOBALS['_SERVER']['HTTP_CONNECTION']

keep-alive

$GLOBALS['_SERVER']['HTTP_USER_AGENT']

Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5

$GLOBALS['_SERVER']['HTTP_ACCEPT']

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

$GLOBALS['_SERVER']['HTTP_REFERER']

http://www.rinconastur.com/php/php29.php

Page 82: Manual PHP

$GLOBALS['_SERVER']['HTTP_ACCEPT_ENCODING']

gzip,deflate,sdch

$GLOBALS['_SERVER']['HTTP_ACCEPT_LANGUAGE']

es-ES,es;q=0.8

$GLOBALS['_SERVER']['HTTP_ACCEPT_CHARSET']

ISO-8859-1,utf-8;q=0.7,*;q=0.3

$GLOBALS['_SERVER']['HTTP_COOKIE']

PHPSESSID=ed3f35bf913ecbae9d18c30daff4f6e8

$GLOBALS['_SERVER']['PATH']

/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin

$GLOBALS['_SERVER']['SERVER_SIGNATURE']

$GLOBALS['_SERVER']['SERVER_SOFTWARE']

Apache/2.0.63 (Unix) mod_ssl/2.0.63 OpenSSL/0.9.8e-fips-rhel5 mod_bwlimited/1.4 PHP/5.2.11

$GLOBALS['_SERVER']['SERVER_NAME']

www.rinconastur.com

$GLOBALS['_SERVER']['SERVER_ADDR']

72.52.140.129

$GLOBALS['_SERVER']['SERVER_PORT']

80

$GLOBALS['_SERVER']['REMOTE_ADDR']

190.82.98.194

$GLOBALS['_SERVER']['DOCUMENT_ROOT']

/home/rinconas/public_html

$GLOBALS['_SERVER']['SERVER_ADMIN']

[email protected]

$GLOBALS['_SERVER']['SCRIPT_FILENAME']

/home/rinconas/public_html/php/php30.php

$GLOBALS['_SERVER']['REMOTE_PORT']

33367

$GLOBALS['_SERVER']['GATEWAY_INTERFACE']

CGI/1.1

$GLOBALS['_SERVER']['SERVER_PROTOCOL']

HTTP/1.1

$GLOBALS['_SERVER']['REQUEST_METHOD']

GET

$GLOBALS['_SERVER']['QUERY_STRING']

$GLOBALS['_SERVER']['REQUEST_URI']

/php/php30.php

$GLOBALS['_SERVER']['SCRIPT_NAME']

/php/php30.php

$GLOBALS['_SERVER']['PHP_SELF']

/php/php30.php

$GLOBALS['_SERVER']['REQUEST_TIME']

1338997030

$GLOBALS['_SERVER']['argv']

Array

$GLOBALS['_SERVER']['argc']

0

Page 83: Manual PHP

$GLOBALS['HTTP_SERVER_VARS']['UNIQUE_ID']

il4An0g0jIEAAHqC@fQAAAAO

$GLOBALS['HTTP_SERVER_VARS']['HTTP_HOST']

www.rinconastur.com

$GLOBALS['HTTP_SERVER_VARS']['HTTP_CONNECTION']

keep-alive

$GLOBALS['HTTP_SERVER_VARS']['HTTP_USER_AGENT']

Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5

$GLOBALS['HTTP_SERVER_VARS']['HTTP_ACCEPT']

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

$GLOBALS['HTTP_SERVER_VARS']['HTTP_REFERER']

http://www.rinconastur.com/php/php29.php

$GLOBALS['HTTP_SERVER_VARS']['HTTP_ACCEPT_ENCODING']

gzip,deflate,sdch

$GLOBALS['HTTP_SERVER_VARS']['HTTP_ACCEPT_LANGUAGE']

es-ES,es;q=0.8

$GLOBALS['HTTP_SERVER_VARS']['HTTP_ACCEPT_CHARSET']

ISO-8859-1,utf-8;q=0.7,*;q=0.3

$GLOBALS['HTTP_SERVER_VARS']['HTTP_COOKIE']

PHPSESSID=ed3f35bf913ecbae9d18c30daff4f6e8

$GLOBALS['HTTP_SERVER_VARS']['PATH']

/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin

$GLOBALS['HTTP_SERVER_VARS']['SERVER_SIGNATURE']

$GLOBALS['HTTP_SERVER_VARS']['SERVER_SOFTWARE']

Apache/2.0.63 (Unix) mod_ssl/2.0.63 OpenSSL/0.9.8e-fips-rhel5 mod_bwlimited/1.4 PHP/5.2.11

$GLOBALS['HTTP_SERVER_VARS']['SERVER_NAME']

www.rinconastur.com

$GLOBALS['HTTP_SERVER_VARS']['SERVER_ADDR']

72.52.140.129

$GLOBALS['HTTP_SERVER_VARS']['SERVER_PORT']

80

$GLOBALS['HTTP_SERVER_VARS']['REMOTE_ADDR']

190.82.98.194

$GLOBALS['HTTP_SERVER_VARS']['DOCUMENT_ROOT']

/home/rinconas/public_html

$GLOBALS['HTTP_SERVER_VARS']['SERVER_ADMIN']

[email protected]

$GLOBALS['HTTP_SERVER_VARS']['SCRIPT_FILENAME']

/home/rinconas/public_html/php/php30.php

$GLOBALS['HTTP_SERVER_VARS']['REMOTE_PORT']

33367

$GLOBALS['HTTP_SERVER_VARS']['GATEWAY_INTERFACE']

CGI/1.1

$GLOBALS['HTTP_SERVER_VARS']['SERVER_PROTOCOL']

HTTP/1.1

$GLOBALS['HTTP_SERVER_VARS']['REQUEST_METHOD']

GET

$GLOBALS['HTTP_SERVER_VARS']['QUERY_STRING']

Page 84: Manual PHP

$GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI']

/php/php30.php

$GLOBALS['HTTP_SERVER_VARS']['SCRIPT_NAME']

/php/php30.php

$GLOBALS['HTTP_SERVER_VARS']['PHP_SELF']

/php/php30.php

$GLOBALS['HTTP_SERVER_VARS']['REQUEST_TIME']

1338997030

$GLOBALS['HTTP_SERVER_VARS']['argv']

Array

$GLOBALS['HTTP_SERVER_VARS']['argc']

0

$GLOBALS['_REQUEST']['PHPSESSID']

ed3f35bf913ecbae9d18c30daff4f6e8

$GLOBALS['xyz_DOCUMENT_ROOT']

C:/ServidoresLocales/Apache/htdocs

$GLOBALS['mi_xyz']

$GLOBALS['xyz_SERVER_ROOT']

C:/ServidoresLocales/Apache/

$GLOBALS['xyz_INSTALL']

C:/ServidoresLocales/

$GLOBALS['xyz_DIR_INSTALL']

ServidoresLocales

$GLOBALS['ver']

ver.php?URL=

$GLOBALS['ver_extension']

.php

$GLOBALS['extension']

.php

$GLOBALS['prefijo']

$GLOBALS['incluye']

general.inc.php

$GLOBALS['prefx']

$GLOBALS['prefx1']

$GLOBALS['prefx0']

$GLOBALS['opcion']

I

$GLOBALS['anterior']

php29.php

$GLOBALS['siguiente']

php31.php

$GLOBALS['clave']

php31.php

$GLOBALS['nombre']

argc

Page 85: Manual PHP

$GLOBALS['nombre1']

nombre

$GLOBALS['valor']

ed3f35bf913ecbae9d18c30daff4f6e8

$GLOBALS['nombre2']

PHPSESSID

Otras variables

Valores de las variables

Cuando hablábamos de las variables y nos referíamos a su ámbito decíamos que las variables definidas dentro de una función pierden sus valores en el momento en el que abandonemos el ámbito de esa función, es decir, cuando finaliza su ejecución.

Decíamos también que si el ámbito en el que hubiera sido definida fuera externo a una función los valores sólo se perderían–temporalmente– mientras durara la eventual ejecución de las instrucciones de aquella y que, una vez acabado ese proceso, volvían a recuperar sus valores.

Bajo estas condiciones, si invocáramos repetidamente la misma función obtendríamos cada vez el mismo resultado porque las posibles modificaciones que pudieran haberse producido (a través de las instrucciones contenidas en la función) en sus valores se perderían cada vez que abandonáramos la función con lo cual, cada una de las llamadas sucesivas restablecería el valor inicial.

Variables estáticas

Para poder conservar el último valor de una variable definida dentro de una función basta con definirla como estática. La instrucción que permite establecer una variable como estática es la siguiente:

static nombre = valor;

Por ejemplo: si la variable fuera $a y el valor inicial asignado fuera 3 escribiríamos: static $a=3;

La variable conservará el último de los valores que pudo habérsele asignado durante la ejecución de la función que la contiene. No retomará el valor inicial hasta que se actualice la página.

Ejemplo de variables estáticas

<?php # Observa que hemos prescindido de los encabezados HTML. # No son imprescindibles para la ejecución de los scripts /* Escribamos una función y llamémosla sinEstaticas Definamos en ella dos variables sin ninguna otra especificación e insertemos las instrucciones para que al ejecutarse se escriban los valores de esas variables */ function sinEstaticas(){ # Pongamos aquí sus valores iniciales $a=0; $b=0; # Imprimamos estos valores iniciales echo "Valor inicial de $a: ",$a,"<br>"; echo "Valor inicial de $b: ",$b,"<br>";

Page 86: Manual PHP

/* Modifiquemos esos valores sumando 5 al valor de $a y restando 7 al valor de $b. $a +=5 y $b -=7 serán quienes haga esas nuevas asignaciones de valor ya lo iremos viendo, no te preocupes */ $a +=5; $b -=7; # Visualicemos los nuevos valores de las variables echo "Nuevo valor de $a: ",$a,"<br>"; echo "Nuevo valor de $b: ",$b,"<br>"; } # Escribamos ahora la misma función con una modificación que será # asignar la condición de estática a la variable $b # Llamemos a esa función: conEstaticas function conEstaticas(){ # Definimos $b como estática $a=0; static $b=0; echo "Valor inicial de $a: ",$a,"<br>"; echo "Valor inicial de $b: ",$b,"<br>"; $a +=5; $b -=7; echo "Nuevo valor de $a: ",$a,"<br>"; echo "Nuevo valor de $b: ",$b,"<br>"; } # Insertemos un texto que nos ayude en el momento de la ejecución print ("Esta es la primera llamada a sinEstaticas()<br>"); # Invoquemos la función sinEstaticas; sinEstaticas(); # Añadamos un nuevo comentario a la salida print ("Esta es la segunda llamada sinEstaticas()<br>"); print ("Debe dar el mismo resultado que la llamada anterior<br>"); # Invoquemos por segunda vez sinEstaticas; sinEstaticas(); # Hagamos ahora lo mismo con la función conEstaticas print ("Esta es la primera llamada a conEstaticas()<br>"); conEstaticas(); print ("Esta es la segunda llamada a conEstaticas()<br>"); print ("El resultado es distinto a la llamada anterior<br>"); conEstaticas(); ?>

Page 87: Manual PHP

ejemplo16.php

Variables de variables

Además del método habitual de asignación de nombres a las variables -poner el signo $ delante de una palabra-, existe la posibilidad de que tomen como nombre el valor de otra variable previamente definida.

La forma de hacerlo sería esta: $$nombre_variable_previa;

Veamos un ejemplo. Supongamos que tenemos una variable como esta: $color="verde";. Si ahora queremos definir una nueva variable que utilice como nombre el valor (verde) que está contenido en la variable previa ($color), habríamos de poner algo como esto:

$$color="es horrible";

Si se tratara de visualizar el valor de esta nueva variable podríamos hacerlo de una de estas tres maneras:

print $$color; o print ${$color}; o también print $verde;

Cualquiera de las instrucciones anteriores nos produciría la misma salida: es horrible. Podemos preguntarnos ¿cómo se justifica que existan dos sintaxis tan similares como $$color y ${$color}? ¿Qué pintan las llaves?.

La utilización de las llaves es una forma de evitar situaciones de interpretación confusa. Supongamos que las variables tienen un nombre un poco más raro. Por ejemplo que $color no se llama así sino $color[3] (podría ser que $color fuera un array –una lista de colores– y que esta variable contuviera el tercero de ellos).

En este supuesto, al escribir: print $$color[3] cabría la duda de si el número 3 pertenece (es un índice) a la variable $color o si ese número corresponde a $$color. Con print ${$color[3]} no habría lugar para esas dudas. Estaríamos aludiendo de forma inequívoca a 3 como índice de la variable $color.

¿Qué ocurre cuando la variable previa cambia de valor?

Cuando la variable utilizada para definir una variable de variable cambia de valor no se modifica ni el nombre de esta última ni tampoco su valor. Puedes ver este concepto, con un poco más de detalle, en el código fuente del ejemplo.

<?php # Definamos una variable y asignémosle un valor $color="rojo"; # Definamos ahora una nueva variable de nombre variable # usando para ello la variable anterior $$color=" es mi color preferido"; # Veamos impresos los contenidos de esas variables print ( "El color ".$color. $$color ."<br>"); #o también print ( "El color ".$color. ${$color}."<br>"); # o también print ( "El color ".$color. $rojo."<br>"); # advirtamos lo que va a ocurrir al visualizar la página print ("Las tres líneas anteriores deben decir lo mismo<br>"); print ("Hemos invocado la misma variable de tres formas diferentes<BR>");

Page 88: Manual PHP

# cambiemos ahora el nombre del color $color="magenta"; /* La variable $rojo seguirá existiendo. El hecho de cambiar el valor a $color no significa que vayan a modificarse las variables creadas con su color anterior ni que se creen automáticamente variables que tengan por nombre el nuevo valor de $color */ # Pongamos un mensaje de advertencia para que sea visualizado en la salida print ("Ahora la variable $color ha cambiado a magenta<br>"); print ("pero como no hemos creado ninguna variable con ese color<br>"); print ("en las lineas siguientes no aparecerá nada <br>"); print ("detrás de la palabra magenta <br>"); print ("solo un mensaje de error advirtiendo que la variable no existe<br>"); /* para evitar ese mensaje de error basta con anteponer el símbolo @ a las dos instrucciones print siguientes # Escribimos los print advertidos @print (" El color ".$color.$$color."<br>"); @print (" El color ".$color.${$color}."<br>"); # Comprobemos que la variable $rojo creada como variable de variable # cuando $color="rojo" aún existe y mantiene aquel valor print ("Pese a que $color vale ahora ".$color."<br>"); print ("la vieja variable $rojo sigue existiendo <br>"); print ("y conserva su valor. Es este: ".$rojo); ?>

ejemplo17.php Con mensaje de error

ejemplo18.php Sin mensajes de error

Inserción de símbolos como elementos de texto

Tanto PHP como los navegadores hacen interpretaciones como símbolos del lenguaje de algunos caracteres especiales tales como$, ", o <, que cuando se pretendieran incluir como elementos no se visualizarían en pantalla.

Cuando pretendamos que aparezcan escritos tendremos que indicarlo de una forma especial. En este enlace podrás ver la forma de hacerlo.

Tipos de variables

Tipos de variables

Ya hemos comentado que en PHP no es necesaria una definición previa del tipo de variables. Según los valores que se les vayan asignando, las variables podrán cambiar de tipo –de modo automático– y se irán adaptando a los valores que contengan en cada momento.

Las variables en PHP pueden ser de tres tipos:

� – Enteras (tipo Integer)

� – De coma flotante (tipo Double)

� – Cadenas (tipo String)

Page 89: Manual PHP

Cualquier número entero cuyo valor esté comprendido entre ± 231 será interpretado por PHP como de tipo Integer.

Si el valor de una variable es un número decimal o –siendo entero– desborda el intervalo anterior, bien por asignación directa o como resultado de una operación aritmética, PHP la convierte a tipo Double.

Cualquier variable a la que se le asigne como valor el contenido de una cadena de caracteres (letras y/o números delimitados por comillas) es interpretada por PHP como tipo String.

En el cuadro siguiente podemos ver los tres tipos de variables que utiliza PHP.

Las variables en PHP

Tipo Ejemplo Valor máximo Valor mínimo Observaciones

Integer $a=1234 2147483647 -2147483647 Cualquier valor numérico entero (dentro de este intervalo) que se asigne a una variable será convertido a este tipo

Double $a=1.23 Cualquier valor numérico decimal, o entero fuera del intervalo anterior, que se asigne a una variable la convertirá a este tipo

String $a="123" Cualquier valor entrecomillado (sean números o letras) que se asigne a una variable la convertirá a este tipo

Determinación de tipos de variables

PHP dispone de la función:

gettype(variable)

dónde variable es el nombre de la variable, devuelve una cadena de caracteres indicando el tipo de la variable que contiene.La cadena devuelta por esta función puede ser: Integer, double o string.

Dado que PHP gestiona las variables de forma automática y modifica los tipos de acuerdo con los valores que va tomando durante la ejecución del script, este valor puede ser distinto según el punto del script dónde se ejecute la función.

En esta tabla tienes algunos ejemplos de aplicación de esa función. Puedes observar –en la columna Sintaxis– que para visualizar el resultado anteponemos echo a gettype. Es decir, le indicamos a PHP que muestre el resultado obtenido al determinar el tipo de variable.

Ejemplos de determinación del tipo de una variable

Variable Sintaxis Devuelve

$a1=347 echo gettype($a1) integer

$a2=2147483647 echo gettype($a2) integer

$a3=-2147483647 echo gettype($a3) integer

$a4=23.7678 echo gettype($a4) double

$a5=3.1416 echo gettype($a5) double

$a6="347" echo gettype($a6) string

$a7="3.1416" echo gettype($a7) string

$a8="Solo literal" echo gettype($a8) string

$a9="12.3 Literal con número" echo gettype($a9) string

$a10="" echo gettype($a10) string

Forzado de tipos de variable

PHP permite forzar los tipos de las variables. Eso quiere decir que se puede obligar a PHP a asignar un tipo determinado a una variable determinada, siempre que los valores que contenga estén dentro del rango del nuevo tipo de variable.

Los tipos se pueden forzar tanto en el momento de definir la variable como en un momento posterior.

Page 90: Manual PHP

Forzado y asignación simultánea de valores

Al asignar un valor a una variable, se puede forzar su tipo de la siguiente forma. Si deseamos que la variable pase a ser tipo de doublebasta con anteponer a su valor –entre paréntesis– tal como se indica una de las expresiones siguientes:

(double), (real) o (float).

Por ejemplo, tanto con $a=((double)45); como con $a=((float)45); o con $a=((real)45); se produciría el mismo resultado: convertir la variable $a a tipo Double.

Para forzar una variable a tipo Integer podemos anteponer a su valor una de estas expresiones:

(integer), o (int).

Por ejemplo: $b=((integer)4.5); o $b=((int)45); producirían el mismo efecto: convertir la variable $b a tipo Integer.

Para forzar una variable a tipo String basta con anteponer a su valor (entre paréntesis):

(string).

Por ejemplo: $c=((string)4.5); convertiría la variable $c a tipo String.

Forzado de tipo de variable

Variable Sintaxis Devuelve

$a1=347 echo gettype((real)$a1) double

$a2=2147483647 echo gettype((double)$a2) double

$a3=-2147483647 echo gettype((float)$a3) double

$a4=23.7678 echo gettype((int)$a4) integer

$a5=3.1416 echo gettype((integer)$a5) integer

$a6="347" echo gettype((double)$a6) double

$a7="3.1416" echo gettype((int)$a7) integer

$a7="3.1416" echo gettype((string)$a7) string

$a8="Solo literal" echo gettype((double)$a8) double

$a9="12.3 Literal con número" echo gettype((int)$a9) integer

¡Cuidado! Al modificar los tipos de variables pueden modificarse sus valores. Si forzamos a entera una variable que contenga un número decimal se perdería la parte decimal y la variable modificada solo contendría el valor de la parte entera. Si tratamos de convertir a numérica una variable alfanumérica el nuevo valor sería cero.

Nuevos valores de la variable

Valor inicial Sintaxis Nuevo valor

$a1=347 echo ((real)$a1) 347

$a2=2147483647 echo ((double)$a2) 2147483647

$a3=-2147483647 echo ((float)$a3) -2147483647

$a4=23.7678 echo ((integer)$a5) 23

$a5="3.1416" echo ((double)$a6) 3.1416

$a6="347" echo ((int)$a7) 347

$a7="3.1416" echo ((string)$a7) 3.1416

$a8="Solo literal" echo ((int)$a8) 0

$a9="12.3 Literal con número" echo ((double)$a9) 12.3

Page 91: Manual PHP

$a9="12.3 Literal con número" echo ((int)$a9) 12

$a10="" echo ((int)$a10) 0

Forzado de tipos en variables ya definidas

La forma más aconsejable de forzado de tipos en variables que ya estuvieran definidas previamente, es el uso de la siguiente instrucción:

settype( var, tipo)

donde var es el nombre de la variable cuyo tipo pretendemos modificar y tipo una expresión que puede contener (entre comillas) uno de estos valores: 'double', 'integer', o 'string' según se trate de forzar a: coma flotante, entero, o cadena.

Un ejemplo podría ser este: settype($a,'integer') que convertiría a tipo entero la variable $a.

La ejecución de la instrucción settype devuelve (da como resultado) un valor que puede ser: true o false (1 ó 0) según la conversiónse haya realizado con éxito o no haya podido realizarse.

Aquí tienes algunos ejemplos del uso de esa función. La tabla está organizada en bloques de tres filas que corresponden a la ejecución de tres instrucciones y a la visualización del resultado de cada una de ellas.

El resultado de settype –primera fila– solo podrá ser 1 ó 0 según la instrucción se haya ejecutado con éxito o no haya podido realizarse.

En la segunda fila comprobamos el nuevo tipo de variable obtenida mediante la ejecución de la instrucción anterior y en la terceravisualizamos los nuevos valores de la variable, que pueden haber cambiado como consecuencia del cambio de tipo.

Forzado de tipos con settype()

Variable Sintaxis Devuelve

$a1=347

echo (settype($a1,'double') 1

echo gettype($a1) double

echo $a1 347

$a2=2147483647

echo (settype($a2,'double') 1

echo gettype($a2) double

echo $a2 2147483647

$a3=-2147483647

echo settype($a3,'double') 1

echo gettype($a3) double

echo $a3 -2147483647

$a4=23.7678

echo settype($a4,'integer') 1

echo gettype($a4) integer

echo $a4 23

$a5=3.1416

echo settype($a5,'integer') 1

echo gettype($a5) integer

echo $a5 3

$a6="347"

echo settype($a6,'double') 1

echo gettype($a6) double

echo $a6 347

$a7="3.1416"

echo settype($a7,'integer') 1

echo gettype($a7) integer

echo $a1 3

$a8="Solo literal" echo settype($a8,'double') 1

echo gettype($a8) double

Page 92: Manual PHP

echo $a8 0

$a9="12.3 Literal con número"

echo settype($a9,'integer') 1

echo gettype($a9) integer

echo $a9 12

Operaciones con distintos tipos de variables

PHP permite la realización de operaciones aritméticas con cualquiera de los tres tipos de variables y adecúa el resultado al tipo más apropiado.

En la tabla puedes ver algunos ejemplos, pero, en resumen, ocurre lo siguiente:

� – Al operar con dos enteros, si el resultado está dentro del rango de los enteros, devuelve un entero.

� – Si al operar con dos enteros el resultado desborda el rango entero, convierte su valor, de forma automática, al tipo coma flotante

� – Al operar un entero con una variable tipo coma flotante el resultado es de coma flotante.

� – Al operar con una cadena lo hace como si se tratara de un entero. Si hay caracteres numéricos al comienzo, los extrae (hasta que aparezca un punto o un carácter no numérico) y los opera como un número entero.

� – Si una cadena no comienza por un carácter numérico PHP la operará tomando su valor numérico como CERO.

La tabla siguiente contiene –en cada fila– los valores asignados a dos variables (A y B) y el resultado de la suma de ambas. A continuación se recogen los tipos de variable de cada una de ellas y el del resultado. El tipo de este último –generado por PHP– estará condicionado por el valor del resultado de cada una de las operaciones.

Resultados de operaciones y tipos de variables resultantes

Valores Tipos de variables

A B A+B A B A+B

12 16 28 integer integer integer

12 2147483647 2147483659 integer integer integer

-12 -2147483640 -2147483652 integer integer integer

12 1.2456 13.2456 integer double double

1.2456 12 13.2456 double integer double

1.2456 123.4567 124.7023 double double double

12 abc 12 integer string integer

1.2456 abc 1.2456 double string double

12 12abc 24 integer string integer

12 12.34567abc 24.34567 integer string double

1.2456 12.34567abc 13.59127 double string double

1.2456 12.3e2abc 1231.2456 double string double

abc 12abc 12 string string integer

abc 12.34567abc 12.34567 string string double

12abc 12.34567abc 24.34567 string string double

Utilizando formularios

PHP dinámico

Hasta el momento, PHP sólo nos ha servido para escribir una serie de scripts y ver los resultados de su ejecución pero aún no hemos hecho mención alguna a la manera en la que puede establecerse un diálogo (interacción) entre cliente y el servidor que, en definitiva, es la razón de ser de las páginas dinámicas.

Page 93: Manual PHP

Cuando se trata de páginas estáticas la comunicación está muy restringida. El cliente hace una petición (escribe el nombre de una página en la barra de direcciones del navegador) y el servidor se limita a devolver los contenidos de esa página. Un primer paso para mejorar es esa comunicación será que el cliente especifique algo más en su petición y que el servidor interprete esa información complementaria. Ese algo más puede incluirse en la barra de direcciones con la siguiente sintaxis:

pagina.php?nombre1=valor1&nombre2=valor2, ...

dónde pagina.php es la dirección de una página que contiene scripts PHP y dónde ? es un carácter obligatorio que indica que detrás de él se incluye más información. Esa información estará formada por parejas nombre - valor enlazadas por un signo = y separadas entre sí por el símbolo &.

De esa forma el servidor entendería la petición de la siguiente forma: Vete a la página pagina.php, mira los scripts PHP, asigna a las variables nombre1, nombre2 etcétera los valores valor1, valor2,... ejecuta los scripts con esos valores y devuélveme los resultados.

Observarás que los nombres de las variables nunca llevan el signo $ y que los valores de las variables –sean números o cadenas–tampoco se escriben nunca entre comillas.

Algunos caracteres especiales (& por ejemplo) no pueden escribirse directamente dado que se prestan a confusión dando lugar a la duda de si habrían de intepretarse como un valor como el símbolo de unión. En esos casos es necesario sustituir el carácter por su codificación URL que representa cada carácter anteponiendo el signo % al valor de su código ASCII expresando en formato hexadecimal. Eneste enlace tienes una tabla de conversión.

Se pueden incluir tantos nombre = valor como se desee. La única restricción es la longitud máxima permitida por el método GET (el utilizado en este caso) que se sitúa en torno a los 2.000 caracteres.

Recepción de datos

Cuando es recibida por el servidor la petición de un documento con extensión .php en la que tras el signo ? se incluyen una o varias parejas nombre = valor, los nombres de las variables y sus valores respectivos se incluyen siempre, de forma automática, en variables predefinidas de dos tipos:

$_GET['nombre1']=valor1, $_GET['nombre2']=valor1, etcétera, en las que nombre1, nombre2, ... coinciden exactamente con nombres especificados en las petición y los valores asignados a estas variables también coinciden con los recibidos, junto con cada nombre, a través de la petición.

$_REQUEST['nombre1']=valor1, $_REQUEST['nombre2']=valor1 recoge los mismos valores que la anterior $_GET. Esta aparente duplicidad de información tiene una funcionalidad añadida que veremos un poco más adelante

La variables anteriores tienen una sintaxis un poco engorrosa. Por eso, las versiones más recientes de PHP incluyen una nueva función que puede agregar algo de funcionalidad. Es la siguiente:

extract(array asociativo)

que crea de forma automática variables con los nombres de todos los indices del array les asigna el valor de ese elemento del array. Es decir, si hemos recibido valores como $_GET['nombre1']=7 y $_GET['nombre2']='pepe' e incluimos en el script la función extract($_GET)PHP creará de forma automática las variables $nombre1=7 y $nombre2='pepe'.

Mediante extract($_REQUEST) obtendrían los mismos resultados. En definitiva, la función extract() no hace otra cosa que crear variables PHP con idéntico nombre al indicado en la petición.

Hay configuraciones de PHP en las que no es necesario utilizar la función extract(). Cuando el fichero php.ini tiene configurada la directiva register_globals=ON las variables del tipo: $nombre1=valor1, $nombre2=valor2, etcétera se crearían de forma automática al ser recibida la petición del cliente.

¡Cuidado! La directiva register_globals ha evolucionado mucho a través de las distintas versiones de PHP. En las versiones más antiguas venía configurada, por defecto, con el valor ON. Actualmente viene configurada, también por defecto

Page 94: Manual PHP

comoOFF y se comenta que en futuras versiones de PHP su valor seguirá siendo OFF pero ya no será posible modificarla. Por ello, quizá lo más prudente escribir nuestro código pensando siempre que su configuración es OFF y además es inmodificable.

Veamos algunos ejemplos.

<?php /* Empezaremos utilizando $_GET['a'], $_GET['b' */ # Pongamos un comentario de advertencia. Recuerda que <br> # sirve para insertar un salto de línea en la salida print ("Este resultado es el que utiliza \$_GET<br>"); print ($_GET['a']." x ".$_GET['b']." = ".$_GET['a']*$_GET['b']); /* Ahora trataremos de comprobar que también podemos utilizar la superglobal $_REQUEST como $_REQUEST['a'] y $_REQUEST['b'] con iguales resultados que las anteriores */ # Un comentario para identificar el origen del resultado print("<br>El resultado siguiente ha sido generado usando \$_REQUEST <br>"); print $_REQUEST['a']." x ".$_REQUEST['b']." = "; print $_REQUEST['a']*$_REQUEST['b']; ?>

ejemplo19.php ejemplo20.php

Si pulsas el enlace del ejemplo19 verás que te aparece una retahila de mensajes de advertencia. Es lógico que así sea. El script necesita los valores de las variables $a y $b. Como no recibe nada los ha de considerar con valor cero y, a la vez, dado que PHP tiene configurada la gestión de errores de forma muy estricta nos advierte que esas variables no han sido inicializadas.

Si escribieras ejemplo19.php?a=21&b=456 todo funcionaría correctamente. No habría lugar al mensaje de advertencia porque las variables ya tienen contenido.

Para evitar la visualización de los mensajes de advertencia existen diferentes posibilidades. Además de las mencionadades cuando estudiábamos las variables –las tienes comentadas en este enlace– tenemos la opción de modificar el script anteponiendo a las instrucciones print (las que pueden dar el mensaje de error cuando se ejecuten sin asignar valores a las variables) el símbolo @. Mediante ese símbolo (@) inhibimos la aparición del mensaje de error al ejecutarse la instrucción a la que precede. Eso es lo que hemos hecho en elejemplo20. Verás que al ejecutarlo (aquí está su código fuente) ya no aparece el mensaje de error.

Como es lógico, al asignar valores a las variables escribiendo en la barra de direcciones del navegador la siguiente dirección:ejemplo20.php?a=21&b=456, (o pulsemos directamente en este enlace) el resultado no diferiría en nada del obtenido con el ejemplo19.

¡Cuidado! Aquí, más que nunca, conviene reiterar, una vez más, los errores de sintaxis más frecuentes:

� Los nombres de variables son distintos si se cambian mayúsculas y minúsculas. Pon mucho cuidado en escribirlos correctamente.

� Los nombres de las variables predefinidas, tales como $_GET, etcétera van en mayúsculas. � No olvides poner punto y coma al final de cada línea de instrucciones. � Presta atención a la apertura/cierre de comillas y mucha más atención aún si se trata de comillas anidadas. En este

caso procura usar (") para las exteriores y (') para las interiores.

Probemos otras de las opciones. Utilicemos la función extract para leer los datos recibidos.

<?php /* al incluir extract se crearán variables automáticas con los nombres incluidos a través de la barra de direcciones

Page 95: Manual PHP

del navegador */ extract($_GET); /* Escribamos una instrucción, que imprima en pantalla el valor de una de las variables extraidas ($a), una "x" que hará funciones de aspa en la presentación, otra variable ($b), el signo igual y $a*$b que es el producto de ambas variables El simbolo de multiplicar es (*) anteponemos al print el simbolo @ para evitar que aparezca mensaje de error por variable no definida */ @print ($a." x ".$b." = ".$a*$b); # Añadamos una bobadilla, para animar... ;-) print ("<br> ¡¡Acabamos de usar la función extract!!"); ?>

ejemplo21.php ejemplo22.php

Ejecuta este ejemplo (no aparecerán mensajes de advertencia porque hemos incluido los @ que los inhiben) y observa lo que aparece. ¿Sólo x =0? ¿y la otra línea?

El resultado es lógico. No hemos asignado valores a las variables $a y $b y por eso no escribió su valor. Sin embargo, a la hora de multiplicar -recuerda que una variable vacía es interpretada como cero a la hora de hacer operaciones– la interpretó como cero y –con muy buen criterio– nos respondió que cero por cero es cero.

Escribamos ahora la dirección: ejemplo21.php?a=13&b=7, (también puedes pulsar en este enlace) y podremos comprobar que 13 x 7 = 91 . Observa también que ahora no aparece ningún mensaje de error porque hemos antepuesto @ a la instrucción print.

Hagamos un nuevo experimento. Probemos ejemplo22.php?a=13&b=7 y veamos que el resultado no es el esperado. El ejemplo22 es muy similar al ejemplo21 (puedes ver aquí su código fuente) pero tiene una diferencia importante. Hemos comentado la línea que contiene la función extract. Por tanto esta ya no se ejecuta. Lo que realmente ocurre es que no lee los valores de las variables $a y $b porque para poder leerlas directamente se requería que la directiva register_globals de php.ini estuviera ON y, por defecto, esta directiva viene configurada como register_globals=Off.

Si pruebas a cambiar esa configuración, reinicias el servidor y pulsas de nuevo en el enlace, verás que esta vez si ha recibido los valores de a y b y nos ha dado el resultado de la operación.

¡Cuidado! En modo local puedes establecer las configuraciones de php.ini a tu antojo que te permite utilizar cualquiera de las opciones de transferencia de variables. Pero, si pretendes publicar tus páginas utilizando un hosting ajeno debes usar la opción de mayor compatibilidad se son, sin duda alguna, $_GET, ó $_REQUEST complementadas, si lo estimas más cómodo con la función extract. Ten en cuenta que allí no vas a poder modificar las configuraciones y de no tener en cuenta estos aspectos, puedes verte obligad@ a modificar tu código fuente para adecuarlo a la configuración de tu hosting.

Envío a través de formularios

La interacción cliente–servidor que acabamos de ver resulta incómoda en su uso y no demasiado estética.

Hay una segunda opción –la de uso más frecuente– que es la utilización de formularios que no son elementos propios de PHP –actúan del lado del cliente– siendo su estudio es más propio del ámbito de HTML que de PHP. No obstante –por si te fuera necesario– aquí tienes un formulario, en el que se comenta la sintaxis de sus elementos más comunes.

Un ejemplo formulario

<html> <head>

Page 96: Manual PHP

</head> <body> <!-- Un formulario debe empezar siempre con una etiqueta de este tipo <form ...> dentro de la cual obligatorio indicar con esta sintaxis action='nombre.extension' el nombre de la página destinataria de la información que se incluya en el formulario. nombre.extension debe contener el nombre (o la ruta completa en el caso de que estuviera en un directorio o hosting distinto del que alberga el documento que contiene el formulario desde el que se realiza la petición) Es opcional incluir (también dentro de la etiqueta <form ...>) method que puede tener dos valores method='GET' ó method='POST' Por defecto (cuando no se indica) se interpretará que method='GET' También es opcional -a los efectos de PHP- incluir name. Ese valor es útil cuando se incluyen scripts del lado del cliente del tipo JavaScript //--> <form name='mi_formulario' action='ejemplo26.php' method='post'> <!-- Pueden incluirse textos dentro del formulario --> Escribe tu nombre: <!-- Uno de los tipos de campos posibles es el tipo texto. Su sintaxis (hablamos de HTML) requiere la etiqueta <input type='text'> que indica que el contenido será texto. Esta etiqueta debe incluir obligatoriamente un name='nombre' en el que pueden usarse caracteres alfabéticos, sin tildes ni eñes y sin espacios. Salvo excepciones que veremos más adelante el nombre ha de ser único y distinto para cada elemento del formulario. También dentro de la etiqueta <input type='text'> puede incluirse value='' que puede no contener nada tal como ocurre aquí o contener el texto que, por defecto, queremos que aparezca en ese campo al cargar el formulario. También puede conteenr (es opcional) size=xx. Su utilidad es la de ajustar el tamaño de la ventana al número de caracteres que se indiquen mediante xx //--> <input type='text' name='nombre' value='' size=15><br> Escribe tu clave: <!-- <input type='password'> solo se diferencia del anterior en que en el momento de rellenarlo se sustituyen los carecteres visualizados (no el contenido) por asteriscos //--> <input type='password' name='clave' value=''><br> Elige tu color de coche favorito:<br> <!-- Los <input type='radio'> permite optar entre varios valores posibles. Habrá que repetirlos tantas veces como opciones queramos habilitar. Todos los input –correspondientes a la misma opción– deben tener el mismo nombre (name) value='xxxxx' deberá tener un valor distinto en cada uno de ellos. El valor (loquesea) de la opción marcada es que será transferido a través del formulario Si queremos que una opción aparezca marcada (por defecto)al cargar el formulario, deberemos incluir en su etiqueta la palabra checked

Page 97: Manual PHP

Los contenidos de value no se visualizan en el navegador por lo que conviene incluir una descripción de los valores después de cerrar la etiqueta de cada uno de esos input. Al enviar el formulario solo se transmite el value de la opción seleccionada //--> <input type='radio' name='color' value='Rojo'>Rojo</br> <input type='radio' checked name='color' value='Verde'>Verde</br> <input type='radio' name='color' value='Azul'>Azul</br> Elige los extras:<br> <!-- Cada uno de los <input type='checkbox'> requiere un nombre distinto (name) y un valor (value) Pueden marcarse uno, varios, todos o ninguno. Si queremos que una casilla aparezca marcada (por defecto) al cargar el formulario, deberemos incluir en su etiqueta la palabra checked Solo serán transferidos a través del formulario los nombres y valores de aquellos cuya casilla esté marcada. Los contenidos de value no se visualizan en el navegador por lo que conviene incluir una descripción de los valores después de cerrar la etiqueta de cada input //--> <input type='checkbox' name="acondicionado" value="Aire">Aire acondicionado<br> <input type='checkbox' checked name="tapiceria" value="Tapicieria">Tapiceria en piel<br> <input type='checkbox' name="llantas" value="aluminio">Llantas de aluminio<br> <br>¿Cual es el precio máximo que estarías dispuesto a pagar? <!-- La etiqueta <input type='select'> requiere un nombre y una etiqueta de cierre </select> Entre ambas -apertura y cierre- deben incluirse las diferentes opciones entre las etiquetas <option>valor<option> Al enviar el formulario se transmite lo contenido después de opción en la seleccionada salvo que dentro de la propia etiqueta opción se incluya un value. Si dentro de una etiqueta option escribimos selected será esa la que aparezca seleccionada ,por defecto, al cargarse el formulario//--> <select name="precio"> <Option>Menos de 6.000 euros</option> <Option>6.001 - 8.000 euros</option> <Option selected >8.001 - 10.000 euros</option> <Option value=11634.52>10.001 - 12.000 euros</option> <Option>12.001 - 14.000 euros</option> <Option>Más de 14.000 euros</option> </select> <!-- Las áreas de texto deben tener una etiqueta de apertura <textarea name='checkbox'> seguida de una etiqueta de cierre </textarea> Dentro de la etiqueta de apertura puede incluirse rows=xx (indicará el número de filas) cols=yy (indicará el ancho expresado en número de caracteres) y opcionalmente un value='lo que sea...' que puede contener el texto que -por defecto- pretendemos que aparezca en ese espacio al cargar el formulario //--> <br> Escribe aquí cualquier otro comentario:<br> <textarea rows=5 cols=50 name='texto'></textarea><br>

Page 98: Manual PHP

<!-- El <input type='hidden'> permite insertar en un formulario una valor oculto que no requiere ser cumplimentado por el usuario y que no aparece visible en el documento. Requiere un name y un value //--> <input type="hidden" name='oculto' value='Esto iría oculto'><br> <!-- El <input type='submit'> es el encargado de ejecutar la action incluida en la etiqueta de apertura del formulario. Sería la llamada a la página que se indica en la action El texto que se incluya en su value será el que se visualice en el propio botón de envio //--> <input type="submit" value="enviar"> <!-- El <input type='reset'> permite borrar todos los contenidos del formulario y reestablecer los valores por defecto de cada campo //--> <input type="reset" value="borrar"> <!-- La etiqueta </form> es la etiqueta (obligatoria) de fin del formulario //--> </form> </body> </html>

Interpretación de los datos recibidos a través de formularios

Igual que ocurría en el caso anterior, los datos enviados a través de un formulario son recogidos en diferentes tipos de variables predefinidas. Ahora se añade una nueva particularidad. Existe la posibilidad de dos métodos (method) de envío: GET o POST. En el caso anterior decíamos que se utilizaba el método GET, pero en el caso de los formularios son posibles ambos métodos. Conviene tenerlo en cuenta.

Método GET

No se diferencia en nada de lo descrito para el supuesto anterior. Utiliza las mismas variables predefinidas ($_GET y $_REQUEST), las utiliza con idéntica sintaxis y se comporta de igual forma en lo relativo a las opciones de register_globals.

Los nombres de las variables son en este caso, los incluidos como name en cada una de las etiquetas del formulario.

Respecto a los valores recogidos del formulario serían los siguientes. En los casos de campos tipo: text, password y textarea serían los valores introducidos por el usuario en cada uno de ellos.

En el caso de los campos tipo radio –en el que varias opciones pueden tener el mismo nombre– recogería el valor indicado en la casilla marcada; mientras que si se trata de campos tipo checkbox se transferirían únicamente las variables –y los valores– que corresponden a las casillas marcadas.

Si se tratara de un campo oculto –tipo hidden– se transferiría el valor contenido en su etiqueta y, por último, en el caso del select sería transferido como valor de la variable la parte del formulario contenida entre las etiquetas <option></option> de la opción seleccionada salvo que, dentro de la propia etiqueta option se incluya un valor mediante value=xxxxx.

Método POST

En el caso de que el método de envío sea POST hay una diferencia a tener en cuenta en cuanto a las variables que recogen la información. Ahora será la variable superglobal $_POST['nombre'] la que reemplace a $_GET['nombre'] usado en el caso del método GET.

Si register_globals está en On el comportamiento de las variables directas es idéntico al comentado para el caso de GET.

Page 99: Manual PHP

La opción REQUEST

La variable superglobal $_REQUEST['nombre'] aúna las funcionalidades de $_GET y $_POST y que recoge tanto los valores transferidos mediante el método GET como mediante POST.

Identificación del método de envío

PHP recoge en una variable el método utilizado para enviar los datos desde un formulario. Se trata de la variable:

$_SERVER['REQUEST_METHOD']

que devuelve una cadena (GET ó POST) que especifica el método de envío utilizado.

Diferencias ente los métodos GET y POST

Las diferencias entre uno y otro método son las siguientes:

Método GET

� – Al ser enviado el formulario se carga en el navegador la dirección especificada como action, se le añade un ? y a continuación se incluyen los datos del formulario. Todos los datos de la petición van a ser visibles en la barra de direcciones del navegador.

� – Únicamente son aceptados los caracteres ASCII.

� – Tiene una limitación en el tamaño máximo de la cadena que contiene los datos a transferir. En IE esa limitación es de 2.083 caracteres.

� – Suele ser utilizado cuando los datos no producen modificaciones en el servidor tal como ocurre en consultas en bases de datos, búsquedas y similares.

Método POST

� – No tiene las limitaciones indicadas para el caso de GET en lo relativo a la aceptación de caracteres no ASCII.

� – Los datos del formulario no se visualizan en la barra de direcciones del navegador.

� – Este método de transferencia de datos es el más habitual cuando se utilizan formularios.

� – Suele ser utilizado cuando los datos transferidos producen algún tipo de modificación en los dos datos almacenados en el servidor tales como: altas en bases de datos, modificaciones, etcétera.

Tipos de contenidos de los formularios

Vamos a contemplar un último aspecto relativo a los formularios. Se trata de la forma de codificar los datos para su transmisión (ENCTYPE). Puede especificarse dentro de la etiqueta<form> utilizando la sintaxis: enctype='valor'.

En el caso de que no se especifique, tomará el valor application / x-www-form-urlencoded, sea GET o POST el método que se utilice.

ENCTYPE admite también el valor multipart/form-data como una opción alternativa a la anterior.

Las diferencias básicas entre ambos modos de codificación son las siguientes:

� – En el tipo application/x-www-form-urlencoded los nombres de control y los valores se transforman en secuencias de escape, es decir, convirtiendo cada byte en una cadena %HH, donde HH es la notación hexadecimal del valor del byte.

� – Además, los espacios son convertidos en signos +, los saltos de línea se representan como %0D%0A, el nombre y el valor se separan con el signo = y los diferentes bloques nombre/valor, se separan con el carácter &.

� – El tipo de contenido application / x-www-form-urlencoded es utilizable cuando el volumen de datos es reducido y todos los caracteres remitidos son ASCII.

� –En cuanto a la encriptación tipo multipart/form-data, sigue las reglas de las transferencias MIME, que comentaremos más adelante cuando tratemos el tema del correo electrónico.

Page 100: Manual PHP

� – El tipo de contenido multipart / form-data debe utilizarse en formularios que contienen archivos adjuntos, caracteres no-ASCII, y datos binarios.

¡Cuidado! Cuando se incluye una cadena vacía ("") como valor de action en un formulario se recargará el mismo documento como respuesta al envío del formulario.

Ejemplos de las diferentes opciones de formularios

Intentaremos ver ejemplos de las diferentes opciones. Utilizaremos un formulario idéntico al descrito anteriormente dónde incluiremos como action una llamada al script ejemplo26.php (su código fuente es el que puedes ver aquí debajo) y donde dónde no especificaremos ningún valor para ENCTYPE con lo cual asumirá su valor por defecto, application / x-www-form-urlencoded

En el código fuente que ves aquí debajo intentamos leer los contenidos transferidos utilizando las diferentes posibilidades que nos ofrece PHP. Incluimos dos ejemplos cuya una única diferencia el método utilizando en el formulario. En el primero de ellos se utiliza el método GET y en el segundo POST.

<?php print "Empezaremos comprobando el método de envío"; print " El method que ha usado fué: ".$_SERVER['REQUEST_METHOD']."<br>"; print "Leeremos los contenidos de la superglobal \$_REQUEST"; print " Funcionará tanto con el método GET como con POST"."<br>"; @print $_REQUEST['nombre']."<br>"; @print $_REQUEST['clave']."<br>"; @print $_REQUEST['color']."<br>"; @print $_REQUEST['acondicionado']."<br>"; @print $_REQUEST['tapiceria']."<br>"; @print $_REQUEST['llantas']."<br>"; @print $_REQUEST['precio']."<br>"; @print $_REQUEST['texto']."<br>"; @print $_REQUEST['oculto']."<br>"; print "Ahora leeremos los contenidos de la superglobal \$_POST"; print " Sólo dará resultados cuando el método sea POST"."<br>"; @print $_POST['nombre']."<br>"; @print $_POST['clave']."<br>"; @print $_POST['color']."<br>"; @print $_POST['acondicionado']."<br>"; @print $_POST['tapiceria']."<br>"; @print $_POST['llantas']."<br>"; @print $_POST['precio']."<br>"; @print $_POST['texto']."<br>"; @print $_POST['oculto']."<br>"; print "Ahora leeremos los contenidos de la superglobal \$_GET"; print " Sólo dará resultados cuando el método sea GET"."<br>"; @print $_GET['nombre']."<br>"; @print $_GET['clave']."<br>"; @print $_GET['color']."<br>"; @print $_GET['acondicionado']."<br>"; @print $_GET['tapiceria']."<br>"; @print $_GET['llantas']."<br>"; @print $_GET['precio']."<br>"; @print $_GET['texto']."<br>"; @print $_GET['oculto']."<br>"; print "Ahora intentaremos leer variables directas" ; print " Sólo dará resultados cuando REGISTER_GLOBALS=On"."<br>"; @print $nombre."<br>"; @print $clave."<br>"; @print $color."<br>"; @print $acondicionado."<br>";

Page 101: Manual PHP

@print $tapiceria."<br>"; @print $llantas."<br>"; @print $precio."<br>"; @print $texto."<br>"; @print $oculto."<br>"; print "Incluyamos la función extract aplicada a \$_REQUEST"; print " de esa forma dará resultados tanto cuando el método sea GET como cuando sea POST. "; print " Si la hubiéramos aplicado \$_POST solo funcionaría con ese método."; print " Si lo hubiéramos hecho \$_GET solo atendería ese método"."<br>"; extract($_REQUEST); @print $nombre."<br>"; @print $clave."<br>"; @print $color."<br>"; @print $acondicionado."<br>"; @print $tapiceria."<br>"; @print $llantas."<br>"; @print $precio."<br>"; @print $texto."<br>"; @print $oculto."<br>"; ?>

«Con method = POST» «Con method = GET»

Estos dos nuevos ejemplos son modificaciones de los anteriores. Ahora hemos incluido dentro de la etiqueta <form> la opciónENCTYPE="multipart/form-data" manteniendo el resto del formulario sin ninguna modificación.

«Con method = POST» «Con method = GET»

La seguridad en los envíos de datos

El tema de la seguridad es una preocupación constante entre los usuarios de Internet. Cuando utilizamos las técnicas que venimos comentando en esta página –nos referimos siempre al caso de servidores remotos– corremos dos tipos de riesgo de seguridad que no estaría de más tener en cuenta.

El riesgo de que la información sea interceptada durante el proceso de transmisión desde el cliente hasta el servidor lo compartimos con todos los demás usuarios de la Red, pero hay otro –el riesgo de daños en los contenidos de nuestro espacio de servidor– que esexclusivamente nuestro.

La transparencia del método GET es tal, que incluso muestra –en el momento del envio– todos los datos en la barra de direcciones del navegador. Eso permite que cualquier usuario pueda conocer a simple vista la ruta completa hasta el script, así como los nombres y valores de las variables.

Cuando se usa el método POST los formularios son un poco más discretos, pero igual de transparentes. El código de cualquier formulario estará accesible sólo con ir a la opción Ver código fuente y allí estarán de nuevo todos los datos: nombre del script, nombres de las variables, etcétera, con lo que, cualquier usuario y desde cualquier sitio, puede acceder a ese script.

No haría falta ni usar nuestro formulario. Bastaría guardar una copia del mismo en el ordenador del visitante y después –haciendo ligerísimos retoques– se podría acceder a nuestro script sin necesidad de utilizar el formulario alojado en nuestro servidor.

Si pensamos que uno de nuestros scripts puede estar diseñado con el fin de modificar algunos de los contenidos de nuestro espacio –borrar datos, por ejemplo– seguramente sería cuestión de empezar a preocuparnos, y mucho más si en nuestro servidor tenemos datosimportantes.

Existen formas de evitar, o al menos reducir, este tipo de riesgos. Restringir a usuarios autorizados el uso de algunos subdirectorios es una de ellas, almacenar datos importantes fuera del directorio root del servidor es otra y el uso de algunas de las variables predefinidas como elementos de protección puede ser una tercera.

Hacemos este comentario a título meramente informativo. Por el momento nos basta con manejar los formularios, pero queremos que tengas presente la existencia de ese tipo de riesgos. Más adelante, ve- remos la manera de tratar de evitarlos. En los

Page 102: Manual PHP

contenidos opcionales de estos materiales hemos incluido información relativa a la configuración y uso de servidores seguros que pueden paliar en gran medida algunos de estos riesgos de seguridad.

Operaciones aritméticas

Operaciones aritméticas

En páginas anteriores hemos podido ver que PHP permite utilizar un tipo de variables –las numéricas– cuyos valores puedan seroperados de la misma forma que se hace con los números en la vida cotidiana.

Los resultados de las operaciones pueden utilizarse de forma directa o ser recogidos en una nueva variable. Si asignamos a una nueva variable el resultado de una operación el valor contenido en ella no se modifica si cambian posteriormente los de las variables que intervinieron su creación.

Sintaxis de print y echo

Si queremos encadenar en una sola instrucción –echo ó print– el resultado de una operación junto con otras variables (o cadenas) esimprescindible poner entre paréntesis las instrucciones de la operación. Esta norma solo tiene dos excepciones: en caso de que el print solo contenga la propia operación o cuando utilicemos echo y el separador sea una coma.

Operadores aritméticos

Las diferentes operaciones aritméticas se realizan en PHP utilizando la siguiente sintaxis:

$a + $b

Devuelve la suma de los valores númericos contenidos en las variables $a y $b.

$a – $b

Devuelve la diferencia de los valores númericos contenidos en las variables $a y $b.

$a * $b

Devuelve el producto de los valores númericos contenidos en las variables $a y $b.

$a / $b

Devuelve el cociente de los valores númericos contenidos en las variables $a y $b.

(int)($a / $b)

Devuelve el cociente entero de los valores númericos contenidos en las variables $a y $b.

$a % $b

Devuelve el resto de la división entera de los valores númericos contenidos en las variables $a y $b.

Sqrt($a)

Page 103: Manual PHP

Devuelve la raíz cuadrada del valor númerico contenido en la variables $a.

pow($a,$b)

Devuelve el resultado de elevar el valor númericos contenido en la variable $a a la potencia indicada en la variable $b (ab)

pow($a,1/$b)

Devuelve el resultado de elevar el valor númericos contenido en la variable $a a la potencia indicada en la variable 1/$b lo cual no es otra cosa que la raíz de índice $b de $a.

Abs($a);

Devuelve el valor absoluto del valor númerico contenido en la variable $a.

<?php # definamos dos variables numéricas asignandoles valores $a=23; $b=34; /* hagamos una suma y escribamos directamente los resultados utilizando las instrucciones print y echo con todas sus posibles opciones de sintaxis */ print("La suma de $a + $b es: " . $a . "+" . $b . "=" . ($a+$b)."<br>"); print "La suma de $a + $b es: " . $a . "+" . $b . "=" . ($a+$b) ."<BR>"; print ("La suma de $a + $b es: " . $a . "+" . $b . "=" . ($a+$b) ."<BR>"); echo "La suma de $a + $b es: " . $a . "+" . $b . "=" . ($a+$b) ."<BR>"; echo "La suma de $a + $b es: " , $a , "+" , $b . "=" , ($a+$b) ."<BR>"; echo "La suma de $a + $b es: " , $a , "+" , $b , "=" , $a+$b ,"<BR>"; # guardemos ahora el resultado de esa operación en una nueva variable $c=$a+$b; /*ahora presentemos el resultado utilizando esa nueva variable adviertiendo el la salida */ print ("Resultados recogidos en una nueva variable<br>"); print "La suma de $a + $b es: " . $a . "+" . $b . "=" . $c ."<BR>"; print ("La suma de $a + $b es: " . $a . "+" . $b . "=" . $c ."<BR>"); echo "La suma de $a + $b es: " . $a . "+" . $b . "=" . $c ."<BR>"; echo "La suma de $a + $b es: " , $a , "+" , $b . "=" , $c ."<BR>"; echo "La suma de $a + $b es: " , $a , "+" , $b , "=" , $c ,"<BR>"; /* modifiquemos ahora los valores de $a y $b comprobando que el cambio no modifica lo contenido en la variable $c */ $a=513; $b=648; print ("<br> C sigue valiendo: " . $c ."<br>"); # experimentemos con los paréntesis en un supuesto de operaciones combinada # tratemos de sumar la variable $a con la variable $b # y multiplicar el resultado por $c. # Si escribimos print($a+$b*$c) nos hará la multiplicación antes que la suma print "<br>No he puesto paréntesis y el resultado es: ".($a+$b*$c); # Si escribimos print(($a+$b)*$c) nos hará la suma y luego multiplicará print "<br>He puesto paréntesis y el resultado es: ".(($a+$b)*$c); ?>

ejemplo33.php

Operaciones aritméticas

Operación Sintaxis A B Resultado

Suma $a+$b 12 -7.3 4.7

Diferencia $a-$b 12 -7.3 19.3

Producto $a*$b 12 -7.3 -87.6

Page 104: Manual PHP

Cociente $a/$b 12 -7.3 -1.64383561644

Cociente entero (int)($a/$b) 12 -7.3 -1

Resto de la división $a%$b 12 5 2

Potencias ab pow($a,$b) 12 5 248832

Potencias ab pow($a,$b) -7.3 -3 -0.00257058174836

Raíz cuadrada Sqrt($a) 12 3.46410161514

Raíz cuadrada Sqrt($a) -7.3 NAN

Raíz enésima pow($a,(1/$b) 12 3 2.28942848511

Valor absoluto Abs($b) -7.3 7.3

Al realizar una operación cuyo resultado no es un número real PHP devuelve la cadena NaN tal como puedes ver en el ejemplo de laraíz cuadrada de un número negativo.

Redondeo de resultados

PHP dispone de tres opciones de redondeo:

floor($z)

Redondeo por defecto

ceil($z)

Redondeo por exceso

round($z)

Redondeo tradicional

Redondeos

tipo Sintaxis A Resultado

Parte entera (int)$a 12 12

Parte entera (int)$a -7.3 -7

Parte entera (int)$a -13.8546 -13

Parte entera (int)$a -24.5 -24

Parte entera (int)$a 13.8546 13

Parte entera (int)$a 24.5 24

Redondeo por defecto floor($a) 12 12

Redondeo por defecto floor($a) -7.3 -8

Redondeo por defecto floor($a) -13.8546 -14

Redondeo por defecto floor($a) -24.5 -25

Redondeo por defecto floor($a) 13.8546 13

Redondeo por defecto floor($a) 24.5 24

Redondeo por exceso ceil($a) 12 12

Redondeo por exceso ceil($a) -7.3 -7

Redondeo por exceso ceil($a) -13.8546 -13

Redondeo por exceso ceil($a) -24.5 -24

Redondeo por exceso ceil($a) 13.8546 14

Page 105: Manual PHP

Redondeo por exceso ceil($a) 24.5 25

Redondeo round($a) 12 12

Redondeo round($a) -7.3 -7

Redondeo round($a) -13.8546 -14

Redondeo round($a) -24.5 -25

Redondeo round($a) 13.8546 14

Redondeo round($a) 24.5 25

Orden de operación

Cuando una misma instrucción contiene una secuencia con varias operaciones el orden de ejecución de las mismas sigue los mismos criterios que las matemáticas. No se realiza una ejecución secuencial sino que se respeta el orden de prioridad matemático. Es decir, las potencias y raíces tienen prioridad frente a los productos y los cocientes, y estos, son prioritarios respecto a la suma y las diferencias.

Igual que en matemáticas se pueden utilizar los paréntesis para modificar el orden de ejecución de las operaciones, e igual que allíPHP también permite encerrar paréntesis dentro de paréntesis.

¡Cuidado! Cuando realices operaciones combinadas, no olvides establecer –mediante paréntesis– las prioridades que sean necesarias. ¡No temas abusar de ellos! Te evitarán muchos problemas.

Punto flotante y precisión arbitraria

Las funciones aritméticas que estamos estudiando utilizan la sintaxis de punto flotante cuya precisión es limitada y, aunque depende del sistema, PHP típicamente utiliza el formato de doble precisión IEEE 754, el cual puede dar un error relativo máximo por aproximación del orden de 1.11*10–16.

Las operaciones aritméticas más elementales nunca van a dar grandes errores aunque la dimensión de estos podrá irse incrementando a medida que se concatenar varios de esos errores simples en operaciones compuestas.

Hay que tener en cuenta la existencia de fracciones que se pueden representar de forma exacta como números de punto flotante en base 10 (por ejemplo 1/10 ó 7/10) pero que carecen de esa exactitud al ser expresados en base 2 que es la usada internamente por PHP.

Así pues, hemos de tener cuidado al considerar números de coma flotante hasta su último dígito por los imprevisibles efectos –sobretodo a la hora de la comparación– de esos errores en el resultado.

Para operaciones matemáticas que necesiten una mejor precisión PHP (innecesaria en el ámbito cotidiano y limitada a casos muy puntuales del ámbito científico) ofrece la calculadora binaria que soporta números de cualquier tamaño y precisión representados como cadenas. Las funciones que utilizan esa forma de cálculo son las siguientes:

bcadd($a,$b,c)

Devuelve la suma de los valores numéricos contenidos en las cadenas $a y $b con una precisión (número de cifras decimales) especificada por el número entero especificado en el parámetro c.

bcsub($a,$b,c)

Devuelve la diferencia entre los valores numéricos contenidos en las cadenas $a y $b con una precisión (número de cifras decimales) especificada por el número entero especificado en el parámetro c.

Page 106: Manual PHP

bcmul($a,$b,c)

Devuelve el producto de los valores numéricos contenidos en las cadenas $a y $b con una precisión (número de cifras decimales) especificada por el número entero especificado en el parámetro c.

bcdiv($a,$b,c)

Devuelve el resultado de dividir los valores numéricos contenidos en las cadenas $a y $b con la precisión especificada por el número entero contenido en el parámetro c.

bcmod($a,$b)

devuelve el resto de la división entera de los valores contenidos en las cadenas $a y $b.

bcpow($a,$b,c)

Devuelve el resultado de elevar el valor numérico contenido en la cadena $a a la potencia expresada por la cadena $b dando el resultado con la cantidad de decimales señalada por el parámetro c.

bcsqrt($a,c)

Devuelve la raíz cuadrada del valor numérico contenido en la cadena $a con la cantidad de decimales señalada por el parámetro c.

bccomp($a,$b,c)

Compara los valores contenidos en las cadenas $a y $b teniendo en cuenta únicamente el número de decimales especificados en c. Si $a>$b devuelve 1. Si las magnitudes comparadas son iguales devuelve 0 y si son distintas devuelve 1.

Si comparamos $a='3.456' (es necesario indicar siempre los valores como cadenas) con $b='3.45' obtendremos como resultado:

bccomp($a,$b,0)=0 Compara 3 con 3 que obviamente son iguales bccomp($a,$b,1)=0 Compara 34 con 34. Son iguales y por tanto devuelve cero bccomp($a,$b,2)=0 Compara 345 con 345. Son iguales y por tanto devuelve cero bccomp($a,$b,3)=1 Compara 3456 con 3450. No son iguales y por tanto devuelve uno

En el cuadro puedes ver, señalados en rojo, los resultados de efectuar la misma operación utilizando el operador de coma flotante y estas funciones de precisión arbitraria. Si comparas las diferencias en los resultados obtenidos con cada una de ellas podrás darte idea del error provocado por los inevitables redondeos de la operación con coma flotante.

Comparación de punto flotante y precisión arbitraria

Comparación de funciones matemáticas

Operación Variable Valor

S U M A

$A "12345678910123456789012345678901234567890"

$B "45678910123456789012345678901234567890123"

bcadd($A,$B,0) 58024589033580245801358024580135802458013

$a=(double)$A 1.23456789101E+40

$b=(double)$B 4.56789101235E+40

Page 107: Manual PHP

$a+$b 5.80245890336E+40 ó 58024589033580236333428572634998418440192

$C "0.0002345678902"

$D "0.00000000000000000000000000000000000000001523786"

bcadd($C,$D,50) 0.00023456789020000000000000000000000000001523786000

$c=(double)$C 0.0002345678902

$d=(double)$D 1.523786E-41

$c+$d 0.0002345678902 ó 0,00023456789020000001029161995624860992393223568797

R E S T A

$A "12345678910123456789012345678901234567890"

$B "45678910123456789012345678901234567890123"

bcsub($A,$B,0) -33333231213333332223333333222333333322233

$a=(double)$A 1.23456789101E+40

$b=(double)$B 4.56789101235E+40

$a-$b -3.33332312133E+40 ó -33333231213333327795815413198205132734464

$C "0.0002345678902"

$D "0.00000000000000000000000000000000000000001523786"

bcsub($C,$D,50) 0.00023456789019999999999999999999999999998476214000

$c=(double)$C 0.0002345678902

$d=(double)$D 1.523786E-41

$c-$d 0.0002345678902 ó 0,00023456789020000001029161995624860992393223568797

P R O D U C T O

$A "12345678910123456789012345678901234567890"

$B "45678910123456789012345678901234567890123"

bcmul($A,$B,0) 56393715734858534797546128354926080723008626986848 1383972550423718379870903950470

$a=(double)$A 1.23456789101E+40

$b=(double)$B 4.56789101235E+40

$a*$b 5.63937157349E+80 ó 56393715734858529559076134525051001632068799181691 5078270593363632973621514731520

$C "0.0002345678902"

$D "0.00000000000000000000000000000000000000001523786"

bcmul($C,$D,62) 0.000000000000000000000000000000000000000000003574 312671362972

$c=(double)$C 0.0002345678902

$d=(double)$D 1.523786E-41

$c*$d 3.57431267136E-45 ´0 0,000000000000000000000000000000000000000000003574 31267136297184

D I V I S I Ó N

$A "12345678910123456789012345678901234567890"

$B "45678910123456789012345678901234567890123"

bcdiv($A,$B,30) 0.270270872854818178953756836882

$a=(double)$A 1.23456789101E+40

$b=(double)$B 4.56789101235E+40

$a/$b 0.270270872855 ó 0,270270872854818211550309570157

$C "0.0002345678902"

$D "0.00000000000000000000000000000000000000001523786"

Page 108: Manual PHP

bcdiv($C,$D,30) 15393755435474535138136194977509965310.08947450626 2690430283517501801

$c=(double)$C 0.0002345678902

$d=(double)$D 1.523786E-41

$c/$d 1.53937554355E+37 ó 15393755435474538599292516471769399296,00000000000 0000000000000000000

R E S T O S

$A "12345678910123456789012345678901234567890"

$B "57"

bcmod($A,$B) 19

$a=(double)$A 1.23456789101E+40

$b=(double)$B 57

$a % $b 0

P O T E N C I A

$A "12345678910123456789012345678901234567890"

$B "13"

bcpow($A,$B)

15477282435120092620517640487873307882539433894477 02478876805675247359287360605519476488004915460644 30130253272627846453956411651820029120439549209342 64779279536097719586538292066102759581864670705845 47341619065960659102701030110334399943453939049941 81961179086427277645221130645386339095405377300948 55610257804029129290263282188925049521874093123594 94558994966581650612470024575134583192369561297975 69626211024080786875604177270092747586939296603680 65512433192215011092075340601280601173670507434702 7729224690000000000000

$a=(double)$A 1.23456789101E+40

$b=(double)$B 13

pow($a, $b) INF ó inf

R A I Z

C U A D R A D A

$A "12345678910123456789012345678901234567890"

$B "13"

bcsqrt($A,15) 111111110651111110153.9111071478030905799220364

$a=(double)$A 1.23456789101E+40

$b=(double)$B 13

Sqrt($a) 1.11111110651E+20 ó 111111110651111112704,0000000000000000000000000

Aunque las diferencias de resultados puedan parecer muy grandes en valor absoluto (hemos exagerado premeditadamente los valores operados (tanto en valores grandes como en valores pequeños) los errores relativos son de escasa significación. En la inmensa mayoría de las situaciones (por no decir todas las cotidianas) resulta suficiente utilizar los operadores de coma flotante.

Logaritmos y trigonometría

Logarítmos y trigonometría

La sintaxis para el uso de las funciones logarítmicas y trigonométricas es esta:

log($a)

Devuelve el valor del logaritmo neperiano de valor numérico contenido en la variable $a

Page 109: Manual PHP

Log10($a)

Devuelve el valor del logaritmo decimal de valor numérico contenido en la variable $a

Exp($a)

Devuelve el valor del número e elevado a la variable $a (e$a)

Operaciones con logaritmos

Operación Sintaxis A Resultado

Logaritmo neperiano log($a) 12 2.48490664979

Logaritmo neperiano log($a) -7.3 NAN

Logaritmo decimal Log10($a) 12 1.07918124605

Logaritmo decimal Log10($a) -7.3 NAN

exponencial ea Exp($a) 12 162754.791419

exponencial ea Exp($a) 1 2.71828182846

exponencial ea Exp($a) -7.3 0.000675538775194

exponencial 10a pow(10,$a) 12 1000000000000

exponencial 10a pow(10,$a) -7.3 5.01187233627E-8

Al realizar una operación cuyo resultado no es un número real PHP devuelve la cadena NAN tal como puedes ver en los ejemplos delogaritmos de números negativos.

Las funciones trigonométricas tanto las directas como las inversas consideran los valores de los angulos expresados en radianes.

pi()

devuelve el valor del número irracional π

Sin($a)

devuelve el valor del seno del angulo $a expresado en radianes

Cos($a)

devuelve el valor del coseno del angulo $a expresado en radianes

Tan($a)

devuelve el valor de la tangente del angulo $a expresado en radianes

deg2rad($a)

Transforma en radianes el del angulo $a expresado en grados sexagesimales

Funciones trigonométricas

Page 110: Manual PHP

Operación Sintaxis A Resultado

Valor de PI Pi() 3.14159265359

Seno de A (radianes) Sin($a) 12 -0.536572918

Seno de PI (radianes) Sin(pi()) pi() 1.22464679915E-16

Coseno de A (radianes) Cos($a) 12 0.843853958732

Coseno de PI (radianes) Cos(pi()) pi() -1

Tangente de A (radianes) Tan($a) 12 0.843853958732

Tangente de PI (radianes) Tan(pi()) pi() -1.22464679915E-16

Tangente de PI/2 (radianes) Tan(pi()) pi()/2 1.63312393532E+16

Pasa grados a radianes deg2rad($a) 45 0.785398163397

Pasa grados a radianes deg2rad($a) 30.8 0.537561409614

Asin($a)

devuelve, expresado en radianes, la medida del angulo cuyo seno es $a

Acos($a)

devuelve, expresado en radianes, la medida del angulo cuyo coseno es $a

Atan($a)

devuelve, expresado en radianes, la medida del angulo cuya tangente es $a

rad2deg($a)

convierte en grados sexagesimales la medida del angulo $a expresado en radianes

Funciones trigonométricas inversas

Operación Sintaxis A Resultado

Arco seno de A (en radianes) Asin($a) 0.8 0.927295218002

Arco seno de A (en radianes) Asin($a) 12 NAN

Arco coseno de A (en radianes) Acos($a) 0.8 0.643501108793

Arco coseno de A (en radianes) Acos($a) 12 NAN

Arco tangente de A (en radianes) Atan($a) 0.8 0.674740942224

Arco tangente de A (en radianes) Atan($a) 12 1.48765509491

Pasa radianes a grados rad2deg($a) pi()/4 45

Cambios de base y formatos

Formatos de las variables numéricas

En PHP, para asignar valores numéricos a una variable pueden utilizarse uno de los siguientes sistemas de numeración:

Los números enteros pueden escribirse en una cualquiera de estas bases:

• Base decimal $a=número

Page 111: Manual PHP

No se pueden insertar ceros a la izquierda cuando se escriben números en base decimal.

• Base octal $a=0número octal

Basta poner un CERO delante del número para que sea interpretado como escrito en base OCTAL. Obviamente, sólo admite los dígitos de 0 a 7.

• Base hexadecimal $a=0xnúmero hexadecimal

Si se escribe CERO EQUIS (0x) delante del número, PHP lo interpretará como expresado en hexadecimal. En este caso, admitirá como dígitos de 0 a 9 y de A a F.

Un número de coma flotante puede escribirse de cualquiera de estas formas:

• Notación decimal. $a=número

Se puede utilizar un cero a la izquierda del punto decimal.

• Notación científica $a=número e exponente

Se puede utilizar un cero a la izquierda del punto decimal. Un ejemplo: $a=1.2e5 asigna a $a el valor: 1.2 x 105. Cuando el exponente es negativo $a=1.2e-5 asigna a $a el valor: 1.2 x 10-5

Asignación de valores en distintas bases

Base Sintaxis Valor decimal Aplicable a

Base Decimal $a=17 17 Números enteros

Base Octal $a=017 15 Números enteros

Base Hexadecimal $a=0x17 23 Números enteros

Base Hexadecimal $a=0x1A3B 6715 Números enteros

Notación decimal $a=123000; 123000 Coma flotante

Base Decimal $a=0.174 0.174 Coma flotante

Notación científica $a=1.23e5; 123000 Coma flotante

Notación científica $a=23.4e-2; 0.234 Coma flotante

Cambios de base

PHP permite hacer todo tipo de cambios de base. Para evitar ser reiterativos, observa los ejemplos. Allí tienes las diferentes funciones mediante las que se puede realizar ese proceso.

Asignación de valores en distintas bases

Valor de la variable Base Nueva base Sintaxis Expresión

$a=1234 10 8 decoct($a) 2322

$a=1234 10 16 dechex($a) 4d2

$a=1234 10 2 decbin($a) 10011010010

$a=1234 8 10 octdec($a) 668

$a=1234 16 10 hexdec($a) 4660

Page 112: Manual PHP

$a=1010011 2 10 bindec($a) 83

$a=1234 7 14 base_convert($a,7,14) 254

$a=1234 5 18 base_convert($a,5,18) ae

$a=1234 18 5 base_convert($a,18,5) 202123

Presentaciones numéricas

La presentación de los valores numéricos permite una gran variedad de formatos. El número de cifras decimales, los separadores de decimales y los separadores de mil pueden configurarse a voluntad utilizando alguna de estas funciones.

number_format (número)

Presenta la parte entera del número (sin decimales) y utiliza como separador de miles una coma (,).

number_format (número , número de cifras decimales)

Presenta el número de cifras decimales que se indiquen y utiliza como separador decimal un punto (.) y el separador de miles es una coma (,).

number_format (número , núm decimales , "sep. decimal" , "sep. miles")

Permite establecer el número de cifras decimales de la presentación así como el carácter que se establezca como separador de decimales y como separadores de miles.

¡Cuidado!. No te olvides de escribir los caracteres de separación entre comillas.

Formatos de presentación de número

Valor inicial

Nº de decimales

Sep. dec.

Sep. miles

Sintaxis Resultado

$a=1234567.234 0 , number_format($a) 1,234,567

$a=1234567.234 2 . , number_format($a,2) 1,234,567.23

$a=1234567.234 1 , . number_format($a ,2 , "," ,".") 1.234.567,2

$a=1234567.234 1 ' esp number_format($a ,2 , " ' " ," ") 1 234 567'2

Números aleatorios

El valor Unix Epoch

El conocido como tiempo UNIX –o también Unix Epoch– es un sistema referencia de tiempo cuya unidad son los segundos y que tiene su valor cero a las 00:00:00 horas (GMT) del día uno de enero de 1970.

time()

La función time() devuelve el número de segundos transcurridos desde el comienzo de la Unix Epoch hasta el momento actual. ¿Que cuantos son? Pues mira, desde el comienzo del tiempo UNIX hasta este mismo instante han transcurrido 1 338 997 370 segundos

microtime()

Page 113: Manual PHP

Usando la función microtime() se obtiene una cadena que, además del número de segundos transcurridos desde el comienzo de laUnix Epoch, incluye – al comienzo de ella y separada por un espacio– la parte decimal de ese tiempo expresada en microsegundos.

El valor de la cadena devuelta por microtime() en este instante es 0.32155100 1338997370 . Como verás, aparece la fracción decimal del tiempo Unix Epoch delante de su valor entero.

(double)microtime()

Dado que microtime() devuelve una cadena, es posible convertirla en número de coma flotante anteponiendo (double) que, como recordarás, es la función que permite forzar una cadena a número coma flotante. Una vez cambiado el tipo de variable el valor devuelto pormicrotime se convierte en: 0.321555 que como puedes observar es un número con seis decimales (si las últimas cifras son ceros no se visualizarán).

Si se multiplica el valor anterior por 1.000.000 obtenemos un número entero de seis cifras que cambia de valor cada millonésima de segundo. Observa el resultado de esta operación: 321563 ¡Fíjate que ha cambiado! La diferencia no es otra cosa que el tiempo transcurrido entre los instantes en que se ejecutaron ambas instrucciones.

Números aleatorios

PHP dispone de dos funciones capaces de generar números aleatorios. Se trata de la función rand() y de la función mejoradamt_rand().

Generación de un número aleatorio

Generador rand() Generador mt_rand()

Sintaxis Nº aleatorio Sintaxis Nº aleatorio

echo rand() 1691253796 echo mt_rand() 1938204944

El número aleatorio generado está comprendido entre CERO y un valor máximo que puede determinarse para cada una de las dos opciones mediante las funciones getrandmax() y mt_getrandmax() Estos son sus valores.

Valores máximos de los generadores de números aleatorios

Generador rand() Generador mt_rand()

Sintaxis Valor máximo Sintaxis Valor máximo

echo getrandmax() 2147483647 echo mt_getrandmax() 2147483647

Según las librerías que esté usando PHP, puede ocurrir que los valores máximos con ambos generadores sean iguales.

Una semilla que mejora la aleatoriedad

Se pueden ejecutar las funciones srand() o mt_srand() incluyendo en ellas una expresión como la especificada en rojo

srand((double)microtime()*1000000) mt_rand((double)microtime()*1000000)

antes de invocar a las funciones rand() o mt_rand(). De esta forma estaremos introduciendo una semilla cuyo valor, tal como comentamos en el epígrafe anterior, se modifica cada millonésima de segungo y que mejora sustancialmente la aleatoriedad de los números obtenidos.

Aquí tenemos un ejemplo:

Generación de un número aleatorio con semilla

Generador rand()

Sintaxis Nº aleatorio

Page 114: Manual PHP

srand((double)microtime()*1000000); echo rand()

82654550

Generador mt_rand()

Sintaxis Nº aleatorio

mt_srand((double)microtime()*1000000); echo mt_rand()

909959302

Manteniendo la sintaxis anterior –no te olvides de incluir las semillitas– se pueden generar números aleatorios comprendidos dentro del intervalo que preestablezcamos. Bastaría con añadir los valores de los extremos de ese intervalo como parámetros de la función. La sintaxis sería esta:

rand(extremo inferior , extremo superior) y para la función mejorada mt_rand(extremo inferior , extremo superior)

Generación de un número aleatorio delimitando intervalos

Generador rand()

Sintaxis Nº aleatorio

srand((double)microtime()*1000000); echo rand(1,300)

126

Generador mt_rand()

Sintaxis Nº aleatorio

mt_srand((double)microtime()*1000000); echo mt_rand(1,300)

173

Variables tipo cadena

Las variables de cadena

A las variables tipo cadena se les puede asignar los valores escribiendo el contenido entre comillas $var="Texto del contenido"; o por medio de la conocida como sintaxis de documento incrustado que es la siguiente: $var= <<< EOD ... contenido de la cadena... ... puede ir .... .. en varias líneas... EOD; donde EOD es una palabra cualquiera que debe repetirse exactamente igual al final de la instrucción.

La sintaxis de documento inscrutado requiere tener presentes algunas consideraciones:

� – El nombre de la variable, el signo igual que la precede, los tres símbolos < y el EOD deben escribirse en la misma línea, que esta vez no irá acabada en punto y coma (observa que no acaba allí la instrucción).

� – Puede incluirse el texto (valor de la variable) en tantas líneas como se desee, pero hay que tener en cuenta que a la hora de visualizar el documento no se mantiene esa estructura ya que HTML sólo entiende la etiqueta <BR> como indicador de salto de línea.

� – El cierre de la instrucción debe hacerse –siempre– escribiendo el EOD al comienzo una nueva línea –independiente– que ahora sí tiene que llevar el punto y coma que indica a PHP el final de una instrucción.

Aquí tienes un ejemplo en el que se utilizan las dos formas de asignación de valores a una cadena.

<html> <head>

Page 115: Manual PHP

<title>Ejemplo 34 - PHP</title> </head> <body> <?php $cadena1="Esto es una cadena de texto"; $cadena2= <<<Pepe Esta es otra cadena escrita con la sintaxis de documento incrustado. Se escribe en varias líneas y tiene la sintaxis siguiente. Después de escribir el nombre de la variable y el signo igual se ponen los tres <<< y un nombre cualquiera. En este caso, Pepe. Luego hay que saltar de línea y escribir el texto con las líneas que se desee, pero cuidado... a la hora de visualizar la cadena con la instrucción echo todo este texto se verá seguido ya que para que se visualizaran saltos de línea en una página web habría que poner las famosas etiquetas <BR>. Se indica el final de la cadena escribiendo de nuevo el nombre asignado en la primera línea -Pepe- pero teniendo la precaución de escribirlo en una linea nueva al final de todo el texto... Así como lo ves en el código fuente. Pepe; $cadena3= <<<Pepa Esta es otra cadena con el nombre Pepa puedo escribir Pepa cuantas veces quiera porque el PHP no interpretará el final de documento incrustado hasta que no la escriba en una sola linea y seguida del punto y coma Pepa; echo $cadena1,"<br>"; echo $cadena2,"<br>"; echo $cadena3,"<br>"; ?> </body> </html>

ejemplo34.php

Operaciones con cadenas

La concatenación de cadenas

Para concatenar (unir en una sola cadena) varias porciones de texto hemos venido utilizando –en las instrucciones print y echo– un punto (.).

Este punto (.) es un elemento muy importante que, además de la forma que hemos visto en las páginas anteriores, tiene los siguientes usos:

• Unir dos cadenas y recogerlas en una variable $a="cad1" . "cad2"; o $a= $b . $c

podemos obtener una nueva variable formada por la unión dos trozos. Pero seguramente te preguntarás ¿qué ocurre si juntamos una variable cadena y una numérica? o ¿qué ocurre si juntamos dos variables numéricas?

Page 116: Manual PHP

En cualquiera de los supuestos –puedes verlo en el ejemplo– las variables serán tratadas por PHP –con independencia de lo que puedan contener– como de tipo cadena y la variable que contiene el resultado es del tipo string. • Añadir contenidos a una variable tipo string $a .="cad1" o $a .=$b

Si utilizamos una sintaxis como esta (presta mucha atención al punto que va delante del signo igual) se añadiría al valor actual de la variable $a el contenido indicado después del signo igual. Fíjate en la importancia del punto. Si está presente se añaden nuevos contenidos a la variable. En el caso de que no estuviera se asignaría a la variable únicamente lo incluido después del signo igual.

Aquí tienes un ejemplo de concatenación de variables tipo string.

<?php #definamos y asignemos valores a variables tipo cadena $cadena1="Esto es una cadena de texto"; $cadena2="Esta es una segunda cadena de texto"; #hagamos lo mismo con variables numéricas $cadena3=127; $cadena4=257.89; # unámoslas mezclando tipos $union1=$cadena1 . $cadena2; $union2=$cadena1 . $cadena3; $union3=$cadena3 . $cadena4; #veamos que ha ocurrido echo $union1,"<br>"; echo $union2,"<br>"; echo $union3,"<br>"; # modifiquemos ahora una cadena # añadiendole contenidos $cadena3 .=" Este es el texto que se añadirá a la variable cadena3"; # imprimamos los resultados echo $cadena3,"<br>"; # añadamos ahora un nuevo trozo, esta vez # a partir de una cadena escrita con la # sintaxis de documento incrustado $cadena3 .= <<<Pepito Ahora le añado a la cadena este trocillo asignado con el "formato" de documento incrustado Pepito; # visualicemos el resultado echo $cadena3,"<br>"; ?>

ejemplo35.php

¡Cuidado! Observa en el ejemplo que, excepcionalmente, la sintaxis de documento incrustado permite introducir comillas (sin ningún método especial), pero recuerda que en cualquier otro caso hay que recurrir al truco del que hablábamos en aquí.

Page 117: Manual PHP

Array escalar y asociativo

¿Qué es un array?

Un array es algo tan simple una tabla de valores. Cada uno de los elementos de esa tabla se identifica por medio de un nombre(común para todos) y un índice (que sería el elemento diferenciador de cada uno de ellos).

Mediante el uso de arrays podemos utilizar el mismo nombre para varias variables diferenciándolas por medio de sus índices. La sintaxis que permite definir elementos en un array es esta:

$nombre[indice]

dónde $nombre utiliza exactamente la misma sintaxis empleada para definir variables, con la única particularidad de que ahora deben añadírsele los corchetes y los índices. El índice puede ser un número (habría que escribirlo dentro del corchete sin comillas), una cadena(que habría que poner en el corchete encerrada entre comillas sencillas –'–), o una variable que tampoco necesitaría ir entre comillas.

Cuando los índices de un array son números se dice que es escalar mientras que si fueran cadenas se le llamaría array asociativo.

Tablas unidimensionales

Array escalar Array asociativo

Variable Indice Valor Variable Indice Valor

$a[0] 0 Domingo $a['Primero'] Primero Domingo

$a[1] 1 Lunes $a['Segundo'] Segundo Lunes

$a[2] 2 Martes $a['Tercero'] Tercero Martes

$a[3] 3 Miércoles $a['Cuarto'] Cuarto Miércoles

$a[4] 4 Jueves $a['Quinto'] Quinto Jueves

$a[5] 5 Viernes $a['Sexto'] Sexto Viernes

$a[6] 6 Sábado $a['Septimo'] Septimo Sábado

Arrays escalares

Los elementos de un array escalar puede escribirse con una de estas sintaxis:

$a[]=valor ó $a[xx]=valor ó $a=array(valor0, valor1, valor2,...)

En el primero de los casos PHP asigna los índices de forma automática atribuyendo a cada elemento el valor entero siguiente al último asignado. Si es el primero que se define le pondrá índice 0 (CERO).

En el segundo de los casos, seremos nosotros quienes pongamos (xx) el número correspondiente al valor del índice. Si ya existiera un elemento con ese índice, se cambiaría el valor de su contenido, en caso contrario creará un nuevo elemento del array y se le asignaría como valor lo especificado detrás del signo igual que –de la misma forma que ocurría con las variables– debería ir entre comillas si fuera una cadena o sin ellas si se tratara de un número.

En el tercero de los supuestos, la palabra array que precede al paréntesis indica que cada uno de los valores –numéricos o cadenas– separados por comas e incluidos dentro del paréntesis son los valores correspondientes a los elementos de índices 0, 1, 2... de un array escalar.

Arrays asociativos

Page 118: Manual PHP

Los elementos de un array asociativo (aquel cuyos índices no son números) pueden escribirse usando la siguiente sintaxis:

$a['indice']=valor

En este caso estamos obligados a escribir el nombre del índice que habrá de ser una cadena escrita entre comillas o también una variable definida previamente y con ese tipo de contenido.

Tanto en el caso de los array asociativos como en los escalares es posible –y bastante frecuente– utilizar como índice el contenido de una variable. El modo de hacerlo sería este

$a[$indice]=valor

En este caso, sea cual fuere el valor de la variable $indice, el nombre de la variable nunca se pone entre comillas.

Ejemplo de arrays unidimensionales

<?php # Crearemos un array escalar (basta con definir un elemento) $a[2]="Este elemento es el segundo del array"; # creemos un nuevo elemento de ese array # esta vez de forma automática # si ponemos corchetes vacíos va añadiendo índices automaticamente $a[]="¿Será este tercero?"; # comprobemos que le ha puesto índice 3 echo "El elemento ".$a[3]." tiene indice 3 (siguiente a 2) <br>"; # ahora insertemos un nuevo elemento con indice 32 $a[32]="Mi indice es 32"; # insertemos otro elemento de forma automática $a[]= "¿Irá a parar al indice 33 este elemento?"; # la inserción se hará con indice 33, comprobémoslo print "Vemos que contiene el elemento de indice 33 ...".$a[33]."<br>"; # ¿qué ocurrirá si pido que imprima el elemento 21 que nadie ha definido # seguramente estará vacio, ¡¡comprobémoslo!! # antepondremos el @ para evitar el mensaje de advertencia de PHP @print ("Aqui--> ". $a[21]. "<--- si es que hay algo<br>"); # ahora crearemos un nuevo array llamado $b # insertémosle de forma automatica su PRIMER elemento $b[]="Estoy empezando con el array b y mi indice será cero"; # comprobemos que efectivamente ha empezado con indice CERO print ($b[0]."<br>"); # veamos ahora eso de los arrays asociativos # creemos uno llamado $c con varios elementos $c["objeto"]="coche"; $c["color"]="rojo"; $c["tamaño"]="ideal"; $c["marca"]= "Ferrari"; $c["precio"]="prohibitivo para un humilde docente"; #encadenemos variables para hacer una salida # pondremos cadenas " " para que no aparezcan los textos # pegados unos a otros.. $salida="<H2> El ". $c["objeto"] ." ".$c["marca"]." ".$c["color"]; $salida .=" tiene el tamaño ideal ".$c["tamaño"]; $salida .=" y su precio es ".$c["precio"]; $salida .="</H2>"; print $salida; # sigamos experimentando ahora # ¿qué ocurriría si nos olvidamos de poner nombre al indice # e insertamos un corchete vacio ¿lo crearía?¿que indice pondria? # probemos ....

Page 119: Manual PHP

$c[]="¿creará un array escalar nuevo y le pondrá indice cero?"; # tratemos ahora de visualizar esa variable # probemos a escribir $c[0] porque PHP # habrá entendido que queremos un array escalar # y como no existe ninguno con ese nombre empezará por cero # comprobémoslo echo $c[0]; ?>

ejemplo36.php

Arrays bidimensionales

Arrays bidimensionales

Los arrays bidimensionales pueden entenderse como algo bastante similar a una tabla de doble entrada. Cada uno de los elementos se identifica –sigue siendo válido el nombre único que se usaba en los unidimensionales– por un nombre ($nombre) seguido de dos ([ ]) que contienen los índices (en este caso son dos índices) del array.

Los índices pueden ser de tipo escalar -equivalen al número de fila y columna que la celda ocupa en la tabla– asociativos lo que equivaldría en alguna medida a usar como índices los nombres de la fila y de la columna y también mixtos (uno de los índices es escalar y el otro asociativo).

¡Cuidado! No dejes de tener en cuenta lo que hemos advertido al hablar de arrays unidimensionales. En este supuesto, también, se empiezan a numerar los arrays escalares a partir de CERO.

Arrays bidimensionales escalares

Los elementos de un array bidimensional escalar pueden escribirse usando una de estas sintaxis:

$a[ ][ ]=valor o $a[xx][ ]=valor o $a[ ][xx]=valor o también $a[xx][yy]=valor

En el primero de los casos PHP asigna automáticamente como primer índice el valor que sigue al último de los asignado a ese mismo índice y, si es el primero que se define, le pondrá como índice 0 (CERO). Sea cual fuere el valor de primer índice al segundo se le asignará cero ya que es en este mismo momento cuando se habrá creado el primero y, por tanto, aún carecerá de elementos.

En el segundo de los casos, asignamos un valor al primer índice (xx) y será el segundo quien se incremente en una unidad respecto al de valor más alto de todos aquellos cuyo primer índice coincide con el especificado.

La tercera opción es bastante similar a la anterior. Ahora se modificaría automáticamente el primer índice y se escribiría el contenido (xx) como valor del segundo.

En la cuarta de las opciones se asignan libremente cada uno de los índices (xx e yy) poniéndoles valores numéricos.

Este es un ejemplo de uso de array bidimensionales escalares.

<?php # rellenamos el array desde [0][0] hasta [0][4] # la insercion automatica haria que este primero fuera [0][0] $a[][]=" ";

Page 120: Manual PHP

# ahora pondremos cero como indice del primer array y dejemos que PHP # nos vaya insertando automaticamente el segundo $a[0][]="3-2";$a[0][]="5-3";$a[0][]="7-1";$a[0][]="0-2"; #ahora desde [1][0] hasta [1][4] #este primero lo dejamos como automático en ambos indices # de esta forma el primero tomará valor uno (siguiente al anterior) # de forma automática $a[][]="0-11"; # repetimos el proceso anterior $a[1][]=" ";$a[1][]="2-1";$a[1][]="1-0";$a[1][]="1-2"; # y repetimos de nuevo, ahora crearia 2 como primer indice $a[][]="0-0"; #insertariamos los restantes valores de indice 2 $a[2][]="1-3";$a[2][]=" ";$a[2][]="1-4";$a[2][]="2-0"; # nuevo incremento del primer indice $a[][]="1-0"; # rellenamos $a[3][]="6-3";$a[3][]="14-3 ";$a[3][]=" ";$a[3][]="1-0"; # nuevo y ultimo incremento de primer indice $a[][]="1-1"; # rellenamos de nuevo $a[4][]="2-3";$a[4][]="0-1 ";$a[4][]="1-1";$a[4][]=""; # como verás el proceso no tiene complicaciones, pero ... pesadillo si es # ¿verdad que si tuviéramos una base de datos sería más fácil? # estamos en ello, todo se andará... # tendríamos que ver esos valores pero.. escribir "a mano" # una tabla puede ser una tortura, así que mejor introducimos # una bucle, otro recurso que estudiaremos pronto # para esa labor repetitiva de mostrar en una tabla # todos los datos del array # Sería algo como esto # creamos la etiqueta de apertura de una tabla print ("<TABLE BORDER=2>"); # ahora dos bucles anidados (rojo uno, magenta el otro) # para rellenar las celdas de cada fila (el magenta) # y para insertar las etiquetas <TR> utilizaremos el rojo # antepondremos el @ a print para evitar mensaje de error for ($i=0;$i<5;$i++){ print("<tr>"); for($j=0;$j<5;$j++) { @print("<td>".$a[$i][$j]."</td>"); } } #ponemos la etiqueta de cierre de la tabla print("</table>"); ?>

ejemplo37.php

Utilizando el script anterior, con ligeros retoques estéticos, hemos construido esta tabla:

Page 121: Manual PHP

Todos los resultados de la liguilla

Indice 0 1 2 3 4

0 3-2 5-3 7-1 0-2

1 0-11 2-1 1-0 1-2

2 0-0 1-3 1-4 2-0

3 1-0 6-3 14-3 1-0

4 1-1 2-3 0-1 1-1

Arrays bidimensionales asociativos

Los elementos de un array asociativo bidimiensional se pueden escribir usando la siguiente sintaxis:

$a["indice1"]["indice2"]=valor

En este caso, los índices serán cadenas y se escribirán entre comillas.

Como ejemplo de array bidimensional emplearemos una tabla de resultados de una liga de fútbol en la que intervienen cinco equiposque –como en toda liga que se precie– se juega a doble partido.

Resultados de la liguilla

Indice Juvencia Mosconia Canicas Condal Piloñesa

Juvencia 3-2 5-3 7-1 0-2

Mosconia 0-11 2-1 1-0 1-2

Canicas 0-0 1-3 1-4 2-0

Condal 1-0 6-3 14-3 1-0

Piloñesa 1-1 2-3 0-1 1-1

Arrays multidimensionales

Arrays multidimensionales

PHP permite el uso de arrays con dimensión superior a dos. Para modificar la dimensión del array basta con ir añadiendo nuevos índices.

$a[x][y][z]=valor;

asignaría un valor al elemento de índices x, y y z de un array tridimensional

$a[x][y][z][w]=valor;

haría lo mismo, ahora con un array de dimensión cuatro.

Pueden tener cualquier tipo de índices: escalares, asociativos y, también, mixtos.

La función array();

Para asignar valores a una matriz puede usarse la función array(), que tiene la siguiente sintaxis:

Page 122: Manual PHP

$a= array ( índice 0 => valor, ..... , índice n => valor, );

Por ejemplo:

$z=array (0 =>2, 1=>"Pepe", 2=>34.7, 3=>"34Ambrosio") producirá igual resultado que: $z[0]=2; $z[1]="Pepe"; $z[2]=34.7; $z[3]="34Ambrosio"

Anidando en array();

La función array() permite escribir arrays de cualquier dimensión utilizando la técnica de anidado. Si pretendemos escribir los elementos de este array:

$z[0][0]=34; $z[0][1]=35; $z[0][2]=36; $z[1][0]=134; $z[1][1]=135; $z[1][2]=136;

podriamos hacerlo asi:

$z=array( 0 => array ( 0 => 34, 1 => 35, 2 => 36, ), 1 => array ( 0 => 134, 1 => 135, 2 => 136, ) );

Como puedes observar, se trata de sustituir los valores asignados a los elementos de una primera función array() por otra nueva función array que contiene los segundos índices asi como los valores asignados a los mismos.

El anidado sucesivo permitiría generar arrays de cualquier dimensión.

Aunque en el ejemplo anterior nos hemos referido a un array escalar, idéntico procedimiento sería válido para arrays asociativos con sólo cambiar los números por cadenas escritas entre comillas.

Este podría ser un ejemplo de array asociativo:

$z['a']['A']=34; $z['a']['B']=35; $z['a']['C']=36; $z['b']['A']=134; $z['b']['B']=135; $z['b']['C']=136;

que podría definirse también de esta forma:

$z=array( "a" => array ( "A" => 34, "B" => 35, "C" => 36, ), "b" => array ( "A" => 134, "B" => 135, "C" => 136, ) );

Page 123: Manual PHP

A medida que la dimensión se hace mayor la sintaxis requiere muchísima más atención y los errores son poco menos que inevitables. Refresquemos un poco la memoria.

� – No olvides los punto y coma del final de las instrucciones.

� – Cuidado con las formas anidadas y también con los paréntesis.

� – Cierra cada uno de los paréntesis que abras y no olvides que los paréntesis se anidan, ya sabes... el primero que se abre siempre con el último que se cierra, el segundo con el penúltimo, etcétera.

� – No dejes de prestar atención a las comillas. Recuerda que hay que cerrarlas siempre y que hay que diferenciarlas en los casos en que van comillas dentro de otras comillas.

Esta es la forma en la que hemos definido el array tridimensional que utilizaremos en el ejemplo.

<?php $b = array( "Juvencia" => array( "Juvencia" => array ( "Resultado" => " ", "Amarillas" => " ", "Rojas" => " ", "Penalty" => " " ), "Mosconia" => array ( "Resultado" => "3-2", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "1" ), "Canicas" => array ( "Resultado" => "5-3", "Amarillas" => "0", "Rojas" => "1", "Penalty" => "2" ), "Condal" => array ( "Resultado" => "7-1", "Amarillas" => "5", "Rojas" => "2", "Penalty" => "1" ), "Piloñesa" => array ( "Resultado" => "0-2", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "0" ), ), "Mosconia" => array( "Juvencia" => array ( "Resultado" => "0-11 ", "Amarillas" => "4", "Rojas" => "2", "Penalty" => "4" ), "Mosconia" => array ( "Resultado" => " ", "Amarillas" => " ", "Rojas" => " ", "Penalty" => " " ),

Page 124: Manual PHP

"Canicas" => array ( "Resultado" => "2-1", "Amarillas" => "0", "Rojas" => "0", "Penalty" => "2" ), "Condal" => array ( "Resultado" => "1-0", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "0" ), "Piloñesa" => array ( "Resultado" => "1-2", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "0" ), ), "Canicas" => array( "Juvencia" => array ( "Resultado" => "0-0", "Amarillas" => "0", "Rojas" => "1", "Penalty" => "1" ), "Mosconia" => array ( "Resultado" => "1-3", "Amarillas" => "2", "Rojas" => "0", "Penalty" => "1" ), "Canicas" => array ( "Resultado" => " ", "Amarillas" => " ", "Rojas" => " ", "Penalty" => " " ), "Condal" => array ( "Resultado" => "1-4", "Amarillas" => "2", "Rojas" => "1", "Penalty" => "1" ), "Piloñesa" => array ( "Resultado" => "2-0", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "0" ), ), "Condal" => array( "Juvencia" => array ( "Resultado" => "1-0 ", "Amarillas" => "4", "Rojas" => "1", "Penalty" => "2" ), "Mosconia" => array ( "Resultado" => "6-3", "Amarillas" => "1", "Rojas" => "2",

Page 125: Manual PHP

"Penalty" => "3" ), "Canicas" => array ( "Resultado" => "14-3", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "0" ), "Condal" => array ( "Resultado" => " ", "Amarillas" => " ", "Rojas" => " ", "Penalty" => " " ), "Piloñesa" => array ( "Resultado" => "1-0", "Amarillas" => "3", "Rojas" => "1", "Penalty" => "0" ), ), "Piloñesa" => array( "Juvencia" => array ( "Resultado" => "1-1", "Amarillas" => "0", "Rojas" => "0", "Penalty" => "1" ), "Mosconia" => array ( "Resultado" => "2-3", "Amarillas" => "1", "Rojas" => "0", "Penalty" => "0" ), "Canicas" => array ( "Resultado" => "0-1", "Amarillas" => "0", "Rojas" => "0", "Penalty" => "0" ), "Condal" => array ( "Resultado" => "1-1", "Amarillas" => "1", "Rojas" => "2", "Penalty" => "0" ), "Piloñesa" => array ( "Resultado" => " ", "Amarillas" => " ", "Rojas" => " ", "Penalty" => " " ), ) ); ?>

ejemplo38.php

Page 126: Manual PHP

Presentación de números

Funciones de salida

Ya conocemos algunas de las funciones que PHP utiliza para mostrar información –salidas– en la ventana del navegador del cliente.

Recordémoslas:

echo

La función echo, aunque admite también la forma echo(), no requiere de forma obligatoria el uso de los paréntesis. Detrás de lainstrucción echo pueden insertarse: variables, cadenas (éstas entre comillas) y números (éstos sin comillas) separadas por comas.

Este es un ejemplo de código:

$a=24; $b="Pepe"; $c="<br>"; echo $a,$b,25, "Luis",$c; que produciría esta salida: 24Pepe25Luis print()

La función print() sólo puede contener dentro del paréntesis una sola variable, o el conjunto de varias de ellas enlazadas por un punto. Aquí tienes algunos ejemplos: print(25.3) produciría esta salida 25.3 print("Gonzalo") escribiría Gonzalo $z=3.1416; print($z); escribiría 3.1416

Recuerda también que es posible utilizar dentro del paréntesis el concatenador de cadenas.

$h=3; $f=" hermanos"; print("Heladeria ".$h.$f) que escribiría Heladeria 3 hermanos

Salidas con formato

Ni la función echo, ni tampoco print permiten establecer una presentación (formato) en sus salidas, excepto que alguna de las variables que se use contenga el resultado de una función number_format.

La función printf() ofrece un gran número de posibilidades en este sentido. Tanto la sintaxis como los valores de los diferentes parámetros –cuando se trate de presentar números– las tienes resumidas en la tabla.

En la página siguiente veremos el uso de printf() para el tratamiento de variables tipo cadena.

Sintaxis printf(cadena de formato,variable1,variable2,..) Cadena de formato "%[relleno][alineación][ancho][precisión][tipo]"

Relleno

Carácter Valor Sintaxis Resultado

0 0 printf("%020d",32) 00000000000000000032

* '* printf("%'*20d",32) ******************32

espacio1) ' printf("%' 20d",32) 32

- '- printf("%'-20d",32) ------------------32

Observaciones En este apartado prestaremos atención únicamente a los caracteres marcados en rojo, que son los que corresponden a las diferentes formas de relleno. Los demás parámetros los iremos tratando uno en los apartados siguientes. Cuando se pretende rellenar con ceros –a la izquierda– basta escribir el 0 inmediatamente detrás del

Page 127: Manual PHP

signo % Si se trata de rellenar con un carácter distinto de cero debe escribirse inmediatamente después de% una comilla simple ' seguida del carácter de relleno. Si se pretende rellenar con espacios forzados se puede escribir la comilla simple 'einmediatamente después teclear la combinación ALT+0160 (carácter ASCII 160) usando el teclado numérico. Aunque obviamente no se visualiza el espacio si se conserva tal como puede verse en el ejemplo 1) Obsérvese que como la tipografía es de ancho variable y que según el carácter que se use como relleno se modifica el ancho de la presentación. Quizá convenga recordar que 32 es en este caso la variable a la que pretendemos dar formato y que ese valor podría ser sustituido por el nombre de una variable que contenga valores numéricos.

Alineación

Carácter Valor Sintaxis Resultado Ninguno Dcha printf("%020d",32) 00000000000000000032

- Izda printf("%0–20d",32) 32

Ninguno Dcha printf("%'*20d",32) ******************32 - Izda printf("%'*–20d",32) 32****************** Ninguno Dcha printf("%020s",32) 00000000000000000032

- Izda printf("%0–20s",32) 32000000000000000000

Ninguno Dcha printf("%'*20s",32) ******************32 - Izda printf("%'*–20s",32) 32******************

Observaciones En los casos en que figura Ninguno en la columna Caracter tratamos de indicar que no es necesario escribir nada en la cadena de formato. Cuando aparece un signo (–) estamos indicando que debe insertarse un signo menos. Fíjate que en los cuatro primeros supuestos el identificador de tipo es d, lo cual hace que considere la variable como numérica, mientras que en los cuatro últimos ese valor es s, con lo cual considera la variable como tipo cadena. Cuando tratamos de rellenar una variable numérica con ceros por la derecha PHP los omite para no alterar el valor numérico en la presentación Con cualquier otro carácter de relleno (incluidos los caracteres numéricos con ' delante) sí efectúa el relleno.

Ancho

Carácter Valor Sintaxis Resultado Entero 14 printf("%'*14d",32) ************32 Entero 17 printf("%'*-17d",32) 32*************** Decimal 14.5 printf("%'*14.5d",32) ************32 Decimal 17.8 printf("%'*-17.8d",32) 32*************** Decimal 14.5 printf("%'*14.5f",32) ******32.00000 Decimal 11.8 printf("%'*-11.8f",32) 32.00000000

Observaciones El ancho (nº de caracteres totales) puede especificarse mediante un número entero para todo tipo de variables Si se expresa mediante un número decimal y la variable es tipo coma flotante la parte decimal indica la precisión (nº de cifras decimales) y la parte entera el ancho como número de caracteres de la parte entera o de la parte decimal, según se rellene a la derecha o a la izquierda.

Tipo

Tipo Valor Sintaxis Resultado Presentación

en forma binaria b printf("%'*14b",17) *********10001

Caracter correspondiente al código ASCII

c printf("%'*14c",97) a

Número presentado como entero

d printf("%'*14d",17.83) ************17

Número presentado con decimales

f printf("%'*14f",17.45) *****17.450000

Presentación en forma octal

o printf("%'*14o",17) ************21

Presentación en hexadecimal

x printf("%'*14x",170) ************aa

Presentación en hexadecimal

X printf("%'*14X",170) ************AA

Presentación como >cadena

s printf("%'*14s",170) ***********170

Page 128: Manual PHP

Cadenas alfanuméricas (I)

Funciones sobre cadenas

Algunas de las funciones que permiten manejar los formatos de las cadenas de caracteres son estas:

chr(n)

Devuelve el carácter cuyo código ASCII es n.

ord(cadena)

Devuelve el código ASCII del primero de los caracteres de la cadena.

strlen(cadena)

Devuelve la longitud (número de caracteres) de la cadena. Los espacios son considerados como un caracter más.

strtolower(cadena)

Cambia todos los caracteres de la cadena a minúsculas.

strtoupper(cadena)

Convierte en mayúsculas todos los caracteres de la cadena.

ucwords(cadena)

Convierte a mayúsculas la primera letra de cada una de las palabras de la cadena.

ucfirst(cadena)

Convierte a mayúsculas la primera letra de la cadena y pone en minúsculas todas las demás.

ltrim(cadena)

Elimina todos los espacios que pudiera haber al principio de la cadena.

rtrim(cadena)

Elimina todos los espacios que existieran al final de la cadena.

trim(cadena)

Elimina los espacios tanto al principio como al final de la cadena.

Page 129: Manual PHP

chop(cadena)

Elimina los espacios al final de la cadena. Es idéntica a rtrim.

¡Cuidado! Tanto trim, como ltrim y rtrim eliminan, además de los espacios, las secuencias: \n, \r, \t, \v y \0; llamadas tambiéncaracteres protegidos. substr(cadena,n)

Si el valor de n es positivo extrae todos los caracteres de la cadena a partir del que ocupa la posición enésima a contar desde la izquierda.

Si el valor de n es negativo serán extraídos los n últimos caracteres contenidos en la cadena.

substr(cadena,n,m)

Si n y m son positivos extrae m caracteres a partir del que ocupa la posición enésima, de izquierda a derecha.

Si n es negativo y m es positivo extrae m (contados de izquierda a derecha) a partir del que ocupa la posición enésima contada de derecha a izquierda.

Si n es positivo y m es negativo extrae la cadena comprendida entre el enésimo carácter (contados de izquierda a derecha) hasta elemésimo, contando en este caso de derecha a izquierda

Si n es negativo y m también es negativo extrae la porción de cadena comprendida entre el emésimo y el enésimo caracteres contando, en ambos casos, de derecha a izquierda. Si el valor absoluto de n es menor que el de m devuelve una cadena vacía.

strrev(cadena)

Devuelve la cadena invertida

str_repeat(cadena, n)

Devuelve la cadena repetida tantas veces como indica n.

str_pad(cadena, n, relleno, tipo)

Añade a la cadena los caracteres especificados en relleno (uno o varios, escritos entre comillas) hasta que alcance la longitud que indica n (un número)

El parámetro tipo puede tomar uno de estos tres valores (sin comillas): STR_PAD_BOTH (rellena por ambos lados) STR_PAD_RIGHT(rellena por la derecha) STR_PAD_LEFT (rellena por la izquierda).

Si se omite la cadena de relleno utilizará espacios y si se omite el tipo rellenará por la derecha

str_replace(busca, sustituto, cadena)

Busca en la cadena las subcadenas de texto cuyo contenido es igual a busca, las reemplaza por el valor sustituto

Page 130: Manual PHP

substr_replace(cadena, sustituto, comienzo, longitud)

Sustituye por sustituto un número de caracteres igual a longitud contados a partir del que ocupa la posición comienzo dentro de lacadena original. Cuando el valor de comienzo es negativo contará de derecha a izquierda para determinar el lugar de comienzo de la sustitución. Si el valor de longitud es negativo no sustituirá al final de la cadena un número de carácteres igual al valor absoluto de longitud.

Estos son algunos ejemplos de aplicación de las funciones de manejo de cadenas

Código ASCII y viceversa

Función Ejemplo Resultado

chr(código ASCII) chr(97) a

ord("cadena") ord("abadesa") 97

Longitudes y conversiones mayúsculas/minúsculas

Función Ejemplo Resultado

strlen("cadena") strlen("Mide la longitud de esta cadena") 31

strtolower("cadena") strtolower("CONVIERTE A MINÚSCULAS") convierte a minÚsculas

strtoupper("cadena") strtoupper("pasa a mayúsculas") PASA A MAYúSCULAS

ucwords("cadena") ucwords("todas empiezan por mayúscula") Todas Empiezan Por Mayúscula

ucfirst("cadena") ucfirst("mayúscula al principio") Mayúscula al principio

Eliminar espacios

Función Ejemplo Resultado

ltrim("cadena") ltrim("\n \nEliminar espacios") Eliminar espacios

rtrim("cadena") rtrim("Eliminar espacios\n \n") Eliminar espacios

trim("cadena") trim("\n \nEliminar espacios\n \n") Eliminar espacios

chop("cadena") chop("\n \nEliminar espacios\n \n") Eliminar espacios

Extraer porciones de una cadena

Función Ejemplo Resultado

substr("cadena",n) substr("Extrae caracteres",3) rae caracteres

substr("cadena",n) substr("Extrae caracteres",0) Extrae caracteres

substr("cadena",n) substr("Extrae caracteres",-5) teres

substr("cadena",n) substr("Extrae caracteres",-2) es

substr("cadena",n,m) substr("Extrae caracteres",2,6) trae c

substr("cadena",n,m) substr("Extrae caracteres",0,8) Extrae c

substr("cadena",n,m) substr("Extrae caracteres",2,-3) trae caracte

substr("cadena",n,m) substr("Extrae caracteres",-7,5) acter

substr("cadena",n,m) substr("Extrae caracteres",-7,-5) ac

substr("cadena",n,m) substr("Extrae caracteres",-5,-7)

Modificaciones de cadenas

Función Ejemplo Resultado

strrev("cadena") strrev("Invierte la cadena") anedac al etreivnI

str_repeat("cadena",n) str_repeat("Rep",5) RepRepRepRepRep

str_pad("cadena",n,"Relleno",Tipo) str_pad("Pepe",10,"*",STR_PAD_BOTH) ***Pepe***

str_pad("cadena",n,"Relleno",Tipo) str_pad("Pepe",10,"*",STR_PAD_LEFT) ******Pepe

str_pad("cadena",n,"Relleno",Tipo) str_pad("Pepe",10,"*",STR_PAD_RIGHT) Pepe******

str_pad("cadena",n,"Relleno",Tipo) str_pad("Pepe",10,"*") Pepe******

str_replace ("lo que dice",lo que dira,"Cadena") str_replace("e","a","Pepe") Papa

str_replace ("lo que dice",lo que dira,"Cadena") str_replace("pe","pa","Pepepe") Pepapa

str_replace ("lo que dice",lo que dira,"Cadena") str_replace("Pepe","Luis","Pepe") Luis

substr_replace ("Cadena",lo que dira,n,m) substr_replace("Esta es una cadena es de prueba","*",5,2) Esta * una cadena es de prueba

substr_replace ("Cadena",lo que dira,n,m) substr_replace("Esta es una cadena es de prueba","*",5,-2) Esta *ba

substr_replace ("Cadena",lo que dira,n,m) substr_replace("Esta es una cadena es de prueba","*",-5,2) Esta es una cadena es de p*eba

substr_replace ("Cadena",lo que dira,n,m) substr_replace("Esta es una cadena es de prueba","*",-5,-2) Esta es una cadena es de p*ba

Page 131: Manual PHP

Formatos en cadenas

La funcion prinf tiene un comportamiento idéntico al comentado en la página anterior.

printf(cadena de formato,variable1,variable2,..)

Cadena de formato

Dentro de la cadena de formatos deben repetirse tantos formatos como variables se pretenda manejar "%[rell1][alin1][anc1][prec1][tipo1][sepa1]%[rell1][alin1][anc1][prec1][tipo1][sepa1]"

Hemos de mencionar aquí los separadores ya que no fueron mencionados en la página anterior Se puede introducir una cadena de separación al final de una cadena de formato que puede hacer, entre otras, función de separación entre dos cadenas. Por ejemplo, printf("%'*15.2f Euros",1475.875) nos devolvería: ********1475.88 Euros La función printf() permite presentar varios valores o variables con distintos formatos utilizando la sintaxis que se indica más arriba. Este ejemplo : printf("%'*15.2f Euros=%'*18.0f Pesetas",1475.875,1475.875*166.386) devuelve como resultado: ********1475.88 Euros=************245565 Pesetas

Existe otra función PHP con características muy similares a la anterior. Se trata de sprintf().

La sintaxis es idéntica sprintf (cadena de formato, variable1,variable2, ...) y su única diferencia con printf es que, mientras que printf()imprime las variables utilizando el formato indicado, sprintf() puede guardar en una nueva variable la cadena resultante de la aplicación del formato.

Cadenas alfanuméricas (II)

Funciones sobre cadenas

Como complemento a las descritas en la página anterior, añadimos aquí algunas otras funciones PHP que también permiten manejarcadenas de caracteres.

AddSlashes(cadena)

Inserta el carácter \ delante los siguientes: "(comillas dobles), ' (comillas simples), \ (escape) y NUL (el bit nulo).

stripslashes(cadena)

Quita las marcas añadidas a una cadena con la función AddSlashes().

Page 132: Manual PHP

chunk_split(cadena, nº caracteres, separador )

Devuelve la cadena cadena después de haberle insertado, cada nº caracteres, la cadena indicada en el parámetro separador. Si no se indica separador PHP pondrá un espacio. Si no se establece el parámetro nº caracteres insertará el separador cada 76 caracteres. Esta función coloca siempre un separador al final de la cadena.

parse_str(cadena)

Devuelve las variables –con su valor– indicadas dentro de la cadena (observa la sintaxis del ejemplo). Dentro de la cadena cada variable se denomina con un nombre que va seguido de un signo igual. Los espacios se señalan con el signo + y los separadores de variables son signos &

explode(separador, cadena,numero)

Devuelve un array cuyos elementos contienen cada una de las porciones de la cadena (cadena) comprendidas entre dos de los caracteres señalados como (separador) hasta el máximo de porciones señaladas (numero). Los caracteres separadores no son incluídos en las cadenas resultantes. Si no se indica la cantidad de porciones, será fraccionada toda la cadena. Si se indica numero el último trozocontendrá toda la cadena restante.

implode(separador, array)

Devuelve una cadena formada por todos los elementos del array separados mediante los caracteres indicados en separador.

join(separador, array)

Es idéntica a implode.

strtok(cadena, separador)

Esta función divide la cadena cadena en trozos delimitados por el separador que se indica en separador. Cuando se invoca la primera vez –extrae el primer trozo– debe llevar las sintaxis strtok(cadena,separador). Al invocarla sucesivamente, se escribe solo strtok(" ") e irá recogiendo de forma secuencial los trozos sucesivos.

Marcas, divisiones y uniones de cadenas

Variable cadena Sintaxis Resultado

$a="Esta ' y \ y también NUL" AddSlashes($a) $a="Esta \' y \\ y también NUL"

$a="Esta \' y \\ y también NUL" stripslashes($a) Esta ' y y también el nulo

$a="Esta es una cadena larga que presuntamente será troceada" chunk_split($a,5,"–") Esta –es un–a cad–ena l–arga –que p–resun–tamen–te se–rá tr–ocead–a–

$a="Esta es una cadena larga que presuntamente será troceada" chunk_split($a,5) Esta es un a cad ena l arga que p resun tamen te se rá tr ocead a

$a="Esta es una cadena larga que presuntamente será troceada" chunk_split($a,76,"-") Esta es una cadena larga que presuntamente será troceada-

$todo="v1=Esto+sera+una+variable&v2=esto+otra&p[]=incluso+un+array"

Divide la cadena $todo en sus componentes

parse_str($todo); echo $v1; echo $v2; echo $p[0];

Esto sera una variable esto otra incluso un array

$a="Esta cadena sera devuelta en trozos"

Recogerá en un array cada uno de los trozos delimitados por los separadores

$trozo1=explode(" ",$a); echo $trozo1[0]; echo $trozo1[1]; echo $trozo1[2]; echo $trozo1[3]; echo $trozo1[4]; echo $trozo1[5]; >

Esta cadena sera devuelta en trozos

Recogerá en un array cada uno de los trozos delimitados por los separadores

$trozo2=explode("a",$a); echo $trozo2[0]; echo $trozo2[1]; echo $trozo2[2]; echo $trozo2[3]; echo $trozo2[4];

Est c den ser devuelt en trozos

Page 133: Manual PHP

echo $trozo2[5];

Recogerá en un array cada uno de los trozos delimitados por los separadores hasta un máximo de 3 Anteponemos @ a print para evitar mensajes de error en los valores vacíos

$trozo3=explode(" ",$a,3); @print $trozo3[0]; @print $trozo3[1]; @print $trozo3[2]; @print $trozo3[3]; @print $trozo3[4]; @print $trozo3[5];

Esta cadena sera devuelta en trozos

Recogerá en un array cada uno de los trozos delimitados por los separadores hasta un máximo de 3 Anteponemos @ a print para evitar mensajes de error en los valores vacíos

$trozo4=explode("a",$a,3); @print $trozo4[0]; @print $trozo4[1]; @print $trozo4[2]; @print $trozo4[3]; @print $trozo4[4]; @print $trozo4[5];

Est c dena sera devuelta en trozos

implode(" ",$trozo1) Esta cadena sera devuelta en trozos

implode("*",$trozo2) Est* c*den* ser* devuelt* en trozos

implode("-",$trozo3) Esta-cadena-sera devuelta en trozos

implode(":",$trozo4) Est: c:dena sera devuelta en trozos

join(" ",$trozo1) Esta cadena sera devuelta en trozos

join("*",$trozo2) Est* c*den* ser* devuelt* en trozos

join("-",$trozo3) Esta-cadena-sera devuelta en trozos

join(":",$trozo4) Est: c:dena sera devuelta en trozos

$cadena="Esta cadena será dividida con la función strtok"

$trocin = strtok ($cadena," "); while ($trocin) { echo "$trocin<br>"; $trocin = strtok (" "); }

Esta cadena será dividida con la función strtok

$trocin = strtok ($cadena," "); echo $trocin,"<br>"; $trocin1 = strtok (" "); echo $trocin1,"<br>"; $trocin2 = strtok (" "); echo $trocin2,"<br>";

Esta cadena Esta

$trocin = strtok ($cadena,"a"); while ($trocin) { echo "$trocin<br>"; $trocin = strtok ("a"); }

Est c den será dividid con l función strtok

Encriptación de cadenas

PHP dispone de funciones que permiten codificar o encriptar cadenas de caracteres.

bin2hex(cadena)

Devuelve una cadena ASCII que contiene la representación hexadecimal de la cadena. La conversión se realiza byte a byte, con los 4 bits superiores primero.

crypt(cadena)

Devuelve la cadena encriptada utilizando una semilla aleatoria de dos caracteres. Por su caracter aleatorio, si se ejecuta dos veces seguidas –tal como puedes observar en el ejemplo– dará dos resultados diferentes.

crypt(cadena,"xx")

Devuelve la cadena encriptada utilizando como semilla los dos caracteres (entre comillas) que se escriben como segundo parámetro de la función. Tanto en este supuesto como en el anterior, los dos primeros caracteres de la cadena encriptada coinciden con los que han sido utilizados como semilla.

Page 134: Manual PHP

md5(cadena)

Aplica el algoritmo md5 y devuelve la huella digital generada por él. Este algoritmo presenta como peculiaridades que –tenga la dimensión que tenga la cadena a la que se aplique– genera siempre una huella digital que no es otra cosa que una cadena formada por 32 caracteres y que no dispone de ningún mecanismo inverso.

Seguramente habrás vivido esta experiencia. En muchos espacios de Internet –grupos de noticias, cuentas de correo web, etcétera– que requieren un login y una contraseña cuando utilizas la opción de recuperar contraseñas no te envían tu contraseña anterior, sino que te generan y envían una nueva.

Esto ocurre porque, por razones evidentes de seguridad, las contraseñas se almacenan usando estas huellas digitales y resulta imposible recuperar los valores originales.

La única solución en estos casos es crear una nueva contraseña (suelen hacerlo con las funciones de números aleatorios), enviarla de forma automática por correo electrónico y sustituir el valor anterior del registro de usuarios por el resultado de la codificación md5 de la nueva contraseña.

crc32(cadena)

Aplica el algoritmo crc32 de comprobación de integridad y devuelve el valor del mismo. Se utiliza muchísimo en los programas decompresión y descompresión de ficheros. Se aplica en el momento de comprimir y se incluye el valor obtenido dentro del fichero comprimido. Después de la descompresión se vuelve a aplicar el mismo algoritmo y se comparan ambos valores. La coincidencia será la garantía de que el fichero obtenido es idéntico al original.

Encriptaciones y codificaciones

Variable cadena Sintaxis Resultado

$a="Esta es la cadena" bin2hex($a) 45737461206573206c6120636164656e610a

$a="Encriptame" crypt($a) $1$b81rcmj0$p63mW.bk0jMSf51dZiLV10

$a="Encriptame" crypt($a) $1$ci1i4JWI$LEpFUE5kkRytnOFmXdmS//

$a="Encriptame" crypt($a,"zq") zqQ4qOeELzPFg

$a="Encriptame" crypt($a,"zq") zqQ4qOeELzPFg

$a="Encriptame" crypt($a,"@$") @$MB08wYjH9to

$a="Encriptame" md5($a) 67c3ca0aefda2595138168a85e7b33a0

$a="Encriptame" md5($a) 67c3ca0aefda2595138168a85e7b33a0

$a="Encriptame" crc32($a) 3166777410

Búsquedas y recuentos de caracteres

Variable cadena Sintaxis Resultado

$a="Contando caracteres" count_chars($a,0) Array

$a="Contando caracteres" $b=count_chars($a,0); echo $b[97];

3

$a="Contando caracteres" $b=count_chars($a,0); echo $b[ord("o")]

2

$a="Pepe Perez el perverso pecador en penitencia"

substr_count($a,"Pe"); 2

$a="Pepe Perez el perverso pecador enpenitencia"

substr_count($a,"pe"); 4

La función count_char($a,0) devuelve un array cuyos índices son los códigos ASCII de los caracteres y cuyos valores son el número de veces que se repite cada uno de ellos.

La función substr_count($a,"cadena") determina el número de veces que aparece la cadena dentro de $a. Diferencia entre mayúsculas y minúsculas.

Page 135: Manual PHP

Etiquetas HTML

Dos interpretaciones

Recordemos que cuando un documento tiene extensión php es objeto de dos interpretaciones antes de ser visualizado en el navegador. En primer lugar es PHP quien ejecuta sus scripts y devuelve al cliente el documento resultante y, más tarde, será el propio navegador quien realice una segunda interpretación –del documento recibido– cuyo resultado será la visualización de la página.

Cualquier salto de línea que se inserte en un documento será respetado por PHP y enviado al navegador, pero, como éste solo entiende como tales sus propias etiquetas <br>, no serán visualizados, aunque sí estarán en el código fuente de la página visualizada.

Hemos de considerar un nuevo concepto. PHP permite utilizar algunos caracteres especiales que son transformados durante la ejecución del script. Uno de ellos –no es el único– es \n que es interpretado y convertido por el intérprete de PHP en un salto de línea cuyo efecto será visible en el código fuente del documento enviado al navegador, pero que –por no ser una etiqueta <br>– no producirá efecto alguno en la visualización de la página.

nl2br($A)

Esta función inserta de forma automática etiquetas HTML de salto de línea (<br>). Por cada salto de línea que exista en el texto de la cadena inserta una etiqueta <br> en la salida HTML.

strtr($array,$diccionario)

Busca en la cadena $a las palabras coincidentes con los índices del array asociativo $diccionario y las sustituye por los valoresasociados a esos índices.

get_meta_tags($a)

Devuelve un array asociativo cuyos índices son los valores de la propiedad name de las etiquetas <meta> (escritas siempre en minúsculas, sin comillas, y reemplazando, en el caso de que fuera necesario, los caracteres especiales por un guión bajo _) de la página web cuya dirección (absoluta o relativa) se indica en la cadena $a. Los valores de los elementos del array son los contenidos de esas etiquetas.

htmlspecialchars($a)

Convierte en entidades de HTML los caracteres &, ", < y >. Con ello se consigue su visualización y se impide que sean interpretados como signos del lenguaje HTML. htmlentities($a)

Es una función similar a la anterior, pero en este caso afecta a todos los caracteres que tienen equivalentes como entidad HTMLutilizando el juego de carácteres ISO-8859-1. Recuerda que son éstos.

Este es un ejemplo de uso de la citadas funciones

<?php /* definamos algunas variables de cadena tal como se describe en sus contenidos e incluyamos caracteres especiales \n */ $cadena1="Este texto está escrito en varias líneas y hemos saltado de una a otra pulsando enter";

Page 136: Manual PHP

$cadena2="Aquí\nseparamos\nlas\nlíneas\ncon\nsin\npulsar\nenter"; $cadena3=<<<Prueba Nuevamente texto en varias lineas ahora usando sintaxis de documento incrustado. Seguiremos probando Prueba; $cadena4=<<<OtraPrueba Ahora\ninsertaré\nalgo\ncomo\nesto OtraPrueba; # definamos una variable conteniendo saltos de linea HTML # y vayamos construyendo una variable de salida # en la que uniremos las variables anteriores insertando # entre ellas saltos de linea para facilitar la visualización # en el navegador $saltador="<br><br><br>"; $salida=$cadena1.$saltador; $salida .=$cadena2.$saltador; $salida .=$cadena3.$saltador; $salida .=$cadena4.$saltador; # visualicemos el resultado print $salida; #apliquemos ahora a la variable salida # la funcion nl2br y veamos el resultado print $saltador.nl2br($salida); ?>

ejemplo39.php

Si ejecutas el ejemplo y visualizas el código fuente a través del navegador podrás observar como los resultados del primer printgeneran saltos de línea en éste. Y en el caso del código correspondiente al segundo print, podrás visualizar etiquetas <BR /> que son el resultado de la aplicación de la función nl2br

Las cadenas y las etiquetas HTML

Cadenas Sintaxis Resultado

$a="Esto es\nun texto escrito\n en varias lineas\nsin etiquetas<BR>";

nl2br($a)

Esto es un texto escrito en varias lineas sin etiquetas<BR>

$dicc=array ( "Lundi"=>"Lunes", "good"=>"bueno", "sun" =>"sol"); $a="Lundi es un dia good si hace sun"

strtr($a,$dicc) Lunes es un dia bueno si hace sol

$a="index.php" $b=get_meta_tags($a); echo $b['keywords']

Programación, PHP, Apache, MySQL

$a="index.php" $b=get_meta_tags($a); echo $b['description']

Materiales para un curso a distancia

$a="<H1>A</H1>" echo $a A $a="<H1>A</H1>"

echo htmlspecialchars($a)

<H1>A</H1>

La tabla de sustituciones de htmlspecialchars es esta: Sustitye & por &amp; " por &quot;

Page 137: Manual PHP

< por &lt; > por &gt;

Operadores bit a bit

Comentario previo

Incluimos la sintaxis de este tipo de operadores a título meramente informativo. Rara vez será necesario utilizarlos en nuestras aplicaciones PHP. Su utilidad suele limitarse a la gestión de periféricos y algunas operaciones de cálculo de carácter muy reiterativo en la que se puede conseguir un rendimiento muy superior a los operadores tradicionales.

En el ámbito propio del PHP pueden tener algún interés a la hora de elaborar rutinas para encriptar el código fuente de algunos scripts que por su importancia pueden requerir ese tipo de protección. Los que sí han de resultarnos de gran interés serán el resto de los operadores. Los iremos viendo en páginas sucesivas.

Operadores bit a bit

$A & $B

El operador & compara los valores binarios de cada uno de los bits de las cadenas $A y $B y devuelve 1 en el caso que ambos sean 1, y 0 en cualquier otro caso.

Cuando las variables $A y $B son cadenas compara los valores binarios de los códigos ASCII de sus caracteres y devuelve los caracteres ASCII correspondientes al resultado de esa comparación.

El operador &

Números Números como cadenas Cadenas alfanuméricas

Variables Valores binarios Variables Valores binarios Variables Valores binarios

$a=12 1100 $A="12" 110001110010 $A1="Rs" 10100101110011

$b=23 10111 $B="23" 110010110011 $B1="aZ" 11000011011010

$a&$b=4 100 $A&$B=02 110000110010 $A1&$B1=@R 10000001010010

En los casos de cadenas hemos diferenciado en rojo el valor binario correspondiente al primer carácter. Esos valores binarios corresponden a la forma binaria del código ASCII de cada uno de los caracteres

Un caso de aplicación práctica podría se la determinación de la condición de par o impar de un número. El resultado de una comparación $a & 1 significaría que si el valor asignado a $a fuera un número impar su forma binaria tendría como último carácter (por la derecha) un uno. Por el contrario, si fuera par ese último carácter sería cero. Luego la comparación mediante el operador & arrojaría 1 (coincidencia) en el primero caso y 0 (no coincidencia en el segundo). Si ejecutamos este script: $a=13; print ($a & 1); veríamos que el resultado es 1 mientras que si hiciéramos lo mismo con $a=14; print ($a & 1); resultaría 0. Sería una opción alternativa a la de ver si el resto de dividir el valor de la variable entre 2 ($a=13; print ($a % 2);) es 1 (no divisible y en consecuencia impar) o si $a=14; print ($a % 2); da 0(divisible, ya que su resto es cero, y en consecuencia se trata de un número par).

$A | $B

Funciona de forma idéntica al anterior y devuelve 1 cuando al menos el valor de uno de los bits comparados es 1, y devolverá 0 cuandoambos sean 0.

El operador |

Números Números como cadenas Cadenas alfanuméricas

Variables Valores binarios Variables Valores binarios Variables Valores binarios

$a=12 1100 $A="12" 110001110010 $A1="Rs" 10100101110011

Page 138: Manual PHP

$b=23 10111 $B="23" 110010110011 $B1="aZ" 11000011011010

$a|$b=31 11111 $A|$B=33 110011110011 $A1|$B1=s{ 11100111111011

Puedes observar que el tratamiento es distinto cuando los mismos valores numéricos se asignan como entero y como cadena. Al asignarlos como cadena opera los valores binarios de los códigos ASCII de los caracteres, mientras que cuando se trata de números compara los valores de las expresiones binarias de los valores de cada uno de ellos

$A ^ $B

Devuelve 1 cuando los bits comparados son distintos, y 0 cuando son iguales.

El operador ^

Números Números como cadenas Cadenas alfanuméricas

Variables Valores binarios Variables Valores binarios Variables Valores binarios

$a=12 1100 $A="12" 110001110010 $A1="Rs" 10100101110011

$b=23 10111 $B="23" 110010110011 $B1="aZ" 11000011011010

$a^$b=27 11011 ��������$A^$B= 000011000001 $A1^$B1=3) 01100110101001

$A << $B

Realiza la operación $A * 2$B. Hace el cálculo añadiendo $B CEROS (binarios) a la derecha de la cadena binaria $A.

El operador <<

Números Números como cadenas Cadenas alfanuméricas

Variables Valores binarios Variables Valores binarios Variables Valores binarios

$a=12 1100 $A="12" 110001110010 $A1="Rs" 10100101110011

$b=2 10 $B=2 10 $B1=2 10

$a<<b=48 110000 $A<<$B=48 110000 $A1<<$B1=0

El operador << multiplica el valor de la primera cadena por 2 elevado al valor de la segunda. Al ser un operador matemático solo tiene sentido cuando ambas variables son números naturales. En las cadenas alfanuméricas extrae los números que pudiera haber al comienzo y, en caso de no haberlos, toma valor cero.

$A >> $B

Divide el valor $A entre 2$B. Hace la operación en la cadena binaria quitando $B CEROS (por la derecha) de la cadena $A.

El operador >>

Números Números como cadenas Cadenas alfanuméricas

Variables Valores binarios Variables Valores binarios Variables Valores binarios

$a=12 1100 $A="12" 110001110010 $A1="Rs" 10100101110011

$b=2 10 $B=2 10 $B1=2 10

$a>>b=3 11 $A>>$B=3 11 $A1>>$B1=0

Para este operador (>>) son aplicables los mismos comentarios hechos en el párrafo anterior.

Page 139: Manual PHP

~ $A

Invierte los valores de los bits de la cadena $A convirtiendo los CEROS en UNO y los UNO en CERO.

El operador ~

$a=12 1100

~$a=-13 1111111111111111111111111111111111111111111111111111111111110011

Operadores de comparación

Si es este tu primer contacto con el mundo de la programación es probable que estés pensando que todo esto está muy bien pero que a ti lo que te interesa es hacer cosas. En una palabra, que quieres usar PHP como herramienta para el desarrollo de tus proyectos.

Esta página y las siguientes van a ser básicas para el éxito en ese lógico y razonable afán utilitarista. Pensemos en nuestro día a día. En cada momento de nuestra vida hacemos algo. Cada cosa que hacemos suele requerir casi siempre esta secuencia de acciones:comparar, evaluar, optar y hacer.

Supongamos que queremos cambiar de coche. Lo más prudente será empezar por comparar las características (potencia, diseño, precio, etc.) de los diferentes modelos. Salvo en casos excepcionales, no tomaríamos una decisión de compra a través de un solo parámetro sino que haríamos una evaluación conjunta de todos esos factores (menos potencia pero mejor precio y diseño muy similar, por citar un ejemplo) y sería a través de esa evaluación como optaríamos por una marca o modelo.

Una vez ejercida la opción –y no antes– sería el momento de realizar la compra del nuevo coche. PHP, y en general todos los lenguajes de programación, disponen de herramientas que permiten emular cada uno de esos procesos de la conducta humana. Los iremos viendo en esta página y en las siguientes.

Operadores de comparación

PHP dispone de los siguientes operadores de comparación:

$A == $B

El operador == compara los valores de dos variables y devuelve 1 (CIERTO) en el caso de que sean iguales y el valor NUL –carácter ASCII 0– (FALSO) cuando son distintas.

Mediante este operador se pueden comparar variables de distinto tipo. Para comparar una cadena con un número se extrae el valor entero de la cadena (si lleva dígitos al comienzo los extrae y en caso contrario le asigna el valor cero) y utiliza ese valor para hacer la comparación.

Cuando se comparan cadenas discrimina entre mayúsculas y minúsculas ya que utiliza los códigos ASCII de cada uno de los caracteres para hacer la comparación que se hace de izquierda a derecha y devuelve 1 (CIERTO) sólo en el caso que coincidan exactamente los contenidos de ambas cadenas.

El operador ==

A B Operador Sintaxis Resultado

$A=123 $B=123 == $A==$B 1

$A=123.0 $B=123 == $A==$B 1

$A=123 $B="123" == $A==$B 1

$A=123 $B="123ABC" == $A==$B 1

$A=123 $B=124 == $A==$B

$A=123 $B=124 == ord($A==$B) 0

$A="abc" $B="ABC" == $A==$B

$A="abc" $B="abc" == $A==$B 1

$A="abc" $B="abcd" == $A==$B

Page 140: Manual PHP

Los valores de la columna señalada como Resultados se obtienen mediante la función echo($A==$B); Podemos ver que, en los casos que es cierta la igualdad, imprime un 1, mientras que cuando es falsa no imprime nada. Se justifica este extremo porque en caso de no coincidencia el valor devuelto es NUL (código ASCII 0) y ese carácter carece de símbolo gráfico. Hemos marcado en rojo una excepción. En ese caso, la instrucción es echo ord($A==$B); y –como recordarás– dado que ord, que devuelve el código ASCII del caracter contenido en el paréntesis que le acompaña, podemos comprobar a través del cero de este resultado, que, efectivamente, la no coincidencia devuelve NUL.

$A === $B

El operador === es similar al anterior, pero realiza la comparación en sentido estricto. Para que devuelva 1 es necesario que sean iguales los valores de las variables y también su tipo.

El operador ===

A B Operador Sintaxis Resultado

$A=123 $B=123 === $A===$B 1

$A=123.0 $B=123 === $A===$B

$A=123 $B="123" === $A===$B

$A=123 $B="123ABC" === $A===$B

$A=123 $B=124 === $A===$B

$A=123 $B=124 === ord($A===$B) 0

$A="abc" $B="ABC" === $A===$B

$A="abc" $B="abc" === $A===$B 1

$A="abc" $B="abcd" === $A===$B

Observa que los valores señalados en rojo –a diferencia de lo que ocurre con el operador anterior– devuelvenNUL como resultado. En este caso no sólo compara valores sino que también compara tipos de variables. Al ser una de ellas tipo numérico y la otra cadena alfanumérica el resultado no puede ser otro que NUL.

$A != $B

El operador != devuelve 1 cuando los valores de las variables son distintos (en general ! indica negación, en este caso podríamos leer«no igual») y devuelve NUL cuando son iguales.

Este operador no compara en sentido estricto, por lo que puede considerar iguales los valores de dos variables de distinto tipo.

El operador !=

A B Operador Sintaxis Resultado

$A=123 $B=123 != $A!=$B

$A=123.0 $B=123 != $A!=$B

$A=123 $B="123" != $A!=$B

$A=123 $B="123ABC" != $A!=$B

$A=123 $B=124 != $A!=$B 1

$A=123 $B=124 != ord($A!=$B) 49

$A="abc" $B="ABC" != $A!=$B 1

$A="abc" $B="abc" != $A!=$B

$A="abc" $B="abcd" != $A!=$B 1

En los ejemplos señalados en rojo Puedes comprobar el carácter no estricto de este operador. Devuelve NUL porque considera que no son distintos, lo que equivale a interpretar que los considera iguales pese a que las variables sean de distinto tipo.

Page 141: Manual PHP

$A < $B

El operador < devuelve 1 cuando los valores de $A son menores que los de $B .

Los criterios de comparación son los siguientes:

� – Los valores numéricos siguen el criterio matemático.

� – Cuando se trata de un número y una cadena extrae el valor numérico de ésta (cero si no hay ningún dígito al principio de la misma) y hace una comparación matemática.

� – En el supuesto de dos cadenas, compara uno a uno –de izquierda a derecha– los códigos ASCII de cada uno de los carácteres (primero con primero, segundo con segundo, etcétera). Si al hacer esta comprobación encuentra –en la primera cadena– un caracter cuyo código ASCII es mayor que el correspondiente de la segunda cadena, o encuentra que todos son iguales en ambas cadenas devuelve NUL. Solo en el caso de no exitir ninguno mayor y sí haber al menos uno menor devolverá UNO.

� – Cuando las cadenas tengan distinta longitud, considerá (a efectos de la comparación) que los caracteres que faltan en la cadena más corta son NUL (ASCII 0).

El operador <

A B Operador Sintaxis Resultado

$A=123 $B=123 < $A<$B

$A=123 $B="123" < $A<$B

$A=123.0 $B="123" < $A<$B

$A=123 $B="123ABC" < $A<$B

$A=123 $B=124 < $A<$B 1

$A=123 $B=124 < ord($A<$B) 49

$A="abc" $B="ABC" < $A<$B

$A="abc" $B="abc" < $A<$B

$A=" bcd" $B="abcd" < $A<$B 1

$A="aacd" $B="abcd" < $A<$B 1

$A="abc" $B="abcd" < $A<$B 1

$A="abcd" $B="abc" < $A<$B

$A="A" $B="a" < $A<$B 1

$A="a" $B="A" < $A<$B

$A="aBC" $B="A" < $A<$B

$A="123" $B=124 < $A<$B 1

$A=123 $B="124" < $A<$B 1

Observa los ejemplos señalados en rojo. Cuando las cadenas tienen distinta longitud este operador considera que los caracteres que faltan en la cadena más corta son NUL. Esa es la razón por la que en el primer caso devuelve CIERTO (NUL es menor que d) y el segundo FALSO (d no es menor que NUL).

$A <= $B

Se comporta de forma idéntica al anterior. La única diferencia es que ahora aceptará como ciertos los casos de igualdad tanto en el caso de números como en el de códigos ASCII.

El operador <=

A B Operador Sintaxis Resultado

$A=123 $B=123 <= $A<=$B 1

$A=123.0 $B=123 <= $A<=$B 1

$A=123 $B="123" <= $A<=$B 1

$A=123 $B="123ABC" <= $A<=$B 1

$A=123 $B=124 <= $A<=$B 1

$A=123 $B=124 <= ord($A<=$B) 49

Page 142: Manual PHP

$A="abc" $B="ABC" <= $A<=$B

$A="abc" $B="abc" <= $A<=$B 1

$A="abc" $B="abcd" <= $A<=$B 1

$A="A" $B="a" <= $A<=$B 1

$A="a" $B="A" <= $A<=$B

$A="aBC" $B="A" <= $A<=$B

$A="123" $B=124 <= $A<=$B 1

$A=123 $B="124" <= $A<=$B 1

Hemos modificado la instrucción marcada en rojo para comprobar el código ASCII de caracter que devuelve, en el caso de ser cierto, el resultado de la comparación. El valor que aparece (49) como resultado es el código ASCII del carácter 1.

$A > $B

Es idéntico –en el modo de funcionamiento– a $A < $B. Solo difiere de éste en el criterio de comparación que ahora requerirá que los valores de $A sean mayores que los de la variable $B.

El operador >

A B Operador Sintaxis Resultado

$A=123 $B=123 > $A>$B

$A=123 $B="123" > $A>$B

$A=123 $B="123ABC" > $A>$B

$A=123 $B=124 > $A>$B

$A=123 $B=124 > ord($A>$B) 0

$A="abc" $B="ABC" > $A>$B 1

$A="abc" $B="abc" > $A>$B

$A="abc" $B="abcd" > $A>$B

$A="A" $B="a" > $A>$B

$A="a" $B="A" > $A>$B 1

$A="aBC" $B="A" > $A>$B 1

$A="123" $B=124 > $A>$B

$A=123 $B="124" > $A>$B

$A >= $B

Añade al anterior la posibilidad de certeza en caso de igualdad.

El operador >=

A B Operador Sintaxis Resultado

$A=123 $B=123 >= $A>=$B 1

$A=123 $B="123" >= $A>=$B 1

$A=123 $B="123ABC" >= $A>=$B 1

$A=123 $B=124 >= $A>=$B

$A=123 $B=124 >= ord($A>=$B) 0

$A="abc" $B="ABC" >= $A>=$B 1

$A="abc" $B="abc" >= $A>=$B 1

$A="abc" $B="abcd" >= $A>=$B

$A="A" $B="a" >= $A>=$B

$A="a" $B="A" >= $A>=$B 1

$A="aBC" $B="A" >= $A>=$B 1

$A="123" $B=124 >= $A>=$B

$A=123 $B="124" >= $A>=$B

Page 143: Manual PHP

Operadores lógicos

Operadores lógicos

Mediante operadores lógicos es posible evaluar un conjunto de variables lógicas, es decir, aquellas cuyos valores sean únicamente: VERDADERO o FALSO (1 ó NUL). El resultado de esa evaluación será siempre 1 ó NUL.

$A AND $B

El operador AND devuelve VERDADERO (1) en el caso de que todas las variables lógicas comparadas sean verdaderas, y FALSO (NUL) cuando alguna de ellas sea falsa.

El operador AND

Variables $a=32; $b=0; $c=-7; $d=4.32; $f="23 Ptas"

Ejemplo de sintaxis $x=$a>$b;$y=$a>$c;$z=$a>$f; echo ($x AND $y AND $z);

Otra sintaxis echo ($a>$b AND $a>$c AND $a>$f);

Condición A Condición B Condición C A B C Resultado

$a>$b $a>$c $a>$f 1 1 1 1

$a>$b $a>$c $a<$f 1 1

$a>$b $a<$c $a>$f 1 1

$a<$b $a>$c $a>$f 1 1

$a<$b $a<$c $a>$f 1

$a<$b $a>$c $a<$f 1

$a>$b $a<$c $a<$f 1

$a<$b $a<$c $a<$f

$A && $B

El operador && se comporta de forma idéntica al operador AND. La única diferencia entre ambos es que operan con distinta precedencia. Más abajo veremos el orden de precedencia de los distintos operadores.

El operador &&

Variables $a=32; $b=0; $c=-7; $d=4.32; $f="23 Ptas"

Condición A Condición B Condición C A B C Resultado

$a>$b $a>$c $a>$f 1 1 1 1

$a>$b $a>$c $a<$f 1 1

$a>$b $a<$c $a>$f 1 1

$a<$b $a>$c $a>$f 1 1

$a<$b $a<$c $a>$f 1

$a<$b $a>$c $a<$f 1

$a>$b $a<$c $a<$f 1

$a<$b $a<$c $a<$f

$A OR $B

Para que el operador OR devuelva VERDADERO (1) es suficiente que una sola de las variables lógicas comparadas sea verdadera. Únicamente devolverá FALSO (NUL) cuando todas ellas sean FALSAS.

El operador OR

Page 144: Manual PHP

Variables $a=32; $b=0; $c=-7; $d=4.32; $f="23 Ptas"

Condición A Condición B Condición C A B C Resultado

$a>$b $a>$c $a>$f 1 1 1 1

$a>$b $a>$c $a<$f 1 1 1

$a>$b $a<$c $a>$f 1 1 1

$a<$b $a>$c $a>$f 1 1 1

$a<$b $a<$c $a>$f 1 1

$a<$b $a>$c $a<$f 1 1

$a>$b $a<$c $a<$f 1 1

$a<$b $a<$c $a<$f

$A || $B

El operador || se comporta de forma idéntica al operador OR. Su única diferencia es el orden de precedencia con el que opera.

El operador ||

Variables $a=32; $b=0; $c=-7; $d=4.32; $f="23 Ptas"

Condición A Condición B Condición C A B C Resultado

$a>$b $a>$c $a>$f 1 1 1 1

$a>$b $a>$c $a<$f 1 1 1

$a>$b $a<$c $a>$f 1 1 1

$a<$b $a>$c $a>$f 1 1 1

$a<$b $a<$c $a>$f 1 1

$a<$b $a>$c $a<$f 1 1

$a>$b $a<$c $a<$f 1 1

$a<$b $a<$c $a<$f

$A XOR $B

El operador XOR devuelve VERDADERO (1) sólo en el caso de que sea cierta una sola de las variables, y FALSO (NUL) cuando ambas sean ciertas o ambas sean falsas.

El operador XOR

Variables $a=32; $b=0; $c=-7; $d=4.32; $f="23 Ptas"

Condición A Condición B A B Resultado

$a>$b $a>$c 1 1

$a>$b $a<$c 1 1

$a<$b $a>$c 1 1

$a<$b $a<$c

! $A

Este operador NOT (negación) devuelve VERDADERO (1) si la variable lógica $A es FALSA y devuelve FALSO (NUL) si el valor de esa variable $A es VERDADERO.

Sintaxis alternativa

Tal como hemos descrito los distintos operadores lógicos sería necesario que $A y $B contuvieran valores lógicos, y eso requeriría un paso previo para asignarles valores de ese tipo. Habría que recurrir a procesos de este tipo:

Page 145: Manual PHP

$A = $x>$y; $B= $x >=$z; $A && $B;

pero se obtendría el mismo resultado escribiendo:

$x>$y && $x >=$z;

que, aparte de ser la forma habitual de hacerlo, nos evita dos líneas de instrucciones. Aunque el propio ejemplo se auto comenta, digamos que al utilizar operadores lógicos se pueden sustituir las variables lógicas por expresiones que den como resultado ese tipo de valores.

Orden de precedencia

Cuando se usan los operadores lógicos se plantean situaciones similares a lo que ocurre con las operaciones aritméticas. Dado que permiten trabajar con secuencias de operaciones sería posible, por ejemplo, una operación de este tipo:

$a<$b OR $c<$b && $a<3

Surgirían preguntas con estas:

� – ¿qué comparación se haría primero OR ó &&?

� – ¿se harían las comparaciones en el orden natural?

� – ¿alguno de los operadores tiene prioridad sobre el otro?

Igual que en las matemáticas, también aquí, hay un orden de prioridad que es el siguiente: NOT, &&, ||, AND, XOR y, por último, OR.

De esta forma la operación && se realizaría antes que ||, mientras que si pusiéramos AND en vez de && sería la operación || la que se haría antes y, por lo tanto, los resultados podrían variar de un caso a otro.

Aquí también es posible, de la misma manera que en la aritmética, utilizar paréntesis para priorizar una operación frente a otra.

Es muy importante prestar atención a la construcción correcta de estas estructuras. Un descuido en la atención a las prioridades puede ser origen –lo es frecuentemente– de resultados incoherentes que suelen ser detectados bajo una apariencia aleatoria. Ten muy en cuenta que al depurar programas no siempre se ven fácilmente este tipo de errores de programación. Puede que para determinados valores los resultados sean los esperados y que, sin embargo, al utilizar otros distintos pueda manifestarse la incoherencia. Si te ocurriera esto no pienses que es el ordenador el que está haciendo cosas raras, procura revisar los paréntesis y los criterios de prioridad de los distintos operadores contenidos en la estructura lógica.

<?php # asignemos valores a cuatro variables $a=3; $b=6; $c=9; $d=17; # utilicemos operadores de comparación # y recojamos sus resultados en nuevas variables $x= $a<$b; $y= $a<$b; $z=$c>$b; # apliquemos un operador lógico (por ejemplo &&) # e imprimamos el resultado print("Resultado FALSO si no sale nada: ".($y && $z)."<br>");

Page 146: Manual PHP

# hagamos la misma comparación sin utilizar la variables $y y $z # que deberá darnos el mismo resultado print("<br>Resultado FALSO si no sale nada: ".($a<$b && $c> $b)."<br>"); /* veamos ahora qué ocurre al ampliar la estructura ¿qué ocurriría si escribíeramos $a <$b OR $c> $b && $d<$a ? El operador && tiene preferencia ante OR, luego haría primero la comparación $c>$b && $d<$a 9 > 6 es cierto, 17 < 3 es falso, luego como && solo devuelve cierto cuando ambos sean ciertos el resultado de esa opción es FALSO. Veamos qué ocurre al comparar $a<$b OR falso (resultado anterior) como 3 < 6 es cierto OR operará con cierto y falso que dará como resultado CIERTO, ya que basta que se cumpla una de las condiciones */ /* vamos a comprobarlo mediante este operador condicional no conocemos aun su sintaxis pero adelántemosla un poco... si el contenido del paréntesis que va detrás del if es cierto imprimirá cierto y en caso contrario imprimirá falso Aqui debería imprimirnos cierto */ if($a <$b OR $c>$b && $d<$a) { print "cierto<br>"; }else{ print "falso<br>"; } # Cambiemos la estructura anterior por $a<$b || $c>$b AND $d<$a # ahora se operará primero || que como antes dará cierto # pero ese resultado operado mediante AND con falso dará FALSO # AL CAMBIAR LOS OPERADORES POR SUS SIMILARES el resultado el DISTINTO if($a<$b || $c>$b AND $d<$a) { print "cierto<br>"; }else{ print "falso<br>"; } # un paréntesis nos devolverá a la situación anterior # escribamos $a<$b || ($c>$b AND $d<$a) # veremos que el resultado es CIERTO como ocurría en el primer caso if($a<$b || ($c>$b AND $d<$a)) { print "cierto<br>"; }else{ print "falso<br>"; } ?>

ejemplo40.php

Operadores de incremento

Operadores de incremento

Los caracteres ++ y -- escritos al lado del nombre de una variable producen incrementos o decrementos de una unidad en el valor de la misma. De igual forma, los operadores +=n y -=n escritos a la derecha del nombre de una variable producen incrementos o decrementos de nunidades en el valor de la variable.

Page 147: Manual PHP

Como veremos a continuación, los operadores ++ y -- se comportan de distinta forma según estén situados a la izquierda o a laderecha de la variable.

Estas operaciones sólo tienen sentido en variables numéricas –enteras o no–, pero si se aplican a variables de cadena les asignan previamente valor cero, salvo en una curiosa excepción que puedes ver en la primera de las tablas que tienes aquí a la derecha.

Operadores de pre incremento

++$A y --$A

Este operador incrementa el valor de la variable en una unidad (+1 o -1) antes de ejecutar el contenido de la instrucción.

El operador ++$A

Variables numéricas Variables alfanuméricas

Valor inicial Sintaxis Resultado Valor inicial Sintaxis Resultado

$a=23 echo ++$a 24 $b="pepe" echo ++$b pepf

$a=23 echo ++$a*2 48 $b="pepe" echo ++$b*2 0

El operador ––$A

Variables numéricas Variables alfanuméricas

Valor inicial Sintaxis Resultado Valor inicial Sintaxis Resultado

$a=23 echo ––$a 22 $b="pepe" echo --$b pepe

$a=23 echo --$a*2 44 $b="pepe" echo ––$b*2 0

$A+=n y $A-=n

Este operador incrementa el valor de la variable en n unidades (+n o -n) y luego ejecuta el contenido de la instrucción.

El operador $A +=n

Variables numéricas Variables alfanuméricas

Valor inicial Sintaxis Resultado Valor inicial Sintaxis Resultado

$a=23 echo $a+=5; echo $a

28 28

$b="pepe" echo $b+=5; echo $b

5 5

$a=23 echo 2*$a+=5; echo 2*$a

56 56

$b="pepe" echo 2*$b+=5; echo 2*$b

10 10

El operador $A –=n

Variables numéricas Variables alfanuméricas

Valor inicial Sintaxis Resultado Valor inicial Sintaxis Resultado

$a=23 echo $a-=5; echo $a

18 18

$b="pepe" echo $b-=5; echo $b

-5 -5

$a=23 echo 2*$a-=5; echo 2*$a

36 36

$b="pepe" echo 2*$b-=5; echo 2*$b

-10 -10

Operadores de post incremento

$A++ y $A--

Cuando los operadores ++ o -- están situados a la derecha de la variable los incrementos no se producen hasta que se ejecute lainstrucción siguiente.

Page 148: Manual PHP

El operador $A++

Valor inicial de la variable Sintaxis Resultado

$a=23 echo $a++; echo $a

23 24

$a=23 echo 2*$a++; echo 2*$a

46 48

El operador $A--

Valor inicial de la variable Sintaxis Resultado

$a=23 echo $a--; echo $a

23 22

$a=23 echo 2*$a--; echo 2*$a

46 44

Operadores condicionales

Operadores condicionales

Este tipo de operadores son el auténtico cerebro de cualquier aplicación que desarrollemos en PHP o en cualquier otro lenguaje de programación. Los operadores condicionales son la herramienta que permite tomar decisiones tales como: hacer o no hacer, y también:hacer algo bajo determinadas condiciones y otra cosa distinta en caso de que no se cumplan.

Aunque para simplificar los ejemplos vamos a utilizar en ellos una sola condición, este operador permite incluir como tal cualquier estructura lógica, del tipo que hemos visto en la página anterior, por compleja que esta sea.

Alternativas de sintaxis

Como iremos viendo a lo largo de estas líneas, este operador permite diferentes formas de sintaxis que podemos utilizar según nuestra conveniencia. La forma más simple es:

if(condición) ..instrucción... ;

Si se cumple la condición establecida en el paréntesis se ejecutará la primera instrucción que se incluya a continuación de ella. Cualquier otra instrucción que hubiera después de ella no se vería afectada por el condicional ejecutándose en cualquier circunstancia.

<?php # Definamos dos variables y asignémosles valores. # Hubieran podido obteners por cualquier otro procedimiento: # desde un array, # a través de un formulario cuya action ejecute este script, etc. $A=3; $B="3"; if ($A==$B) print ("A es igual B"); # cualquier otra instrucción que incluyéramos de aquí # en adelante se ejecutaría independientemente de que la condición # se cumpla o no ya que esta forma de if (sin llaves) # únicamente considera la primera instrucción # comprobémoslo en este otro supuesto if ($A<$B) print ("A es menor que B"); print("<br>A no es menor que b, pero esto saldrá<br>"); print("Esta es la segunda instrucción. No la condicionará el if"); ?>

Page 149: Manual PHP

ejemplo41.php

if(condición){ ..instrucción 1... ; ..instrucción 2... ; .... ; }

Es una ampliación del caso anterior. Cuando es necesario que –en caso de que se cumpla la condición o condiciones– se ejecutemás de una instrucción, se añade una { para indicar que habrá varias instrucciones, se escriben estas y se señala el final mediante }.

if(condición) : ..instrucción 1... ; ..instrucción 2... ; .... ; endif;

Esta otra forma del condicional se comporta como la anterior pero con otra sintaxis. Se sustituye la { de apertura por : y la } de cierre por endif

<?php $A=3; $B="3"; # en este caso cerraremos entre llaves las líneas # que deben ejecutarse si se cumple la condición if ($A==$B){ print ("A es igual B"); echo "<br>"; echo "Este if tiene varias instrucciones contenidas entre llaves"; } # una sintaxis alternativa a las llaves # sustituyamos la { por : y la } por endif if ($A==$B): print ("A es igual B"); echo "<br>"; echo "Hemos cambiado {} por : endif"; endif; ?>

ejemplo42.php

if(condicion){ ?> ..Etiquetas HTML... ; ..HTML... ; .... ; <? } ?>

PHP permite la utilización del operador condicional if con esta sintaxis. Un primer script PHP establece la condición. Todo lo contenido entre ese primer script y el de cierre: <?}?> será código HTML (está fuera del script), que se insertará en el documento sólo en el caso de que se cumpla la condición.

if(condicion) : ?> ..Etiquetas HTML... ; ..HTML... ; .... ; <? endif; ?>

Idéntica a la anterior, con la sintaxis : y endif.

Page 150: Manual PHP

<?php $a=5; # observa que ponemos la etiqueta de fin de script # después de la llave de apertura if ($a==5){ ?> <!-- Aquí estamos poniendo HTML puro no estamos dentro del script PHP //--> <H1>Esto no ha sido interpretado por PHP</H1> <!-- en la línea siguiente a este comentario volveremos a PHP para insertar la llave que indica el fin del if //--> <? } ?> <? # hagamos lo mismo cambiando {} por : endif if ($a==5): ?> <!-- Aquí estamos poniendo HTML puro no estamos dentro del script PHP //--> <H2>Esto tampoco sido interpretado por PHP</H2> <!-- en la línea siguiente a este comentario volveremos a PHP para insertar la llave que indica el fin del if //--> <? endif; ?>

ejemplo43.php

If ... else

El operador condicional tiene una interesante ampliación. En conjunción con else permite añadir instrucciones que sólo serían ejecutadas en caso de no cumplirse la condición. Esta nueva opción se habilita mediante la siguiente sintaxis:

if(condicion){ ... instrucciones a ejecutar cuando se cumple la condición } else { ... instrucciones a ejecutar cuando NO se cumple la condición } <?php $A=3; $B="4"; if ($A==$B){ #estas instrucciones se ejecutarían si se cumple la condición print ("A es igual B"); echo "<br>"; echo "Este if tiene varias intrucciones"; }else{ # estas se ejecutarían en el caso de no cumplirse # las condiciones epecificadas en el fi print("A no es igual que B"); echo "<br>"; echo ("La estructura de control se ha desviado al else"); } ?>

ejemplo44.php

Permite también la sintaxis alternativa :, endif, aunque en este caso hay que hacer una precisión -puedes verla aquí debajo– la llave de cierre que iba delante de else se elimina y no es sustituida por ningún caracter ni símbolo especial.

Page 151: Manual PHP

<?if(condicion): ?> ... código HTML a incluir cuando se cumple la condición <? else: ?> ... codigo HTML a incluir cuando NO se cumple la condición <? endif; ?> <?php $a=3; # observa que ponemos la etiqueta de fin de script # después de los dos puntos if ($a==5): ?> <!-- Aquí estamos poniendo HTML puro no estamos dentro del script PHP //--> <H1>Esto no es PHP. A es igual 5</H1> <!-- en la línea siguiente a este comentario volveremos a PHP para insertar el else seguido de dos puntos y cerramos de nuevo el script con ?>//--> <? else: ?> <!-- Aquí más HTML para el (else) caso de que no se cumpla la condición //--> <H2>Esto no es PHP. Es el resultado del ELSE</H2> <!-- volveremos a PHP para insertar en endif que indica el fin del if //--> <? endif; ?>

ejemplo45.php

En algunos casos resulta útil y cómodo el uso de esta otra posibilidad de sintaxis conocida como operador condicional ternario

(condición) ? (opcion1) : (opcion2)

Si se cumple la condición se ejecuta la opcion1. En el caso de que no se cumpla se ejecutará la opcion2.

<?php $a=5; ($a==8) ? ($B="El valor de a es 8"): ($B="El valor de a no es 8"); echo $B; ?>

Ejemplo con a=8 Ejemplo con a=5

If ... elseif .. else

Otra posibilidad dentro de la estructura de los operadores condicionales es la inclusión de elseif. Esta es la sintaxis. Dentro del ejemplo tienes los comentarios explicativos.

if(condicion1){ ... instrucciones a ejecutar cuando se cumple la condición1 }elseif(condicion2){ ... instrucciones a ejecutar cuando se cumple la condición2 sin cumplirse condición1 } else { ... instrucciones a ejecutar cuando NO se cumple ni la condición1 ni la condicion2 }

Page 152: Manual PHP

<?php $a=1; if ($a==1){ echo "El valor de la variable A es 1"; }elseif ($a==2){ echo "El valor de la variable A es 2"; }elseif ($a==3){ echo "El valor de la variable A es 3"; }else{ echo "La variable A no es 1, ni 2, ni 3"; } ?>

Ejemplo con a=3 Ejemplo con a=-7

<?php $a=1; if ($a==1): ?> <H1>A es igual a 1</H1> <? elseif($a==2): ?> <H1>A es igual a 2</H1> <? elseif($a==3): ?> <H1>A es igual a 3</H1> <? else: ?> <H1>A no es igual ni a 1, ni a 2, ni a 3</H1> <? endif; ?>

Ejemplo con a=2 Ejemplo con a=8

Condicionales anidados

El anidado no es otra cosa que el equivalente a los paréntesis dentro de paréntesis en las matemáticas. Y este operador lo permite, con una única condición, que verás en esta muestra de sintaxis.

if(condición1){ ... instrucciones para le caso de que se cumpla la condición1.... if(condición2){ ... instrucciones cuando se cumple la condicion1 y la condicion2... } else { ...instrucciones cuando se cumple la condicion 1 y no se cumple la condicion 2 } }else{ ... instrucciones para el caso de que no se cumpla la condicion 1 }

Observa que todo el bloque if.. else... marcado en azul se cierra antes de abrir la opción else marcada en marrón. Es obligatorio que así sea. De igual forma, podríamos insertar bloques sucesivos hasta llegar a crear una estructura tan amplia como fuera necesaria. Como ves, todo un mundo de posibilidades.

La función exit()

PHP dispone de una función exit() muy útil a los efectos del comentario anterior. Cuando se ejecuta exit() se interrumpe la ejecución del script con lo que la respuesta del servidor a la petición del cliente incluirá únicamente los contenidos generados antes de su ejecución.

Page 153: Manual PHP

La función switch

La función switch

Una alternativa al uso de condicionales del tipo if es la función switch. Se trata de un condicional que evalúa una variable y, según su valor, ejecuta unas instrucciones u otras. Su sintaxis es la siguiente:

switch ( variable ) { case n1: ...instrucciones a ejecutar en el caso de que la variable sea igual a n1... case n2: ..instrucciones a ejecutar en el caso de que la variable sea igual a n2... }

Cuando se usa esta sintaxis se ejecutan todas aquellas instrucciones que han sido incluidas a partir de la etiqueta en la que el valor que sigue a case coincide con el valor de la variable. Es decir, si en la expresión anterior la variable es igual a n1 se ejecutarían todas las instrucciones, tanto las que van detrás de case n1 como las que van detrás de case n2.

La forma más habitual de uso de esta función es esta:

switch ( variable ) { case n1: ...instrucciones a ejecutar en el caso de que la variable sea igual a n1... break; case n2: ..instrucciones a ejecutar en el caso de que la variable sea igual a n2... break; }

Esta opción incluye antes de cada nuevo case la función de ruptura break. Cuando PHP encuentra el break interrumpe la ejecución yno la reanuda hasta la instrucción siguiente a la } que cierra el switch. Insertando break en cada una de las opciones case, sólo se ejecutarían las instrucciones contenidas entre case num y ese break.

default:

Bajo este nombre (default:) se pueden incluir dentro de la función switch un conjunto de instrucciones que solo serán ejecutadas en el caso de que el valor de la variable no coincida con ninguno de los case. Su comportamiento es equivalente a else en el condicional if.

Ejemplos con switch

<?php # esta es la variable que controlará la función switch $i=1; switch ($i) { #insertamos la etiqueta case 0 y a continuación # las instrucciones correspondientes case 0: print "i es igual a 0 - No he puesto el break<br>"; # insertamos la etiqueta case 1 y a continuación # las instrucciones correspondientes # como no hemos puesto break y en este ejemplo $i=1 # se ejecutarán todas las instrucciones escritas # de aquí en adelante

Page 154: Manual PHP

case 1: print "i es igual a 1 - No he puesto el break<br>"; case 2: print "i es igual a 2 - No he puesto el break<br>"; }; # ahora incluiremos break al final de las intrucciones de cada case # con ello lograremos que solo se ejecuten las intrucciones correspondientes # a cada uno de ellos switch ($i) { case 0: print "i es igual a 0 - Ahora lleva break<br>"; break; case 1: print "i es igual a 1 - Ahora lleva break<br>"; break; case 2: print "i es igual a 2 - Ahora lleva break<br>"; break; } ?>

ejemplo53.php

<?php $i=3; switch ($i) { case 0: print "La variable i es 0<br>"; break; case 1: print "La variable i es 1<br>"; break; case 2: print "La variable i es 2<br>"; break; # al intoducir default y dado que $i=3 se ejecutarán # las instrucciones contenidas aquí ya que la variable # no coincide con ninguno de los case establecidos default: print "La variable i es mayor que dos o menor que cero"; break; } ?>

ejemplo54.php

Bucles while

Los bucles

La necesidad de repetir la ejecución de instrucciones es algo habitual en el mundo de la programación. Frente a la alternativa –poco práctica– de reescribir las instrucciones todos los lenguajes de programación disponen de funciones que pueden ejecutar un bloque de instrucciones de forma repetitiva.

La instrucción while

Como ocurría en el caso de if, el parámetro condición permite cualquier estructura lógica, y también dispone de distintas opciones de sintaxis.

Page 155: Manual PHP

while(condición) ...instrucción que se ejecutará mientras se cumpla la condición

Con esta sintaxis estaremos indicando que la instrucción siguiente (sólo una instrucción) ha de ejecutarse continua y repetidamente hasta que deje de cumplirse la condición establecida.

<?php # asignemos un valor a la variable $A $A=0; /* establezcamos la condición menor que cinco e insertemos dentro de la instrucción algo que modifique el valor de esa variable de modo que en algún momento deje de cumplirse la condición; de no ocurrir esto, el bucle se repetiría indefinidamente en este ejemplo el autoincremento ++ de la variable hará que vaya modificándose su valor*/ while ($A<5) echo "El valor de A es: ",$A++,"<br>"; # comprobemos que este while solo ejecuta una instrucción # la delimitada por el punto y coma anterior print("Esto solo aparecerá una vez. While no lo incluye en su bucle"); ?>

ejemplo55.php

while(condición) { ...instrucción que se ejecutará mientras se cumpla la condición... ....otra ...instrucción que se ejecutará mientras se cumpla la condición... .......... }

De forma similar a la utilizada en el caso de if, también en este caso, las llaves hacen la función de contenedores de las instrucciones cuya ejecución debe repetirse mientras se cumpla la condición.

<?php $A=0; /* utilicemos ahora el bucle para crear un tabla HTML empecemos escribiendo la etiqueta de apertura de esa tabla fuera del bucle (ya que esa se repite una sola vez) y utilicemos el bucle para escribir las celdas y sus contenidos */ print ("<table width=300 border=2>"); while ($A<=5){ echo "<tr><td align=center>"; print $A; # esta instrucción es importantísima # si no modificamos el valor de $A el bucle sería infinito $A++; print("</td></tr>"); } # cerremos la etiqueta table print "</table>"; ?>

ejemplo56.php

while(condición) : ...instrucción que se ejecutará mientras se cumpla la condición... ....otra ...instrucción que se ejecutará mientras se cumpla la condición... ..........

Page 156: Manual PHP

endwhile;

También aquí se mantiene la similitud con la sintaxis del condicional if. La llave ({) pueden sustituirse por (:) y en este caso en vez de (}) habría que escribir endwhile.

<?php # utilicemos whiles anidados para construir una tabla de $filas=5; $columnas=3; # insertemos la etiqueta de apertura de la tabla print ("<table border=2 width=400 align=center>"); # un primer while rojo que utiliza la condición filas mayor que cero # en este caso, la variable tendrá que ir disminuyendo su valor con $filas-- # para escribir las etiquetas <tr> y </tr> # y el modificador de la variable filas # y un segundo while (magenta) para insertar las etiquetas correspondientes # a las celdas de cada fila while ($filas>0): echo "<tr>"; $filas--; while ($columnas>0): echo "<td>"; print "fila: ".$filas." columna: ".$columnas; print ("</td>"); $columnas--; endwhile; /* ¡muy importante!. Tendremos que reasignar a la variable columnas su valor inicial para que pueda ser utilizado en la próxima fila ya que el bucle (magenta) va reduciendo ese valor a cero y en caso de no restaurar el viejo valor no volvería a ejecutarse ya que no cumple la condición de ser mayor que cero */ $columnas=3; echo "</TR>"; endwhile; # por ultimo la etiqueta de cierre de la tabla print "</table>"; ?>

ejemplo57.php

Incluimos a continuación un nuevo ejemplo en el hemos hecho una ligera modificación del anterior incluyendo un condicional if. No incluimos aquí el código fuente para evitarte la monotonía de repetir íntegramente y con ligeras modificaciones el supuesto anterior. Utilizaremos un nuevo recurso de visualización tal como puedes comprobar si pulsas en el enlace Ver código fuente. Esta opción utiliza la función

show_source('pagina');

que permite visualizar el código fuente del documento que se indica en el parámetro 'pagina'. Es muy útil para nuestros propósitos pero presenta un problema de seguridad. Si escribes –en el parámetro pag– la dirección completa de una web cualquiera (que tenga extensión php) se visualizará su contenido, salvo que el PHP instalado en el servidor que la aloja la tenga expresamente desactivada.

Recuerda que en HTML se puede asignar el color fondo a una celda incluyendo bgcolor=RGB(x,y,z) –siendo x, y ,z los valores de las componentes de cada color primario– dentro de la etiqueta <TD>.

Ver nuevo ejemplo Ver código fuente

while(condición) : ?> ...etiquetas HTML que se insertarían repetidamente mientras se cumpla la condición

Page 157: Manual PHP

..... <? endwhile; ?>

También while permite cerrar el script PHP después de (:) o de la sintaxis alternativa ({) e insertar etiquetas HTML, indicando más tarde el final del bucle con <? } ?> o <? endwhile; ?>, según proceda.

Whiles anidados

Una nueva similitud sintáctica con if. En el caso de while también es posible insertar un while dentro de otro while utilizando una sintaxis de este tipo:

while(condición1): ...instrucción while(condición2) { ...instrucción ..... } ..... endwhile;

En esta descripción hemos utilizado dos sintaxis distintas. Por si acaso dudaras de si es necesario o no hacerlo de esa forma, te diremos que no es necesario nunca. El hecho de la anidación no limita un ápice las posibilidades de la sintaxis.

Bucles do ... while

El bucle do... while

Estamos ante una variante del bucle while que hemos visto en la página anterior. La sintaxis es la siguiente:

do { .... instrucción 1... .... instruccion2... } while(condición) ;

Se diferencia de while en que en este caso se comprueba la condición después de haber ejecutado las instrucciones contenidas en el bucle, con lo cual, en el caso de que desde el comienzo no se cumplieran las condiciones establecidas en while las instrucciones del bucle se ejecutarían una vez. Respecto a la sintaxis, como puedes observar, detrás de do se inserta una llave ({) que señala el comienzo de las instrucciones pertenecientes al bucle. El final de esas instrucciones lo señala la otra llave (}) que precede a while(condición).

break

La función break –de forma similar a lo que ocurría en el caso de switch– permite interrumpir la ejecución de bucle. Tal como puede verse en el ejemplo, podría –entre otras posibilidades– utilizarse para evitar la primera ejecución de las instrucciones contenidas en el bucle, en el caso de que, desde un primer momento, no se cumplieran las condiciones establecidas en while.

No lo hemos comentado en la página anterior pero break se comporta en el caso de while de forma idéntica a la descrita aquí.

<?php $A=0; do { ++$A; echo "Valores de A usando el do: ",$A,"<br>";

Page 158: Manual PHP

} while($A<5); $B=7; do { echo "Pese a que B es mayor que 5 se ejecuta una vez. B= ",$B,"<br>"; } while($B<5); ?>

ejemplo59.php

<?php $A=500; do { if ($A>=500) { echo "No puede ejecutarse el bucle, porque no se cumple la condicion"; break; } ++$A; echo "Valores de A usando el do: ",$A,"<br>"; } while($A<500); echo "<BR>He salido del bucle porque A es: ",$A; ?>

ejemplo60.php

El ejemplo que tienes aquí debajo es similar al ejemplo n.º 57 de la página anterior. Sólo hemos sustituido los bucles while que allí habíamos utilizado por otros del tipo do ... while.

ejemplo61.php Ver código fuente

Bucles for

El bucle for

Se trata de una nueva forma –de uso bastante habitual– que permite establecer un bucle que se repetirá mientras una variable numérica se mantenga dentro de intervalo –especificado en la sintaxis del propio bucle– indicándose también en la propia instrucción el criterio de modificación de esa variable en cada ejecución del bucle. La sintaxis es la siguiente:

for ( desde ; hasta ; incremento ){ ......... ...instrucciones.... ......... }

El parámetro desde permite asignar un valor inicial a una variable ($var=num) que hará funciones de controladora de iteraciones. El parámetro hasta establece la condición que limita el valor máximo que puede alcanzar la variable de control y el parámetro incremento (con una sintaxis del tipo $variable++; $variable--; ++$variable; --$variable; $variable +=n o $variable -=n establece los incrementos odecrementos de la variable controladora en cada iteración del bucle.

Las instrucciones contenidas entre { } serán ejecutadas cada vez que se reitere el bucle.

<?php for ($i = 1; $i <= 10; $i++) { print $i."<br>"; }

Page 159: Manual PHP

?>

ejemplo62.php

Variantes del bucle for

El bucle for permite algunas variantes respecto a su forma más general. Son estas:

for ( desde ; ; incremento ){ ......... ...instrucciones.... ......... }

En este caso se omite el valor del parámetro hasta (observa que no se omite el separador de parámetros (;) con lo que en realidad se está asignando a hasta el valor NUL.

Cuando se utiliza esta sintaxis, el bucle se repetiría de forma indefinida (la variable podría tomar cero como valor, pero cero es distinto de NUL) salvo que -tal como puedes ver en el ejemplo- se escriba en las instrucciones un operador condicional con una opción de ruptura del bucle -el famoso break que ya hemos visto al estudiar la instrucción while y otras anteriores–.

<?php for ($i = 1;;$i++) { if ($i > 10) { break; } print $i."<br>"; } ?>

ejemplo63.php

for ( ; ; ){ ......... ...instrucciones.... ......... }

En este caso no se inserta ningún parámetro pero si se escriben los ; delimitadores de los mismos. Si observas el ejemplo verás que el control se realiza fuera del for. El valor de la variable contador se asigna fuera del bucle, los incrementos de esa variable están escritos en las líneas de instrucciones y llevan un operador condicional con la función break para permitir la salida.

<?php $i = 1; for (;;) { if ($i > 10) { break; } print $i."<br>"; $i++; } ?>

ejemplo64.php

for( desde ; hasta ; instrucciones, incremento )

Page 160: Manual PHP

Esta nueva variante de for permite insertar instrucciones a través del tercer parámetro de la función. Si insertamos como tercer parámetro un conjunto de instrucciones, separadas por comas, se ejecutarán de igual forma que si estuvieran contenidas entre { y }

En este caso, el modificador de la variable de control (incremento) se incluye como una instrucción más –separada por una coma– de las contenidas en ese tercer parámetro de la función.

<?php for ($i = 1; $i <= 10; print $i."<br>", $i++) ; ?>

ejemplo65.php

for ( desde ; hasta ; incremento ): ......... ...instrucciones.... ......... endfor;

Esta sintaxis es alternativa a la primera de las descritas. Sustituye la { por dos puntos (:) y la } por endfor.

<?php for($i = 1; $i <=10;$i++) : echo $i,"<br>"; endfor; ?>

ejemplo66.php

<?php for ($i = 1; $i <= 10;$i++):?> <H1>Esto se repetirá 10 veces</H1> <? endfor; ?>

ejemplo67.php

Como puedes observar en este último ejemplo también es aplicable aquí la sintaxis de los dos sripts PHP. El primero contiene las instrucciones del bucle y el segundo señala el final del mismo. Entre ambos scripts se escribe el código HTML

¡Cuidado! A la hora de programar bucles hay que evitar el riesgo de convertirlo en un bucle indefinido. Cuando esto ocurre –el error es humano– al abrir la página que lo contiene parecerá que nuestro navegador se ha quedado colgado aunque en realidad estará esperando a que sea atendida la petición. Si llega a planteársete ese problema, tendrás que recurrir a la socorrida solución de pulsar ALT+CTRL+DEL para abortar la petición del navegador. Bucles foreach

El bucle foreach

El bucle foreach es específico de los array y aplicable a ellos tanto si son escalares como si su tipo es asociativo. Tiene dos opciones uso. Mediante una de ellas lee únicamente los valores de los elementos del array. La segunda opción permite leer el índice y el valor de cada elemento.

Lectura de valores

Utilizando la sintaxis:

Page 161: Manual PHP

foreach ( array as $valor ) { ...instrucciones... }

donde array es el nombre del array (sin incluir índices ni corchetes), as es una palabra obligatoria y $valor el nombre de una variable (puede ser creada al escribir la instrucción ya que no requiere estar previamente definida) que irá recogiendo los valores de los sucesivoe elementos del array.

Las instrucciones escritas entre las { } permiten el tratamiento o visualización de los valores obtenidos. La variable $valor no podrá ser utilizada para guardar valores. Hemos de tener en cuenta que su valor se rescribe en cada iteración del bucle y que al acabar este sólo contendrá el último de los valores leídos.

<?php /* definimos un array escalar utilizando la sintaxis nombre del array=array (valores de los elemento separados por comas) si los valores son números no es necesario encerrarlos entre comillas */ $a=array("a","b","c","d","e"); /* definamos ahora un nuevo array, esta vez asociativo utilizando la sintaxis clave => valor tal como puedes ver */ $b=array( "uno" =>"Primer valor", "dos" =>"Segundo valor", "tres" =>"Tecer valor", ); # establecemos el bucle que leerá el array $a # recogiendo en la variable $pepe los valores extraídos # y escribimos los valores foreach($a as $pepe) { echo $pepe,"<br>"; }; # repetimos el mismo proceso, ahora con $b que es un array asociativo foreach($b as $pepe) { echo $pepe,"<br>"; }; ?>

ejemplo68.php

Lectura de índices y valores

Con una sintaxis como la que sigue se pueden leer no sólo los valores de un array sino también sus índices.

foreach ( array as $indices => $valores ) { ...instrucciones... }

donde array es el nombre de la matriz, as es una palabra obligatoria, $indices es el nombre de la variable que recogerán los índices, los caracteres => (son obligatorios) son el separador entre ambas variables y, por último, $valores es el nombre de la variable que recoge el valor de cada uno de los elementos del array.

Tanto esta función como la anterior realizan una lectura secuencial que comienza en el primer valor del array.

<?php $a=array("a","b","c","d","e"); $b=array( "uno" =>"Primer valor", "dos" =>"Segundo valor", "tres" =>"Tecer valor", );

Page 162: Manual PHP

# en este caso extraeremos índices y valores de ambos arrays # usaremos $pepe para recoger los índices y $pepe para recoger los valores # y separaremos ambas variables por => que es el separador obligatorio # para estos casos foreach($a as $pepe=>$pepa) { echo "Indice: ",$pepe," Valor: ",$pepa,"<br>"; }; foreach($b as $pepe=>$pepa) { echo "Indice: ",$pepe," Valor: ",$pepa,"<br>"; }; ?>

ejemplo69.php

Arrays bidimensionales

Cuando se trata de arrays bidimensionales la lectura de los valores que contienen sus elementos requiere el uso de dos buclesanidados. Cuando un array de este tipo es sometido al bucle foreach se extrae como índice el primero de los indices y como valor un array unidimensional que tiene como índice el segundo de los del array original y como valor el de aquel.

La lectura de los valores de cada elemento requiere utilizar un segundo bucle que extraiga la información del array unidimensional obtenido como valor en la primera lectura. La sintaxis sería de este tipo:

foreach ($array as $indice1=>$nuevo_array) { foreach($nuevo_array as $indice2 => $valor) { .... $indice1 es el primer índice.... .... $indice2 es el segundo índice.... .... $nuevo_array nuevo array ..... $valor es el valor .... } }

En el caso de arrays con dimensiones superiores sería necesario proceder del mismo modo, y habría que utilizar tantos buclesforeach como índices contuviera el array.

<?php # definamos un array bidimensional $z=array( 0 => array ( 0 => 34, 1 => 35, 2 => 36, ), 1 => array ( 0 => 134, 1 => 135, 2 => 136, ) ); # intentemos leer índices y valores mediante un bucle foreach # y veamos como los valores que extraemos son Arrays (unidimensionales) # consecuencia del caracter bidimensional del array original

Page 163: Manual PHP

foreach($z as $pepe=>$pepa) { echo "Indice: ",$pepe," Valor: ",$pepa,"<br>"; }; /* anidemos dos bucles foreach de la siguiente forma en el primero extraemos un array que es tratado por el segundo foreach para extraer el segundo índice y el valor realmente contenido en ese elemento bidimensional */ foreach($z as $ind1=>$valor1) { foreach($valor1 as $ind2=>$valorReal) { echo "Ind. 1: ",$ind1,"Ind. 2: ",$ind2," Valor: ",$valorReal,"<br>"; }; }; ?>

ejemplo70.php

La instrucción continue

La instrucción continue

Si la instrucción break permite interrumpir el desarrollo de un bucle, mediante continue se puede impedir que, bajo unas condiciones determinadas, se ejecuten algunas o todas las instrucciones de un bucle sin que por ello se interrumpa la ejecución de las iteraciones siguientes. Esta instrucción es aplicable tanto a bucles for como a los de tipo while o do while.

Seguramente los ejemplos nos ayudarán a aclarar un poco más la idea. En este primer ejemplo (un bucle for) tiene comocondición(if): $i % 2 == 0 que, como recordarás, significa que el resto de la división de $i entre dos($i % 2) sea igual (==) a cero.

En este supuesto (condición de múltiplo de dos) se activará la opción continue y por lo tanto en esa iteración no se ejecuta la instrucción echo y en consecuencia no se imprimirán en pantalla los múltiplos de 2.

<?php for ($i=0;$i<=10;$i++){ #condición de múltiplo de 2 if ($i % 2 ==0 ) { continue ; } echo "La variable I vale ",$i,"<br>"; } ?>

ejemplo71.php

Este otro ejemplo (caso de bucle while) la condición establecida para que se ejecute continue es que el valor de la variable no sea múltiplo de tres, en cuyo caso echo sólo imprimirá los múltiplos de 3.

<?php $i = 0; while ($i++ < 14) { #condición de no múltiplo de 3 usando para distinto la sintaxis != if ($i % 3 !=0){ continue ; }

Page 164: Manual PHP

echo "El valor de i es: ",$i,"<br>"; } ?>

ejemplo72.php

En este nuevo ejemplo utilzaremos un bucle do ... while para presentar en pantalla los múltiplos de 11.

<?php $i = 0; do { # condición de no múltiplo de 11. fíjate en la sintaxis alternativa # observa que aquí distinto lo hemos escrito <> if ($i % 11 <>0 ){ continue ; } echo "El valor de i es: ",$i,"<br>"; }while ($i++ < 100) ?>

ejemplo73.php

La instrucción continue n

La instrucción continue puede utilizar un parámetro n con valor entero positivo que, cuando no se indica de forma expresa, toma por defecto el valor 1. La idea es la siguiente. Cuando tenemos bucles anidados el intérprete de PHP los considera numerados correlativamente–de dentro hacia fuera– a partir de UNO.

Cuando se ejecuta continue n se redirecciona la iteración al bucle cuyo número coincide con el valor de n. Obviamente, el valor de nno puede ser nunca mayor que el número de bucles anidados en el script.

Analicemos algunos ejemplos. En este caso el bucle for sería el número UNO y el while sería el número DOS. Cuando se cumpla la condición que activa continue 2 ($i=3) se redirecciona la iteración al paso siguiente de while (bucle número DOS).

<?php $j=0; while (++$j <5) { for($i=1;$i<5;$i++){ if ($i==3){ continue 2; } echo "El valor de j es: ",$j, " y el de i es: ",$i,"<br>"; } } ?>

Caso continue 1 Caso continue 2

En este otro ejemplo hemos anidado a tres niveles y hemos escrito continue 3, aunque a la hora de ejecutar los ejemplos podrás ver las tres variantes posibles de ese script modificando los valores de continue n.

Page 165: Manual PHP

<?php $j=0;$k=0; do { //n=3 while (++$j <=5) { // n=2 for($i=1;$i<=5;$i++){ // n=1 if ($i==2){continue 3;} echo "El valor de k es: ",$k," y el valor de j es: ",$j, " y el de i es: ",$i,"<br>"; } } }while ($k++ <=5); ?>

Caso continue 1 Caso continue 2 Caso continue 3

Expresiones regulares

Búsqueda y reemplazamiento de textos

PHP dispone de varias opciones para realizar tareas de búsqueda y reemplazamiento de textos. La más antigua, POSIX (PortableOperating System Interface uniX), se anuncia como deprecated a partir de PHP 5.3. Por esa razón centraremos nuestro análisis en la llamadaPCRE (Perl Compatible Regular Expressions) que parece ser la opción con más futuro.

Una expresión regular o patrón de búsqueda, es una forma de describir un conjunto de cadenas de carácteres sin enumerar sus elementos. En el caso de las expresiones regulares compatibles con Perl (PCRE) hemos de empezar diciendo que han tener señalado su comienzo y su final por dos caracteres idénticos llamados delimitadores pudiendo realizar esa función cualquier carácter no alfanumérico.

Expresiones tales como: #tomate#, *tomate* o /tomate/ serían patrones válidos, delimitados por #, * o /, para la búsqueda de la palabra tomate.

Empezaremos considerando el caso de que una coincidencia requiera encontrar una identidad plena con el patrón. Más adelante incidiremos sobre el tema de los patrones de búsqueda y el manejo de su amplísimo abanico de opciones.

Aunque lo trataremos con detalle un poco más adelante vamos a empezar a manejar el modificador i. Cuando este carácter es añadido detrás del último delimitador modifica el patrón de búsqueda para considerar las coincidencias sin hacer distinción entre mayúsculas y minúsculas. Si no se incluyera i la búsqueda atendería al criterio de que mayúscula y minúscula son caracteres diferentes.

El reemplazamiento

Para poder ilustrar de forma gráfica las características de los diferentes patrones de búsqueda empezaremos por conocer el manejo de la función:

preg_replace ( patron, sustituta, cadena, limite_coincidencias, $contador )

dónde patron es el patrón de búsqueda, sustituta la cadena que va a sustituir las coincidencias, cadena la cadena en la que pretendemos realizar la búsqueda y sustitución, limite_coincidencias el número máximo de coincidencias a las que aplicaremos el reemplazamiento y $contador será el nombre de una variable que puede utilizarse como contador del número de cambios efectuados.

<?php /* definimos la cadena en la que pretendemos realizar la búsqueda y sustitución */ $original = <<<Cadena Esta es la cadena que utilizaremos como original. Sobre ella efectuaremos algunos cambios utilizando EXPRESIONES REGULARES

Page 166: Manual PHP

Cadena; /*establecemos lo que queremos buscar*/ $buscar="es"; /*establecemos lo que queremos sustituya a lo anterior que será marcar en rojo con fondo amarillo los mismos caracteres*/ $sustituto="<span style='color:red;background-color:yellow'>es</span>"; /*elegimos un delimitador para el patrón de búsqueda ( / ) */ $delimitador="/"; /* establecemos el modificador i para buscar sin diferencias mayúsculas de minúsculas */ $modificador="i"; /* recogemos en la variable $patron la expresión regular formada por el delimitador inicial, la cadena a buscar, el delimitador final y, en este caso, el modificador i */ $patron=$delimitador.$buscar.$delimitador.$modificador; print "<br />Mediante el patrón de búsqueda: <b><i>".$patron."</i></b>"; print " buscaremos <b><i>".$buscar."</i></b> en la cadena:<br />"; print "<i>".$original."</i><br>"; print " reemplazando <b><i>".$buscar."</i></b> por "; print "<b><i>".$sustituto."</i></b><br />"; print " Este es el resultado: <b><br />"; print preg_replace ($patron,$sustituto,$original); print "</b><br /><br />"; print "Agregemos una restricción en cuanto a coincidencias (1)<br /><b>"; print preg_replace ($patron,$sustituto,$original,1); print "</b><br /><br />Incluyamos un contador de coincidencias <br><b>"; print preg_replace ($patron,$sustituto,$original,-1,$z); print "</b><br /><br />Hemos realizado ".$z." cambios"; /* cambiemos el modificador para que sea sensible a mayúsculas/minusculas */ $modificador=""; /* actualicemos la variable patrón con el último cambio */ $patron=$delimitador.$buscar.$delimitador.$modificador; print "<br /><b>Búsqueda sensible a mayúsculas/minúsculas</b><br>"; print "<br />Mediante el patrón de búsqueda: <b><i>".$patron."</i></b>"; print " buscaremos <b><i>".$buscar."</i></b> en la cadena:"; print "<i>".$original."</i><br>"; print " reemplazando <b><i>".$buscar."</i></b> por "; print "<b><i>".$sustituto."</i></b>"; print " Este es el resultado: <b>"; print preg_replace ($patron,$sustituto,$original); print "</b><br /><br />"; print "Agregemos una restricción en cuanto a coincidencias (1)<br /><b>"; print preg_replace ($patron,$sustituto,$original,1); print "</b><br /><br />Incluyamos un contador de coincidencias <br><b>"; print preg_replace ($patron,$sustituto,$original,-1,$z); print "</b><br /><br />Hemos realizado ".$z." cambios"; ?>

ejemplo79.php

Si se omiten los parámetros limite_coincidencias y $contador se reemplazarán todos los valores posibles sin recoger en ninguna variable el número de cambios. En el caso de que queramos indicar de forma explícita que se efectúen todos los cambios posibles asignaremos al cuarto parámetro (limite_coincidencias) el valor -1.

El parámetro patron puede ser un array en el que cada elemento será un patrón de búsqueda. Cuando se da esa circunstancia ysustituta es una cadena, las coincidencias de cada uno de los diferentes patrones son reemplazadas por la cadena sustituta.

En el caso de que sustituta fuera un array, las coincidencias del patron contenido en cada elemento de array serán reemplazadas por el valor del elemento de sustituta de igual índice tal como puede verse en los ejemplos de la tabla que incluimos a

Page 167: Manual PHP

continuación. Hemos utilizado en todos los casos el modificador i razón por la cual las búsquedas serán insensibles a mayúsculas y minúsculas.

Función Descripción Código fuente

Ver ejemplo

preg_replace($patron, $sustituto, $original) Devuelve una cadena sistituyendo, sin límite de coincidencias, el contenido del patrón por la cadena de reemplazamiento

Ver Probar

preg_replace($patron, $sustituto, $original,n) Devuelve una cadena sistituyendo el contenido. El cuarto parámetro (n) establece que solo se reemplazarán las n primeras coincidencias

Ver Probar

$contador=0; preg_replace($patron, $sustituto, $original, -1, $contador)

Devuelve una cadena sistituyendo el contenido. El cuarto parámetro (-1) establece que se reemplazarán todas las coincidencias. La variable contador, incluida como quinto parámetro recoge el número de ocurrencias

Ver Probar

$patron=array('/xxx/','/yyy/'); preg_replace($patron, $sustituto, $original)

Cuando el patrón es un array (cada elemento ha de incluir los delimitadores) se sustituye cada coincidencia con cualquiera de los elementos del array por la cadena de reemplazamiento

Ver Probar

$patron=array('/xxx/','/yyy/'); preg_replace($patron, $sustituto, $original,n)

En el caso de un patrón en forma de array el límite de coincidencias trata individualmente cada elemento del array

Ver Probar

$contador=0; $patron=array('/xxx/','/yyy/'); preg_replace($patron, $sustituto, $original,n,$contador)

Si se incluye el parámetro contador se cuantificarán todos los cambios realizados.

Ver Probar

$contador=0; $patron=array('/xxx/','/yyy/'); $sustituto=array('/zzz/','/ppp/');preg_replace($patron, $sustituto, $original)

Aquí utilizaremos dos arrays. Uno para establecer los patrones y otro para establecer los reemplazamientos asociados con aquellos

Ver Probar

$contador=0; $patron=array('/xxx/','/yyy/'); $sustituto=array('/zzz/','/ppp/');preg_replace($patron, $sustituto, $original)

Cuando el patron es un array de mayor longitud que el sustituto las sustituciones no definidas serán consideradas como cadenas vacías

Ver Probar

$contador=0; $patron=array('/xxx/','/yyy/'); $sustituto=array('/zzz/','/ppp/');preg_replace($patron, $sustituto, $original)

Cuando el patron es un array de menor longitud que el sustituto los elementos sobrantes de este no producirán ningún efecto

Ver Probar

Patrones de búsqueda

La auténtica potencia de las expresiones regulares radica en el enorme abanico de opciones búsqueda que se pueden configurar utilizando: metacaracteres, indicadores de codicia, secuencias de escape, anclas, indicadores de alternancia, agrupadores, subpatrones y los diferentes tipos de modificadores. Veamos la utilidad de cada una de estas herramientas.

Metacaracteres

Llamaremos metacaracteres a un conjunto de carácteres que tienen un significado especial cuando se incluyen en una expresión regular. A título puramente enumerativo, forman parte de ese conjunto los siguientes: ., +, *, {, }, [, ], -, \, ^, $, |, (, ) y ?. Veamos sus significados:

.

Cuando incluimos un punto como metacaracter en un patrón de búsqueda estamos indicado que se admite cualquier carácter en la posición que ocupa el punto. Es decir, el patrón p. consideraría coincidencia tanto pa, como pz, como p3.

+

Cuando un patrón incluye un signo más se está especificando que el carácter que le precede puede repetirse una o más veces. Es decir pa+ consideraría coincidencias a: pa, paa, paaa y también a paaaaaaa.

*

Este metacaracter es ligeramente diferente al anterior. También permite repeticiones pero no obliga a que aparezca el carácter que le precede. Es decir, el patrón pa* consideraría coincidencias a: pa, paa, paaa y también p3 y pz que no incluyen ninguna a detrás de la p.

Page 168: Manual PHP

{m,n}

Cuando se incluyen esta expresión (en la que m y n son dos números naturales) a continuación de un caracter cualquiera se especifica que se considerará coincidencia cualquier cadena que contenga ese carácter repetido un número veces comprendido entre m y n ambos inclusive. Quiere decir que pa{3,5} considerará coincidencias a paaa, paaaa y paaaaa pero no considerará tal coincidencia en el caso de paa o de paaaaaa.

Existen casos particulares. Cuando se incluye {m} se está estableciendo que solo se considerará coincidencia cuando el caracter se repita exactamente m veces. Si se especifica {m,} (se incluye la coma pero falta el segundo valor) estaremos indicando que seconsiderará coincidencia cuando el carácter se repita m o más veces.

[ ]

Mediante los corchetes se pueden incluir rangos de caracteres en el patrón de búsqueda. Si se especifican letras o números sin ningún separador se considerará coincidencia cualquiera de ellos. Si se separan por medio de un guión – dos números o dos letras se considerará coincidencia cualquiera del rango delimitado por ellos. Puedes verlo en los ejemplos.

^

Cuando este símbolo (acento circunflejo) se incluye como primer carácter dentro de un corchete será interpretado como negación, es decir, no se considerará coincidencia la expresión que le preceda.

Patrón de búsqueda

Descripción Código fuente

Ver ejemplo

o Escribe una "o" marcada con colores sustituyendo a cada "o" de la cadena original sin diferenciar mayúsculas de minúsculas. Es idéntico al caso del los ejemplos anteriores

Ver Probar

d. Considerará coincidencias todas las d junto su carácter siguiente sea cual fuere (el punto indica que puede ser un carácter cualquiera). Cada coincidencia la reemplazará por do sin diferenciar mayúsculas de minúsculas

Ver Probar

do* Considerará coincidencias todas las "d" seguida de una "o" repetida cero o más veces. Cada coincidencia será reemplazada por "do"

Ver Probar

do+ Considerará coincidencia cada "d" seguida de una "o" repetida una o más veces y reemplazará cada coincidencia por "do"

Ver Probar

o{3} Considerará coincidencia tres "o" consecutivas (ooo) y reemplazará cada coincidencia por una "a" Ver Probar

o{1,} Considerará coincidencia cada uno de los grupos cada "o", "oo", "ooo..." sin importar cuantas "o" formen el grupo reemplazando cada coincidencia por una "o" coloreada

Ver Probar

o{2,4} Considerará coincidencia los grupos "oo", "ooo" "oooo" (entre 2 y 4 repeticiones) y reemplazará por por una "a" cada una de las coincidencias

Ver Probar

[abcd] Considera coincidencia uno cualquiera de los caracteres incluidos en el corchete y las sustituye por * Ver Probar

[^abcd] Considera coincidencia cualquiera de los caracteres distintos a los incluidos en el corchete y sustituirá cada una de ellas por un *

Ver Probar

[a-h] Considera coincidencia cualquier caracter comprendido entre a y h (ambos inclusive) Ver Probar

[^a-h] Considera coincidencia cualquier caracter no comprendido entre a y h (ambos inclusive) Ver Probar

[0-9] Considera coincidencia cualquier dígito comprendido entre 0 y 9 (ambos inclusive) Ver Probar

[^0-9] Considera coincidencia cualquier dígito no comprendido entre 0 y 9 (ambos inclusive) Ver Probar

Cuando se utilizan expresiones del tipo[0-9], [A-Z] los extremos de los intervalos se delimitan por los valores de los códigos ASCII del primero y último de los caracteres entendiendo como valores pertenencientes a ese rango aquellos carácteres cuyo código ASCII pertenezca a ese intervalo. El código ASCII de A es 65, el de Z es 90, el de a es 97 y el de z es 122. Hemos de prestar mucha atención a los extremos ya que si estableciéramos un rango como [a-Z] estaríamos indicando un intervalo inaceptable ya que comenzaría en 97 y acabaría en 90 (ha de ser siempre mayor el segundo valor que el primero) y nos daría error tal como puedes ver en este enlace

Los signos utilizados como metacaracteres pueden plantear problemas en los casos en los que pretendamos incluirlos como un carácter más en un patrón de búsqueda. Para utilizarlos de esta forma es necesario «escaparlos», es decir, anteponerles el carácter \. De esa forma serían interpretados como texto.

Además de la utilidad mencionada en los párrafos anteriores, cuando se antepone el carácter de escape a algunos carácteres (distintos de los metacaracteres) se activan algunas condiciones especiales tal como puedes ver en la tabla de ejemplos que tienes a la derecha.

La función: $r= preg_quote($c, $d) recoge en la variable $r el resultado de aplicar esa secuencia de escape a la cadena contenida como parámetro en la variable $c. El parámetro $d deberá recoger el carácter delimitador del patrón de búsqueda.

Page 169: Manual PHP

En esta tabla recogemos algunos ejemplos del uso de las secuencias de escape en los patrones de búsqueda.

Patrón de búsqueda

Descripción Código fuente

Ver ejemplo

\d Considera coincidencia cualquier caracter que sea un dígito comprendido entre 0 y 9 (ambos inclusive). Equivale a [0-9] Ver Probar

\D Considera coincidencia cualquier caracter que no sea un dígito comprendido entre 0 y 9 (ambos inclusive). Equivale a [^0-9]

Ver Probar

\s Considera coincidencia los espacios en blanco Ver Probar

\S Considera coincidencia cualquier caracter que no sea un espacio en blanco Ver Probar

\w Considera coincidencia cualquier carácter de la "a" a la "z" (excluyendo la ñ y caracteres con tilde) o cualquier número del 0 al 9. No incluye los espacios entre las coincidencias

Ver Probar

\W Considera NO COINCIDENCIA cualquier carácter de la "a" a la "z" (excluyendo la ñ y caracteres con tilde) o cualquier número del 0 al 9

Ver Probar

\n Considera coincidencia los saltos de línea (no se consideran como tal las etiquetas <br>) Ver Probar

\/ Al anteponerle el \ a un metacaracter es considerado como un carácter normal en la cadena de búsqueda. Si no lo hiciéramos nos daría un mensaje de error

Ver Probar

En este ejemplo puede verse la utilidad de la función preg_quote Ver Probar

Alternacias

La alternancia utiliza el carácter | para permitir una elección entre dos o más alternativas. Cuando se conjugan incluidas entre paréntesis y teniendo en cuenta que los paréntesis pueden agruparse y/o anidarse pueden construirse expresiones tan complejas como necesitemos.

Patrón de búsqueda

Representa a Código fuente

Ver ejemplo

a|b|c Se considera coincidencia la presencia de uno cualquiera de los caracteres. Todas la coincidencias se reemplazarán por * Ver Probar

(a|b)(c|d) Se considera coincidencia la presencia de uno cualquiera de los caracteres del primer paréntesis segudio de uno de los caracteres del segundo. Serán coincidencias: ac, ad, bc y bd. Todas la coincidencias se reemplazarán por *

Ver Probar

Anclas

En los ejemplos que hemos visto hasta ahora las coincidencias podrían producirse en cualquier parte de la cadena en la que efectuamos la búsqueda. Estas condiciones pueden modificarse restringiendo la búsqueda al principio o al final de la cadena de búsqueda. Para el primer caso se antepone al patrón el metacaracter ^.

¡Cuidado! El metacaracter ^ tiene dos utilidades manifiestamente distintas. Dentro de un corchete [^a] significa negación («no igual a 'a'» en este caso) y fuera de él y como primer carácter del patrón ^[abc] o ^pepe hace labores de ancla para restringir la búsqueda al principio de la cadena.

El uso del modificador A produce el mismo efecto que el metacaracter ^.

Cuando se trata de buscar coincidencias únicamente al final de la cadena es necesario recurrir al metacarácter $ que debe ser incluido detrás del patrón de búsqueda. Tanto este metacarácter como ^ cambian su funcionalidad cuando se incluye el modificador m. Cuando se da esta situación considerará comienzo y final tanto el comienzo y el final de la cadena como las posiciones anteriores y posteriores al carácter de salto de línea \n tal como puedes ver en algunos de los ejemplos de la tabla siguiente.

Patrón de búsqueda

Representa a Código fuente

Ver ejemplo

^[abcd] Considera coincidencia sólo si uno de los caracteres incluidos en el corchete aparece al comienzo de la cadena Ver Probar

/[abcd]/A Utilizando el modificador A en el patrón (en este caso incluimos los caracteres delimitadores en el patrón) se logra el Ver Probar

Page 170: Manual PHP

mismo objetivo que en el ejemplo anterior

[abcd]$ Considera de los caracteres incluidos entre corchetes al final de la cadena Ver Probar

/^[abcd]/mi Utilizando el modificador m en el patrón se comprueban las coincidencias de los caracteres situados al comienzo de cada línea. Se considera perteneciente a una nueva línea cada el carácter que vaya inmediatamente después de la secuencia \n

Ver Probar

/[abcd]$/mi Utilizando el modificador m en el patrón se comprueban las coincidencias de los caracteres situados al final de cada línea. Se considera último carácter de una línea al que precede a la secuencia \n

Ver Probar

Codicia

Se dice que las expresiones regulares son codiciosas porque, salvo que se especifique lo contrario, procuran que la coincidencia abarque el mayor número de caracteres posibles. Eso significaría que un patrón de búsqueda del tipo a.*z considerará como una sola coincidencia la porción de texto delimitado por la primera "a" de la cadena y la última "z".

La codicia puede invertirse de dos maneras. Una de ellas es modificar el patrón de búsqueda agregando el signo ? inmediatamente después de un indicador de repeticiones (+,* o {m,n}). De esa forma a.*?z consideraría como coincidencia la porción de texto delimitado por cada "a" de la cadena y la primera de las "z" que aparezcan después de ella.

La segunda forma de invertir la codicia es agregar el modificador U con lo cual la cadena patrón podría ser similar a esta: /a.*z/U con los mismos resultados descritos en el párrafo anterior.

ejemplo119.php Ver código fuente

Subpatrones

Cuando una expresión regular se encierra en un paréntesis pasa a constituir un subpatrón de captura que, por defecto, tiene dos utilidades prácticas. La primera es su condición de ser parte del patrón que la contenga incluidas las eventuales condiciones de alternancia(a|b) que pueda contener. Junto con esa función los subpatrones disponen de otra muy importante: la captura. Las coincidencias son almacenadas en memoria con la opción de poder ser utilizadas a través de lo que se conoce como retro referencias.

Fuera de una clase carácter, una barra invertida seguida por un dígito mayor que 0 es una retro-referencia a un subpatrón de captura anterior. La retro-referencia no recoge el subpatrón sino el valor de la cadena coincidente con el subpatrón al que alude. Las retro referencias pueden marcarse así: \\n, \\gn o \\g{n} siendo en los tres casos n el número de orden del sub-patrón contando de izquierda a derecha.

Veamos su comportamiento con un ejemplo. Si comparamos la cadena "Me gusta el atún rojo con tomate" con el patrón el((atún|bonito)(blanco|rojo)) vamos a encontrar una coincidencia del patrón completo (atún rojo), y las coincidencias de cada uno de los subpatrones: atún en el caso del primero, y rojo en el caso del segundo. Tales coincidencias son numeradas para su uso como retro referencias por medio de números naturales 1, 2 y 3 que se corresponden con los paréntesis contados de izquierda a derecha.

Por ese criterio de asignación la retro referencia \1 (siempre se antepone \ para aludir a ellas) recogerá el valor atún rojo, \2 contendráatún y \3 incluirá rojo.

Hay situaciones en las que no es necesario usar retro referencias y, dado que pueden ralentizar la operación y requerir abundante espacio de memoria, puede resulte conveniente desactivarlas.

Para ello basta con que incluyamos después del paréntesis de apertura del patrón (o del subpatrón) los metacaracteres ?:.

Aquí tienes algunos ejemplos de utilización de retro referencias.

Patrón de búsqueda Representa a Código fuente

Ver ejemplo

(a|b)\\1 El subpatrón buscará el carácter a o b. El resultado será capturado como \1. Incluyendo la referencia a esa captura como carácter siguiente serán coincidentes con el patrón las cadenas aa y bb.

Ver Probar

(\w)(\w)\\2\\1 El primer carácter será una letra ó número (\w) que ha de coincidir con el cuarto carácter de la cadena (\\1). El segundo puede ser también cualquier letra (\w) que ha de coincidir con el tercer carácter de la cadena(\\2)

Ver Probar

((\w)(\w))\\3\\2\\1 El primer subpatrón (identificado mediante \1) sería el más exterior de todos (marcado en rojo). Incluye dos Ver Probar

Page 171: Manual PHP

subpatrones que numerados de izquierda a derecha resultarán identificados por \\2 y \\3 (verde y azul respectivamente)

((a|b)(c|d))\\1 El primer subpatrón ha de contener una de las cadenas ac, ad, bc, o bd y, además ha de repetirse (\\1) por tanto serán coincidencias: acac, adad, bcbc y bdbd

Ver Probar

(?:a|b)\\1 Dará error ya que ?: es el indicador de no captura lo cual hace inválida la referencia \\1 Ver Probar

(?:a|b) Eliminando la retroreferencia \\1 del ejemplo anterior funciona correctamente Ver Probar

((?:a|b)(?:c|d))\\1 Funciona exactamente igual que el último ejemplo de la tabla anterior. Las referencias eliminadas no están incluidas en el patrón de búsqueda y por tanto no influyen en el resultado

Ver Probar

Subpatrones especiales

Subpatrones modificadores

Entre los tipos especiales de subpatrones cabe hablar de los conocidos como subpatrones modificadores. Su función es la deincluir o excluir modificadores a lo largo del patrón de búsqueda. Dada su condición de subpatrones han de comienzar y terminar por paréntesis (). Por su carácter especial han de llevar ? después del paréntesis de apertura (?) y a continuación pueden incluir uno o varios modificadores. Si se trata de activarlo basta con poner el carácter representativo (A, i, m, etcétera) y para desactivarlos se incluye un signo menos (-) delante de ese carácter.

Por ejemplo (?A-im) activaría el modificador A, desactivaría el i y activaría el m siempre a partir de la posición del patrón de búsqueda en el que es incluido este subpatrón.

Subpatrones modificadores

Patrón de búsqueda Representa a Código fuente

Ver ejemplo

(?-i)[A-Z](?i)[a-z]* El primer subpatrón activa la opción sensible a mayúsculas/minúsculas y la aplica al primer carácter. Para los siguientes caracteres el subpatron (?i) vuelve a desactivar esa opción.

Ver Probar

Subpatrones con aserciones

Otra situación especial es el caso de las aserciones. En este caso se contemplan cuatro posibilidades. En subpatrón y(?=x) donde xe y son condiciones, exige que se cumpla la condición x inmediatamente después de hacerlo la condición y. Si cambiáramos el signo = por! la expresión y(?!x) será interpretada como exigencia de que NO se cumpla la condición x después de hacerlo la condición y

Anteponiendo un < al signo = (o a !) podrían plantearse expresiones del tipo (?<=x)y ó (?<!x)y que serían exigencias del cumplimiento (o no cumplirse si se trata de !) de la condición x inmediatamente antes de que se cumpla la condición y.

Comprobación de los caracteres siguientes o anteriores - Aserciones o declaraciones

Patrón de búsqueda Representa a Código fuente

Ver ejemplo

(?<=[0-9])[a-z] Considera coincidencia cualquier letra ([a-z]) qué esté situada detrás de un número comprendido entre 0 y 9 ([0-9])

Ver Probar

(?<![0-9])[a-z] Considera coincidencias cualquier letra ([a-z]) qué NO ESTÉ situada detrás de un número comprendido entre 0 y 9 ([0-9])

Ver Probar

[0-9](?=[A-Z]) Considera coincidencia cualquier número ([0-9]) qué vaya delante (lleve detrás) de una letra ([A-Z]) Ver Probar

[0-9](?![A-Z]) Considera coincidencia cualquier número ([0-9]) qué NO VAYA delante (NO lleve detrás) una letra ([A-Z])

Ver Probar

Subpatrones condicionales

También existen los subpatrones condicionales que se comportan de una forma similar a los operadores condicionales. Mediante la sintaxis (?(x)y|z) o (?(x)y) se establecen dos posibilidades de coincidencia. Cuando se cumple la condición x será usado el patrón de búsqueda(o subpatrón) x. En caso de que no se cumpla la condición se utilizaría, en caso de haber sido especificado, el patrón z.

Page 172: Manual PHP

Las condiciones sólo pueden ser establecidas por retro referencias o mediante aserciones. Cuando se utilizan retro referencias no se aluden de la forma tradicional (\\n, \\gn o \\g{n}). En este caso bastaría incluir dentro del paréntesis que contiene la condición x el número de la retro referencia (n) sin anteponerle ningún otro carácter. Puedes verlo en los ejemplos de la tabla.

En la tabla que tienes a continuación hemos incluido una serie de ejemplos que contemplan algunas de las múltiples posibilidades que ofrecen los subpatrones condicionales. Es particularmente importante prestar mucha atención a la elaboración de los patrones para evitar resultados inesperados. En los ejemplos incluimos comentarios relativos a algunos de esos resultados que, de no tenerlos previstos, pueden parecer desconcertantes.

Caso en los que la condición es una retroreferencia

Patrones de búsqueda Representan a Código fuente

Ver ejemplo

([0-9]*)(?(1)[a-c]|[d-z]) Todos estos ejemplos tienen marcado en rojo el condicional, dentro de él van la retro referencia, el patrón que usará si se cumple la condición y el patrón que usará si no se cumple la condición.

Ver Probar

(\"|\')(\<.*?)*(?(2)\\1|.*?\\1) Ver Probar

((a)|(b))(?(3) f| c) Ver Probar

([0-9]*)(?(1)[a-c]) Los ejemplos anteriores sin incluir patrón para el caso en que no se cumple la condición. En los ejemplos se comenta lo relativo a los caracteres precedidos del símbolo \.

Ver Probar

(\"|\')(\<.*?)*(?(2)\\1) Ver Probar

((a)|(b))(?(3) f) Ver Probar

([0-9]*)(?(1)|[d-z]) Ahora hemos quitado a los ejemplos iniciales el patrón para el caso de que se cumpla la condición.

Ver Probar

(\"|\')(\<.*?)*(?(2)|.*?\\1) Ver Probar

((a)|(b))(?(3)| c) Ver Probar

([0-9]*)(?(1))

Aquí hemos eliminado del condicional ambos patrones.

Ver Probar

(\"|\')(\<.*?)*(?(2)) Ver Probar

((a)|(b))(?(3)) Ver Probar

Caso en los que la condición es el resultado de una comparación de igualdad o desigualdad

Patrón de búsqueda Representa a Código fuente

Ver ejemplo

(?(?<=[0-9][a-z])H|más) Si se cumple la condición verde(un número delante de una letra) agrupa esa condición a la H para conformar la palabra a buscar. Si no se cumple aquella condición considera patrón de búsqueda la palabra "más"

Ver Probar

(?(?<![0-9][a-z])H|más) Si se cumple la condición verde(un número delante de una letra) agrupa esa condición a la H para conformar la palabra a buscar. Si no se cumple aquella condición considera patrón de búsqueda la palabra "más"

Ver Probar

H(?(?=[0-9][a-z])|más) Si se cumple la condición verde(un número delante de una letra) agrupa esa condición a la H para conformar la palabra a buscar. Si no se cumple aquella condición considera patrón de búsqueda la palabra "más"

Ver Probar

Utilizando las opciones que nos ofrecen cada uno de los elementos que hemos ido viendo a lo largo de esta página podremos componer patrones de búsqueda que satisfagan casi todas nuestras necesidades. Es evidente que a medida que aumentemos los niveles de exigencia los patrones pueden ir adquiriendo una estructura bastante compleja. Veamos, en la página siguiente, los usos que podemos dar a los patrones en PHP.

Otras funciones con expresiones regulares

La función preg_match

Cuando se trata únicamente de conocer la existencia o inexistencia de texto dentro de una cadena o de un fichero puede resultar útil la función:

preg_match( patron, cadena, $coincidencias )

realiza una búsqueda en la cadena por medio de la expresión regular incluida en el patron. Si encuentra alguna coincidencia devuelve el valor booleano TRUE, en caso contrario devuelve FALSE.

Page 173: Manual PHP

El parámetro $coincidencias es opcional. Cuando se incluye recogerá en un array con ese nombre los resultados de la búsqueda.

Información sobre variables

Obtener información sobre variables

Veremos aquí algunas de las múltiples funciones que nos permiten obtener información sobre arrays y variables.

sizeof(variable)

La función sizeof devuelve el número de elementos de un array. Si se aplica sobre una variable (no array) devolverá uno y tanto si se aplica sobre un array vacío, una variable con valor nulo o sobre una variable no definida, devolverá cero.

count(variable)

Tiene idéntica funcionalidad a la de sizeof.

<?php $a=array("Lunes","Martes","Miercoloes","Jueves", "Viernes","Sábado","Domingo"); $b=array( "Precio" => "17340 euros", "Color" => "Rojo", "Llantas" => "Aluminio"); $c=47; $d=""; $e=array(); echo "<h3>Valores usando sizeof()</h3>"; @print " a tiene: ".sizeof($a)." elementos<br>"; @print " b tiene: ".sizeof($b)." elementos<br>"; @print " c tiene: ".sizeof($c)." elementos<br>"; @print " d tiene: ".sizeof($d)." elementos<br>"; @print " e tiene: ".sizeof($e)." elementos<br>"; @print " f tiene: ".sizeof($f)." elementos<br>"; print "<h3>Valores usando count()</h3>"; @print " a tiene: ".count($a)." elementos<br>"; @print " b tiene: ".count($b)." elementos<br>"; @print " c tiene: ".count($c)." elementos<br>"; @print " d tiene: ".count($d)." elementos<br>"; @print " e tiene: ".count($e)." elementos<br>"; @print " f tiene: ".count($f)." elementos<br>"; ?>

ejemplo149.php

isset(variable)

La función isset comprueba si una variable está definida. Si está definida –incluso con valor nulo o como array vacío– isset devolveráUNO, en caso contrario devolverá NUL. Esta función puede ser un complemento de sizeof o de count dado que permitiría comprobar si están definidas las variables en las que aquellas funciones devuelven dimensión cero.

empty(variable)

La función empty comprueba si una variable está vacía o no está definida. Si está vacía (valor nulo o array vacío) o no está definidaempty devolverá UNO, en caso contrario devolverá NUL.

Page 174: Manual PHP

¡Cuidado! No confundas isset con empty. No devuelven los mismos resultados. Mientras que isset sólo comprueba si una variable está definida (tiene un valor aunque sea nulo) la función empty comprueba que la variable no esté definida y, si lo está, que su valor sea nulo. is_array(variable)

Devuelve UNO (verdadero) en el caso de que la variable sea tipo array y NUL en el caso contrario.

is_long(variable) o is_int(variable) o is_integer(variable)

Estas tres funciones –que son idénticas– devuelven UNO en el caso de que la variable sea de tipo entero y NUL en el caso contrario.

is_float(variable) o is_double(variable)

Estas dos funciones –también idénticas– devuelven UNO en el caso de que la variable sea de tipo coma flotante y NUL en el caso contrario.

is_string(variable)

Devuelve UNO en el caso de que la variable sea tipo string y NUL en caso contrario.

unset(variable)

Destruye la variable indicada. Si después de aplicar unset sobre una variable aplicamos de nuevo la función isset, nos devolverá NUL indicando que ya no está definida. La función unset se puede aplicar tanto sobre variables como sobre un array y también sobre un elemento de un array.

<?php $a=array("Lunes","Martes","Miercoloes","Jueves", "Viernes","Sábado","Domingo"); $b=array( "Precio"=>"17340 euros", "Color" =>"Rojo", "Llantas" =>"Aluminio"); $c=47; $d=""; $e=array(); echo "<h3>Variables definidas</h3>"; echo " ¿Esta definida la variable a ? ",isset($a),"<br>"; echo " ¿Esta definida la variable b ? ",isset($b),"<br>"; echo " ¿Esta definida la variable c ? ",isset($c),"<br>"; echo " ¿Esta definida la variable d ? ",isset($d),"<br>"; echo " ¿Esta definida la variable e ? ",isset($e),"<br>"; echo " ¿Esta definida la variable f ? ",isset($f),"<br>"; echo "<h3>Identificación de tipos de variables</h3>"; echo " ¿La variable a es un array ? ",is_array($a),"<br>"; echo " ¿La variable a es tipo double ? ",is_double($a),"<br>"; echo " ¿La variable a es un entero ? ",is_int($a),"<br>"; echo " ¿La variable a es un entero ? ",is_long($a),"<br>";

Page 175: Manual PHP

echo "<h3>Eliminando variables</h3>"; echo "La dimensión de a es: ",count($a),"<br>"; unset($a[0]); echo "Nueva dimensión de a: ",count($a),"<br>"; echo "La dimensión de b es: ",count($b),"<br>"; unset($b); @print "Nueva dimensión de b: ".count($b)."<br>"; echo " ¿Sigue definida la variable b ? ",isset($b),"<br>"; print "<h1>Diferencias entre empty e isset</h1>"; print "<br />Cuando una variable no está definida ( \$sin_definir)<br /><br />"; print "El resultado de empty es (cero si no escribe nada): "; print empty($sin_definir); print "<br />El resultado de isset es (cero si no escribe nada): "; print isset($sin_definir); $soy_nula=""; print "<br /><br />Cuando una variable está definida con valor nulo ( \$soy_nula)<br />"; $definida; print "<br />El resultado de empty es (cero si no escribe nada): "; print empty($soy_nula); print "<br />El resultado de isset es (cero si no escribe nada): "; print isset($soy_nula); ?>

ejemplo150.php

Recuentos en arrays

Recuento de los valores contenidos en una matriz

Algunas de las posibilidades de obtener información sobre los contenidos de un array son las siguientes:

$recuento=array_count_values(array)

Con array_count_values la variable $recuento será un nuevo array cuyos índices son cada uno de los valores distintos que contengaarray y cuyos valores serán el resultado de contar el número de veces que se repite este nuevo índice en el array inicial. Distingue entremayúsculas y minúsculas. Cuando los valores del array inicial (array) son números enteros (sea array escalar o asociativo) $recuento será unarray escalar. En caso contrario, será asociativo.

Búsqueda de elementos en un array

claves=array_keys(array)

Devuelve un array escalar (claves) que contiene como valores los índices del array inicial (array).

claves=array_keys(array, valor)

Devuelve un array escalar (claves) que contiene como valores los índices de los elementos del array inicial cuyo valor coincide con el indicado mediante el parámetro valor. valores=array_values(array)

Esta función recoge en una nueva matriz (valores) todos los valores contenidos en el array. Es una forma de conversión de un array asociativo en otro escalar.

Page 176: Manual PHP

<?php $a=array(1,2,3,1,1,2,3,3,4,4,4,0,1); $b=array("blanco","azul","blanco","blanco","azul","Blanco","Azul"); $c=array( "a"=>"rojo", "b" =>"verde", "c" =>"rojo", "d" =>"rojo", "e" =>"verde", "f" =>"Rojo", "g" =>"Verde"); echo "<h3>Cuenta valores del array()</h3>"; $contador=array_count_values($a); foreach($contador as $valor=>$veces){ echo "El valor ",$valor," se repite ", $veces," veces en la matriz a<br>"; } echo $contador[0],"<br>"; echo $contador[1],"<br>"; echo $contador[2],"<br>"; echo $contador[3],"<br>"; echo $contador[4],"<br>"; $contador1=array_count_values($b); foreach($contador1 as $valor=>$veces){ echo "El valor ",$valor," se repite ", $veces," veces en la matriz a<br>"; } echo $contador1["blanco"],"<br>"; echo $contador1["azul"],"<br>"; echo $contador1["Azul"],"<br>"; echo $contador1["Blanco"],"<br>"; $contador2=array_count_values($c); foreach($contador2 as $valor=>$veces){ echo "El valor ",$valor," se repite ",$veces," veces en la matriz a<br>"; } echo $contador2["rojo"],"<br>"; echo $contador2["Verde"],"<br>"; echo $contador2["verde"],"<br>"; echo $contador2["Rojo"],"<br>"; echo "<h3>Devuelve las claves de un array</h3>"; $claves=array_keys($a); foreach($claves as $v){ echo "El valor ",$v," es una de las claves<br>"; } $claves1=array_keys($a,1); foreach($claves1 as $v){ echo "El valor ",$v," es una de las claves de elementos de la matriz cuyo valor es <b>1</b><br>"; } echo "<h3>Devuelve los valores de un array</h3>"; $valores=array_values($c); foreach($valores as $v){ echo $v," Este es un de los valores de de la matriz c<br>"; } ?>

ejemplo151.php

Ordenación de arrays (I)

Page 177: Manual PHP

Localización de valores en una matriz

in_array(valor,array)

La función in_array busca en la matriz (array) el valor (numérico o cadena) contenido en el parámetro valor. Si lo encuentra devuelve 1, y si no existiera devolvería NUL.

Posicionamientos en una matriz

Mediante estas funciones se puede modificar la posición del puntero interno de una matriz y determinar los índices de los elementos a los que apunta en cada momento.

key(array)

Devuelve el índice del elemento de la matriz al que apunta en ese momento el puntero interno de la matriz.

reset(array)

Desplaza el puntero interno a la posición del primer índice del array.

end(array)

Desplaza el puntero interno a la posición del último índice del array.

pos(array)

Mantiene el puntero interno en la posición del actual.

next(array)

Avanza el puntero interno en una posición respecto a la actual.

prev(array)

Retrocede el puntero interno en una posición respecto a la actual.

Moviendo el puntero interno

<?php $a=array(1,2,3,1,1,2,3,3,4,4,4,0,1); $b=array("blanco","azul","blanco","blanco","azul","Blanco","Azul"); $c=array( "a"=>"rojo", "b" =>"verde", "c" =>"rojo", "d" =>"rojo", "e" =>"verde", "f" =>"Rojo", "g" =>"Verde"); echo "<h3>Busca un valor en una matriz</h3>";

Page 178: Manual PHP

echo " Busca el valor en la matriz: <b>#",in_array(3,$a), "#</b> Si no ha puesto nada no estaba, si 1 lo encontró <BR>"; echo " Busca el valor en la matriz: <b>#",in_array(7,$a), "#</b> Si no ha puesto nada no estaba, si 1 lo encontró <BR>"; echo " Busca el valor en la matriz: <b>#",in_array("gris",$b), "#</b> Si no ha puesto nada no estaba, si 1 lo encontró <BR>"; echo " Busca el valor en la matriz: <b>#",in_array("blanco",$b), "#</b> Si no ha puesto nada no estaba, si 1 lo encontró <BR><br>"; echo "<h3>Posicionandose y determinando indices actuales</h3>"; echo "Este el valor asoaciado al indice 3 de la matriz a: ",$a[3],"<br>"; echo "El puntero interno apunta a la clave: ",key($a),"<br>"; echo "Este es el valor siguiente al anterior: ",next($a),"<br>"; echo "El puntero interno apunta a la clave: ",key($a),"<br>"; echo "Este es el primer valor de la matriz a: ",reset($a),"<br>"; echo "El puntero interno apunta a la clave: ",key($a),"<br>"; echo "Este es el ultimo valor de la matriz a: ",end($a),"<br>"; echo "El puntero interno apunta a la clave: ",key($a),"<br>"; echo "Este es el penúltimo valor de la matriz a: ",prev($a),"<br>"; echo "El puntero interno apunta a la clave: ",key($a),"<br>"; echo "Este es el mismo valor anterior: ",pos($a),"<br>"; echo "El puntero interno apunta a la clave: ",key($a),"<br>"; echo "Este el valor asoaciado al indice 4 de la matriz b: ",$b[4],"<br>"; echo "El puntero interno apunta a la clave: ",key($b),"<br>"; echo "Este es el valor siguiente al anterior: ",next($b),"<br>"; echo "El puntero interno apunta a la clave: ",key($b),"<br>"; echo "Este es el primer valor de la matriz a: ",reset($b),"<br>"; echo "El puntero interno apunta a la clave: ",key($b),"<br>"; echo "Este es el ultimo valor de la matriz a: ",end($b),"<br>"; echo "El puntero interno apunta a la clave: ",key($b),"<br>"; echo "Este es el penúltimo valor de la matriz a: ",prev($b),"<br>"; echo "El puntero interno apunta a la clave: ",key($b),"<br>"; echo "Este es el mismo valor anterior: ",pos($b),"<br>"; echo "El puntero interno apunta a la clave: ",key($b),"<br>"; ?>

ejemplo152.php

Ordenación de arrays (II)

Ordenaciones de arrays

Los elementos de un array se van ordenando según se van definiendo. Por tanto, su orden no es el mismo que el de los valores de sus índices. Las funciones PHP que ordenan los elementos de un array permiten dos opciones. Con una de ellas es posible la ordenación de los elementos sin modificar los valores de los índices, mientras que la otra sí modifica los índices. En el segundo de los casos la modificación puede afectar incluso al tipo de índices dado que los resultados de las ordenaciones –tanto si hemos partido de un array escalar como si lo hemos hecho desde uno asociativo– es siempre un array escalar.

Ordenación por valores sin mantener índices

sort(array)

Ordena los valores del array en sentido creciente y lo reindexa asignando índice CERO al menor de los valores.

Page 179: Manual PHP

rsort(array)

Ordena la matriz en sentido decreciente de sus valores y la reindexa asignando índice CERO al mayor de estos.

Ordenación por índices

ksort(array)

Ordena la matriz según sus índices y en sentido creciente de estos.

krsort(array)

Ordena la matriz por índices en sentido decreciente de los mismos.

Ordenación por valores manteniendo índices

asort(array)

Ordena la matriz según sus valores en sentido creciente y mantiene los índices del array original.

arsort(array)

Ordena la matriz por valores en sentido decreciente y sigue manteniendo los índices originales.

Ordenación mediante función definida por usuario

PHP permite que el usuario pueda definir funciones en las que establezca sus criterios particulares de ordenación. Las funciones PHP que permiten usar esta característica son las siguientes:

uasort(array, funcion)

Ordena la matriz utilizando los criterios establecidos por la función definida por el usuario y mantiene los índices del array.

usort(array, funcion)

Ordena la matriz por valores utilizando los criterios definidos en la función de usuario y modifica los índices.

uksort(array, funcion)

Ordena la matriz por claves utilizando los criterios definidos en la función.

En el ejemplo hemos definido una función de comparación siguiendo el criterio de ser o no ser múltiplo de 2. Trataremos lasfunciones en un tema aparte. La utilidad de la que hemos incluido en el ejemplo es la siguiente: Si el valor de la variable es par le asignamos un número negativo como respuesta y en caso contrario uno positivo. De esta forma los valores del array que devuelven negativos se consideran anteriores en la ordenación a los que dan como resultado un número positivo.

<?php $a=array(1,2,3,1,1,2,3,3,4,4,4,0,1); $b=array("blanco","azul","blanco","blanco","azul","Blanco","Azul"); $c=array( "b" =>"verde",

Page 180: Manual PHP

"c" =>"rojo", "e" =>"verde", "f" =>"Rojo", "g" =>"Verde", "a"=>"rojo", "d" =>"rojo",); sort ($a); echo "<h3>Ordenación por valores usando sort</h3>"; foreach ($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } sort ($c); echo "<h3>Ordenación por valores usando sort</h3>"; foreach ($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } rsort($a); echo "<h3>Ordenación inversa por valores usando rsort</h3>"; foreach ($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } ksort($b); echo "<h3>Ordenación por claves usando ksort</h3>"; foreach ($b as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } krsort($b); echo "<h3>Ordenación inversa por claves usando krsort</h3>"; foreach ($b as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } asort($c); echo "<h3>Ordenación por valores manteniendo indices </h3>"; foreach ($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } arsort($c); echo "<h3>Ordenación inversa por valores manteniendo indices arsort</h3>"; foreach ($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } echo "<h3>Ordenación por valores mediante función de usuario manteniendo indices</h3>";

Page 181: Manual PHP

/* esta función recoge el valor de la variable $a y aplicar el operador de comparación ternario de forma que si el valor de la variable es impar devuelve como valor -2 y si es par devuelve 2 el 2 y el menos 2 únicamente establecen criterios de comparación de modo que los valores -2 serán considerados anteriores a los valores +2 */ function micomparar (&$a) { return ($a%2!=0) ? -2 : 2; } uasort ($a, 'micomparar'); foreach ($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } echo "<h3>Ordenación por clave mediante función de usuario </h3>"; uksort ($a, 'micomparar'); foreach ($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } echo "<h3>Ordenación por valores mediante función de usuario </h3>"; usort ($a, 'micomparar'); foreach ($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor, "<br>"; } ?>

ejemplo153.php

Modificación de arrays

Modificaciones en arrays

var= range(inferior, superior)

Crea una nueva matriz escalar (var) en la que los valores de los elementos serán los números enteros (ordenados) pertenecientes al intervalo comprendido entre los valores inferior y superior, incluidos estos. Tanto inferior como superior deben ser números enteros.

shuffle(array)

Intercambia de modo aleatorio los valores de un array y los reindexa. Igual que ocurría en el caso de los números aleatorios, la funciónshuffle deberá ir precedida de una semilla del tipo srand. En el ejemplo hemos usado como semilla la función: srand(time()).

Page 182: Manual PHP

var= array_flip(array)

Devuelve un array (var) que contiene como valores los índices de la matriz array y como índices los valores del propio array.

Como quiera que los valores pueden estar repetidos y no es posible que lo estén los índices, esta función, en caso de valores repetidos, toma cada uno de esos valores una sola vez, lo utiliza como índice del nuevo array y asigna como valor del nuevo elemento el mayor de los índices –del array original– de los elementos que contuvieran ese valor.

Insertando elementos en un arrays

array_unshift(arr, v1,v2,..)

Inserta al principio de la matriz arr los valores v1, v2, etcétera que pueden ser tantos como se deseen y deben estar separados por comas.

array_push(array, v1,v2,..)

Inserta al final de la matriz array los valores v1, v2, etcétera, que igual que en el caso anterior, pueden ser tantos como se deseen y deben estar separados por comas. Tanto array_unshift como array_push asignan a los nuevos elementos índices numéricos.

array_pad(array, n, var)

Inserta nuevos elementos en array y les asigna el valor contenido en var. Insertará tantos nuevos elementos como sea necesario para que el array alcance una longitud de n elementos. Si el valor de n es positivo inserta los elementos al final del array, si fuera negativo los insertaría al comienzo del mismo. A los nuevos elementos del array se les asignan índices numéricos.

array_merge($a, $b)

Crea un nuevo array escalar en el que se incluyen todos los elementos contenidos en los arrays $a y $b.

Quitar elementos de un array

array_shift($a)

La función array_shift extrae el primer elemento del array $a.

array_pop($a)

La función array_pop extrae el último elemento del array $a.

array_slice($a,n)

La función array_slice extrae n elementos del array $a. Si el valor de n es positivo extraerá todos los elementos a partir del que ocupa la posición n contando desde el primero hasta el último según el orden de creación de los elementos. Si el valor de n es negativo extraerá todos los elementos a partir del enésimo, esta vez contando desde el último hasta el primero.

array_slice($a,n, m)

La función array_slice con dos parámetros permite extraer una parte de los valores de una matriz siguiendo estos criterios:

Page 183: Manual PHP

� – Si n y m son positivos, extraerá m elementos a partir del que ocupa la posición enésima de primero a último.

� – Cuando n es negativo y m es positivo se extraerán m elementos contados a partir del enésimo, esta vez recorriendo el array deúltimo a primero.

� – En el caso en que n tenga valor positivo y m sea negativo extraerá los comprendidos entre el enésimo contado de primero a últimoy el emésimo contado desde el último hasta el primero.

� – Si n es negativo y m es también negativo extraerá los caracteres comprendidos entre el enésimo contado de último a primero y elemésimo contado en el mismo sentido. En este caso se requiere que el valor absoluto de n sea mayor que el de m. En caso de no cumplirse esta condición devolverá un array vacío.

Invertir el orden de un array

array_reverse(array)

Devuelve un nuevo array cuyos elementos están en orden inverso al del array original. De esta forma el elemento que ocupaba la última posición pasa a ocupar la primera y así sucesivamente.

¡Cuidado! Recuerda que las posiciones iniciales de los elementos de un array no tienen relación con sus índices sino con la secuencia en la que fueron creados. Y otra cosa, mucho cuidado con la aplicación de todas estas funciones y con los índices de los arrays resultantes. Fíjate en los ejemplos y verás que algunas estas funciones reindexan los resultados y los convierten en escalares aún en el caso de que originalmente fueran asociativos. <?php $a=array(1,2,3,1,1,2,3,3,4,4,4,0,1); $b=array("blanco","azul","blanco","blanco","azul","Blanco","Azul"); $c=array( "b" =>"verde", "c" =>"rojo", "e" =>"verde", "f" =>"Rojo", "g" =>"Verde", "a"=>"rojo", "d" =>"rojo"); $C=array( "b" =>"verde", "c" =>"rojo", "e" =>"verde", "f" =>"Rojo", "g" =>"Verde", "a"=>"rojo", "d" =>"rojo"); echo "<h3>Crea una matriz de números enteros</h3>"; $r=range(7,11); foreach($r as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Intercambia aleatoriamente elementos en una matriz</h3>"; srand (time()); shuffle ($r);

Page 184: Manual PHP

foreach($r as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Intercambia valores e indices</h3>"; $p=array_flip($a); foreach($p as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; $q=array_flip($c); foreach($q as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Inserta elementos al principio de una matriz</h3>" ; array_unshift($a,97,"Pepe",128); foreach($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; array_unshift($c,97,"Pepe",128); foreach($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Inserta elementos al final de una matriz</h3>"; array_push($a,3.4,"Luis",69); foreach($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; array_push($c,3.4,"Luis",69); foreach($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Inserta elementos iguales al principio o al final de una matriz</h3>"; $wz1=array_pad($a,25,"relleno"); foreach($wz1 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; $wz2=array_pad($c,-17,"relleno");

Page 185: Manual PHP

foreach($wz2 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Fusiona dos matrices</h3>"; $wz3=array_merge($a,$b); foreach($wz3 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Extrae el primer elemento de una matriz</h3>"; array_shift ($a); foreach($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; array_shift ($c); foreach($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Extrae el ultimo elemento de una matriz</h3>"; array_pop($a); foreach($a as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; array_pop ($c); foreach($c as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Extrae elementos de una matriz</h3>"; $zz1=array_slice($a,3); foreach($zz1 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; $zz2=array_slice($a,-3); foreach($zz2 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; $zz3=array_slice($b,3,4); foreach($zz3 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; }

Page 186: Manual PHP

echo "<br>"; $zz4=array_slice($b,3,-2); foreach($zz4 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<br>"; $zz5=array_slice($b,-5,-2); foreach($zz5 as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } echo "<h3>Invierte los elementos de la matriz</h3>"; $inv=array_reverse($C); foreach($inv as $clave=>$valor){ echo "Clave: ",$clave," Valor: ",$valor,"<br>"; } ?>

ejemplo154.php

Funciones de usuario

¿Qué son las funciones de usuario?

De igual forma que lo hace el navegador en el caso del HTML, PHP lee e interpreta las instrucciones contenidas en los scripts de forma secuencial. Es decir, las instrucciones se van ejecutando en el mismo orden en el que aparecen en el documento original, con laexcepción de las funciones ya que estas son bloques de instrucciones son puestos a disposición de PHP que no se ejecutarán hasta el momento en que sean requeridas de forma expresa.

¿Dónde deben insertarse?

Aunque en versiones antiguas de PHP era necesario definir la función antes de invocarla, a partir de la versión 4 no es necesaria esa organización secuencial. La función pueden estar escrita dentro de cualquier script y en cualquier parte del documento, sin que tenga importancia alguna el lugar en el que se incluya la llamada a la misma.

También es posible –y bastante habitual– incluir funciones de uso frecuente en documentos externos de modo que pueden sercompartidas. En este caso, además de invocarla es necesario indicar a PHP el lugar donde debe buscarla. Hablaremos de ello cuando estudiemos lo relativo a include.

Definición de la función

Las funciones de usuario requieren la siguiente sintaxis:

function nombre ( ) { ..... ... instrucciones ... ..... }

Es imprescindible respetar estrictamente la sintaxis que requiere de forma obligatoria los siguientes elementos:

Page 187: Manual PHP

� – La palabra function debe estar escrita en minúsculas.

� – El nombre de la función, que debe seguir criterios similares a los de los nombres de variables, aunque en este caso no se antepone el símbolo $ ni ningún otro.

� – Los paréntesis (), incluso cuando no contengan nada.

� – Las llaves de apertura ({) y cierre (}) entre las que se escribirán las instrucciones correspondientes a la función.

Ejecución de la función

Las funciones PHP no se ejecutan en tanto no sean invocadas. Para invocar una función la sintaxis es la siguiente:

nombre()

Al ser llamada con esta sintaxis –desde cualquier script– se ejecutarán las instrucciones contenidas en ella.

Con este primer ejemplo obtendremos una página en blanco. El script contiene una función pero no hay ninguna instrucción que la invoque y por lo tanto no se ejecutaría.

<?php function a1(){ for($i=1;$i<=10;$i++){ echo $i,"<br>"; } } ?>

ejemplo155.php

<?php a1(); ?> <!-- Hemos escrito un script con una llamada a la función a1 que aún no está definida. Tendremos que hacerlo, pero no importa la parte del documento en la que lo hagamos La pondremos en este nuevo script PHP //--> <?php function a1(){ for($i=1;$i<=10;$i++){ echo $i,"<br>"; } } ?>

ejemplo156.php

Ámbito de las variables

Resumamos lo ya comentado cuando tratamos el tema de las variables.

� – Las funciones no leen valores de variables definidas fuera de su ámbito salvo que dentro de la propia función se definan de forma expresa como globales.

� – Si una función modifica el valor de una variable global, el nuevo valor persiste después de abandonar la función.

Page 188: Manual PHP

� – Si dentro de una función se utiliza un nombre de variable idéntico al de otra externa a ella (sin definirla global) la nueva variable se inicia con valor nulo y los eventuales valores que pudiera ir conteniendo se pierden en el momento en que se acaba su ejecución.

<?php # definamos dos variables y asignémosles un valor $a=5; $b=47; # escribamos una función a1 y pidámosle que imprima sus valores function a1(){ @print "Este es el valor de $a en la función a1: ".$a."<br>"; @print "Este es el valor de $b en la función a1: ".$b."<br>"; } # hagamos una llamada a la función anterior # no nos escribirá ningún valor porque esas variables no pertenecen # al ámbito de la función y serán consideradas como vacías # en el ámbito de la función a1(); # escribamos una nueva función, definamos como global $a # y comprobemos que ahora si la hemos incluido en el ámbito # de la función function a2(){ global $a; @print "Este es el valor de $a en la función a2: ".$a."<br>"; @print "Este es el valor de $b en la función a2: ".$b."<br>"; } # invoquemos esta nueva función y veamos que ahora # si se visualiza el valor de $a pero no el de $b a2(); # creemos una nueva función y ahora modifiquemos dentro de ella # ambas variables function a3(){ global $a; $a +=45; @$b -=348; echo "Este es nuevo valor de $a en la función a3: ",$a,"<br>"; echo "Este es el valor de $b en la función a3: ",$b,"<br>"; } # invoquemos la función a3 a3(); # comprobemos -desde fuera del ámbito de la función # que ocurrió con los valores de las variables echo "El valor de $a HA CAMBIADO despues de ejecutar a3 es: ",$a,"<br>"; echo "El valor de $b NO HA CAMBIADO despues de ejecutar a3 es: ",$b,"<br>"; # probemos que ocurre con una variable superglobal # veremos que sin ser definida expresamente en a4 # si pertenece a su ámbito y por lo tanto visualizamos su contenido function a4(){ print "La superglobales si están: ".$_SERVER['SERVER_NAME']."<br>"; } # invoquemos esta nueva función a4(); ?>

ejemplo157.php

Asignación de valores a variables

A las variables no globales se les pueden asignar sus valores iniciales de dos formas:

� • Incluyéndolas en una línea de instrucciones contenida en la propia función.

Page 189: Manual PHP

� • Insertando los nombres de variable y sus valores dentro del paréntesis que –de forma obligatoria– debe seguir al nombre de la función.

En este último caso la sintaxis sería:

function nombre_funcion ($variable1=valor1,$variable2=valor2)

donde $variable1 y $variable2 son nombres de variables a utilizar en el ámbito de la función y valor1 y valor2 los valores asignados a cada una de ellas. En este paréntesis pueden incluirse –separándolas con comas– cuantas parejas variable = valor sean necesarias.

Una forma alternativa a la anterior sería la siguiente:

function nombre_funcion ($variable1,$variable2)

que requeriría asignar los valores de cada una de la variables desde la llamada a la función, que ahora deberá tener esta sintaxis:

nombre (valor1, valor2,...);

y donde habría que escribir los valores separados por comas, y encerrados entre comillas cuando se trata de variables alfanuméricas.

Si el número de valores contenidos en la llamada fuera mayor que el número de variables definidas en la función, los excedentes serían ignorados y, si fuera inferior, se asignaría valor nulo a las variables a las que no se transfiriera ningún valor.

También es posible incluir en la llamada a la función los nombres de algunas variables definidas en el ámbito externo a la función. Se haría de la siguiente forma:

nombre ($var1, $var2,...); <?php $a=-13; $b=7482; $c="Ambrosio"; # esta es una forma alternativa de asignar valores a una variable # del ámbito de la función function a1($a=56, $b=25){ echo "El valor de $$a en la función a1: ", $a,"<br>"; echo "El valor de $$b en la función a1: ", $b,"<br>"; } a1(); echo "El valor de $a despues de ejecutar la función es: ",$a,"<br><br>"; # Pasando valores desde la llamada a la función # /* Definamos una función fun1 e incluyamos dentro de su paréntesis nombres de variables, separados por comas pero ahora sin asignarles ningún valor */ function fun1($x,$y,$z){ print "Valor de la variable x: ".$x."<br>"; print "Valor de la variable y: ".$y."<br>"; print "Valor de la variable z: ".$z."<br>"; } # debemos hacer la llamada a la función pero ahora # lo haremos de forma distinta.

Page 190: Manual PHP

# Vamos a incluir en la llamada # los valores que queremos asignar a las variables de la función # Escribiremos dentro del paréntesis de la llamada # los valores de cada una de las tres variables # separados por comas # (si se trata de una cadena, pongámosla entre comillas) # y veremos con la función recoge esos valores asignados #en la llamada fun1(14,"Robustiano",23.4); /* si esta llamada contuviera más de tres valores los últimos serian ignorados */ fun1(49.3,"Eustaquio",78,"Lupicio",456); # si contuviera menos de tres valores # PHP nos daría un mensaje de error # advirtiendo que falta un valor # pero nos devolvería los valores fun1("Desiderio","Bailador"); # esos mensajes de error podríamos evitarlos # poniendo una arroba delante de la llamada a la función @fun1("Nuevo Desiderio","Nuevo Bailador"); # también podría utilizarse una sintaxis como esta # en la que dejamos en blanco (entre comillas) # el espacio correspondiente al segundo valor # aunque si incluimos las comas. # La variable que ocupa esa posición # sería considerada como nula fun1("La luna",'',"verde"); # también podríamos incluir en la llamada nombres de variables # definidas en el ámbito general del sript # un este caso la función usaria esos valores fun1($a,$b,$c); ?>

ejemplo158.php

Pasar por referencia

Tal como hemos visto, las funciones PHP pueden recibir valores de variables externas y utilizar esos valores sin que el valor original de las mismas –salvo que se les asigne la condición de globales dentro de la función– sufra modificación.

Una manera de lograr que los valores de una variable externa puedan ser modificados por una función, es lo que se llama en argot informático «pasar variables por referencia».

La forma de hacerlo es esta:

� – Hay que anteponer al nombre de la variable el símbolo & y PHP interpretará que la estamos pasando por referencia.

� – El & puede anteponerse tanto en la definición de la función como en la llamada a la función, tal como puedes ver en el ejemplo.

La segunda de las opciones nos concede mayor libertad dado que permite usar una sola función y decidir en cada llamada la forma de pasar los parámetros.

<?php $a=3; $b=2; function a1($a,$b){

Page 191: Manual PHP

$a=pow($a,2); $b=pow($b,3); echo "El cuadrado de a dentro de la función es: ",$a, "<br>"; echo "El cubo de b dentro de la función es: ",$b, "<br><br>"; } a1($a,$b); echo "Al salir de la función a conserva la modificación: ",$a, "<br>"; echo "Por el contrario, b no la conserva: ",$b, "<br><br>"; $c=8; $d=12; function b1($a,$b){ $a=pow($a,2); $b=pow($b,3); echo "El cuadrado de a dentro de la función es: ",$a, "<br>"; echo "El cubo de b dentro de la función es: ",$b, "<br><br>"; } b1(&$c,$d); echo "Al salir de la función c conserva la modificación: ",$c, "<br>"; echo "Por el contrario, d no la conserva: ",$d, "<br><br>"; ?>

ejemplo159.php

¡Cuidado! Si tratas de ejecutar una función en la que colocas el & en la llamada a la función y te aparece un mensaje como este: «Warning: Call-time pass-by-reference has been deprecated -argument passed by value; If you would like to pass it by reference, modify the declaration of function(). If you would like to enable call-time pass-by-reference, you can set allow_call_time_pass_reference to true in your INI file». lo que estará ocurriendo es que php.ini tiene configurada en Off la directiva allow_call_time_pass_reference Eso suele ocurrir con algunos hostings y también con la configuración por defecto de algunas versiones de PHP. A partir de la versión 5.3.3 este mensaje sustituye Warning por Deprecated con lo cual nos advierte de que se trata de un función que se queda obsoleta y que, probablemente, será desactivida en versiones posteriores de PHP. La manera de evitar esa advertencia es no incluir en símbolo & en la llamada a la función.

Otra forma de definir funciones de usuario

Existe otra opción de definición de funciones de usuario que puede resultar de mucho interés. En este caso la función se define en tres bloques:

� – Definición de la función, llave de apertura { y cierre del script PHP (?>).

� – Contenido de la función formado exclusivamente por código HTML que será incluido en el documento cuando sea invocada la función que lo contiene.

� – Cierre de la función (llave de cierre }) contenido en un script PHP, es decir, entre las etiquetas de apertura (<?php) y cierre (?>) de un script de PHP.

Cuando es invocada una función definida de esta forma –puedes verlo en el ejemplo– PHP se limita a escribir en el documento final los textos contenidos entre la etiqueta de apertura y cierre de la función.

Las funciones de esta forma son particularmente útiles para la construcción de espacios web que contienen una serie de páginas en las que se repiten las mismas estructuras.

Page 192: Manual PHP

<?php function Encabezado() { ?> <!-- Hemos abierto la función y cerrado la etiqueta PHP todo esto es código HTML //--> <html> <head> <title>Titulo de mi página</title></head> <body bgcolor="#FF0000"> <!-- Esta nueva llamada a PHP insertando la llave de cierre de la función indicará a PHP que debe escribir todo lo contenido entre la { y esta } //--> <?php } ?> <?php function Pie() { ?> <HR> </body> </html> <?php } ?> <!-- Utilizaremos esas dos funciones para crear una página web. Llamamos a la función Encabezado luego escribimos un texto y por último insertamos el Pie de página con la función Pie //--> <?php Encabezado(); ?> Este es texto que aparecerá en el cuerpo de la página. Está fuera de los scripts de php y será considerado como un texto HTML. Debajo aparecerá la línea horizontal que insertaremos mediante una nueva llamada a la función Pie <?php Pie(); ?>

ejemplo160.php

Funciones que devuelven valores

Funciones que devuelven valores

Las funciones PHP pueden ser llamadas a partir de un script y posteriormente recoger –en ese mismo script– los resultados de su ejecución. Para conseguir este resultado debemos escribir dentro de la función la instrucción return seguida de la variable o la instrucción cuyo resultado queremos que sea devuelto al script desde el que ha sido llamada la función.

Tal como podemos ver en el ejemplo, los valores devueltos por return pueden ser presentados directamente en la página o recogidos por una variable.

También es posible que la función genere un array y que este sea devuelto a una variable que se convertiría a ese tipo de forma automática.

Otra opción de recoger los valores devueltos por return es invocar la función mediante una llamada del tipo:

list(v1, v2,..)=llamada

Las variables v1, v2, etc. recogerán los valores de los elementos del array devuelto por la función.

<?php # asignamos valores a dos variables $a=3; $b=5; # escribimos una función que eleva la variable a # a la potencia indicada en b # insertando return delante de la operación

Page 193: Manual PHP

function a1($a,$b){ return pow($a,$b); } #incluimos en la instrucción echo una llamada # a la función y en ella pasamos los valores # recogidos en las variables a y b # return conseguirá que se ejecute esa función # y que echo recoja e imprima el resultado echo "El valor de a elevado a b es: ",a1($a,$b),"<br>"; # esta otra función generará y devolverá un array # con los resultados de la ejecucion del bucle for function a2($a,$b){ for ($i=0;$i<=$b;$i++){ $z[]=pow($a,$i); } return $z; } # hacemos una llamada a la función $p=a2($a,$b); # leemos el array devuelto desde fuera de la función foreach($p as $clave=>$valor){ echo "El valor de a (3) elevado a: ",$clave," es: ",$valor,"<br>"; } echo "<br>"; # otra forma de leer el array con los resultados de la función list($r,$s,$t)=a2($a,$b); echo "Este es el valor recogido en la variable r :",$r,"<br>"; echo "Este es el valor recogido en la variable s :",$s,"<br>"; echo "Este es el valor recogido en la variable t :",$t,"<br>"; ?>

ejemplo161.php

Funciones de fecha

Funciones de fecha

PHP es pródigo en cuanto a posibilidades de manejo de fechas y horas. Para ello, cuenta, entre otras, con las siguientes funciones:

date (cadena de formato)

Devuelve valores de fecha y hora actuales utilizando los parámetros que se señalan en la tabla para establecer el formato de salida de los datos. Dentro de la misma cadena de formato puede contener tantos parámetros como se deseen.

Parámetros de formato de date()

Valor Funcionalidad Sintaxis Resultado

A AM-PM date("A") PM

a am-pm date("a") pm

d Día del mes en formato de 2 dígitos date("d") 06

j día del mes sin ceros a la izquierda date("j") 6

F Nombre del mes (texto completo) date("F") June

M Nombre del mes (3 letras) date("M") Jun

m Nº del mes (de 01 a 12) con dos dígitos date("m") 06

n Nº del mes (de 1 a 12) sin dos dígitos date("n") 6

Page 194: Manual PHP

Y Año con cuatro dígitos date("Y") 2012

y Año con dos dígitos date("y") 12

G Hora 0-23 sin ceros a la izquierda date("G") 18

H Hora 0-23 con dos dígitos date("H") 18

g Hora 1-12 sin ceros a la izquierda date("g") 6

h Hora 01-12 con dos dígitos date("h") 06

i Minutos de 00 a 59 con dos dígitos date("i") 12

s Segundos de 00 a 59 con dos dígitos date("s") 38

l día semana en texto completo date("l") Wednesday

D Día de la semana (tres letras) date("D") Wed

w día semana de 0 (domingo) a 6 (sabado) date("w") 3

z días transcurridos del año actual date("z") 157

t Número de días mes actual date("t") 30

L Año actual bisiesto (1), no bisiesto (0) date("L") 1

Z Diferencia (seg.) horaria local con GMT date("Z") 7200

U Segundos Unix Epoch date("U") 1338999158

S Sufijo ordinal inglés date("S") th

Como puedes ver en este ejemplo, respeta los caracteres separadores (espacios, dos puntos, guiones, etcétera) que se hubieran incluido en la cadena de formato siempre que no coincidan con ninguno de los parámetros PHP para esta cadena.

<?php echo "Son las ", date("h : i : s")," y hoy es ", date("j-n-Y")?> devolvería: Son las 06 : 12 : 38 y hoy es 6-6-2012 ¡Cuidado! No olvides que PHP se ejecuta en el servidor que suele estar en un ordenador remoto. Por lo tanto, fecha y hora locales se refieren al lugar donde está instalado el servidor y que en nuestro caso servidor y cliente coinciden en un mismo equipo y coincidirán la hora del sistema con la del servidor. Pero si alojaras esta página en un hosting australiano, PHP nos devolvería los valores con hora y fecha de lasantípodas. date (formato, número)

Esta función nos devuelve la fecha y hora del tiempo Unix indicado en el parámetro número. Recuerda que ese número indicasegundos contados a partir de la 0:00:00 (GMT) del día 1 de Enero de 1970.

Este script devolverá la fecha y hora en la que el tiempo Unix era de 456.573.426 segundos.

<?php echo "Fué a las ", date("h:i:s",456073426)," del ", date("j-n-Y",456073426) ?> devolvería: Fué a las 05 : 03 : 46 del 14-6-1984 gmdate(cadena formato)

Se comporta de forma idéntica a date() con la única diferencia de que devuelve la hora y fecha GMT. Si te fijas en el ejemplo habrá una o dos horas de diferencia según accedas a esta página en verano o invierno.

La fecha actual en hora GMT (observa la diferencia horaria):

<?php echo "Son las ", gmdate("h : i : s")," y hoy es ", gmdate("j-n-Y")?> devuelve: Son las 04 : 12 : 38 y hoy es 6-6-2012 gmdate (formato, número)

Page 195: Manual PHP

Los mismos comentarios que con el caso anterior. La única diferencia es que devuelve hora GMT.

La fecha y hora GMT coincidente con el tiempo Unix 456.073.426 es:

<?php echo "Fué a las ", gmdate("h:i:s",456073426)," del ", gmdate("j-n-Y",456073426) ?> devolvería: Fué a las 03 : 03 : 46 del 20-6-1984 checkdate(mes,día,año)

Comprueba si los valores de los parámetros mes están dentro del rango permitido (de 1 a 12), si el parámetro día es un valor válido para ese mes (considera años bisiestos) y si el valor del año pertenece al rango 0 a 32767.

Devuelve VERDADERO si los valores corresponden a una fecha correcta y FALSO en el caso de que no ocurra así.

Ejemplos de checkdate()

Mes Día Año Sintaxis Devuelve

10 32 1987 Checkdate(10,32,1987)

10 31 1987 Checkdate(10,31,1987) 1

2 29 2000 Checkdate(2,29,2000) 1

2 29 2001 Checkdate(2,29,2001)

gettimeofday()

Esta función devuelve un array asociativo con los siguientes índices:

� – sec El valor asociado a este índice del array recoge la hora actual (Unix Each) expresada en segundos

� – usec El valor asociado a usec recoge la fracción en microsegundos de hora actual (Unix Each)

� – minuteswest Devuelve los minutos al Oeste de Greenwich

� – dsttime Devuelve el tipo de corrección horaria según horarios de verano/invierno. El valor UNO corresponde a horario de verano, el valor CERO al de invierno y MENOS UNO en el caso en que sea desconocido.

Ejemplos de gettimeofday()

Sintaxis Devuelve

$z= gettimeofday(); echo $z;

Array

echo $z['sec']; 1338999158

echo $z[usec]; 727356

echo $z['minuteswest'']; -120

echo $z['dsttime']; 1

getdate()

Devuelve un array asociativo con parámetros de la fecha actual. Los índices de este array y sus valores son los que puedes ver en la tabla.

Ejemplos de getdate()

Funcionalidad Sintaxis Devuelve

Devuelve un array asociativo $s=getdate(); Array

Page 196: Manual PHP

echo $s;

Este índice devuelve los segundos de la hora actual echo $s['seconds'] 38

Este índice devuelve los minutos de la hora actual echo $s['minutes'] 12

Este índice devuelve la hora de la hora actual echo $s['hours'] 18

Este índice devuelve el día del mes actual echo $s['mday'] 6

Este índice devuelve el nº del día de la semana echo $s['wday'] 3

Este índice devuelve el nº del mes echo $s['mon'] 6

Este índice devuelve el año echo $s[year] 2012

Este índice devuelve nº del día en el año actual echo $s['yday'] 157

Este índice devuelve el día de la semana echo $s['weekday'] Wednesday

Este índice devuelve el nombre del mes echo $s['month'] June

getdate(número)

Interpreta el número como una fecha Unix Each (segundos transcurridos desde el día 1 de Enero de 1970) y devuelve un array asociativo con los valores relativos a esa fecha. Los índices de este array y sus valores son idénticos a los de getdate() tal como puedes ver en esta tabla.

Ejemplos de getdate(número)

Funcionalidad Sintaxis Devuelve

Devuelve un array asociativo $s=getdate(127648); echo $s;

Array

Este índice devuelve los segundos de la hora actual echo $s['seconds'] 28

Este índice devuelve los minutos de la hora actual echo $s['minutes'] 27

Este índice devuelve la hora de la hora actual echo $s['hours'] 12

Este índice devuelve el día del mes actual echo $s['mday'] 2

Este índice devuelve el nº del día de la semana echo $s['wday'] 5

Este índice devuelve el nº del mes echo $s['mon'] 1

Este índice devuelve el año echo $s['year'] 1970

Este índice devuelve nº del día en el año actual echo $s['yday'] 1

Este índice devuelve el día de la semana echo $s['weekday'] Friday

Este índice devuelve el nombre del mes echo $s['month'] January

time()

Esta función devuelve la hora actual en segundos expresada en tiempo Unix.

<?php print "Han transcurrido ".time(). " segundos desde las cero horas del día 01-01-1970";?> devolvería: Han transcurrido 1338999158 segundos desde desde las cero horas del día 01-01-1970 microtime()

Esta función devuelve una cadena formada la fracción de microsegundos de la hora actual expresada en tiempo Unix seguida de un espacio y de los segundos del tiempo Unix.

<?php print "Estamos en la fracción ".microtime(). " de segundo de la hora actual";?> devolvería: Estamos en la fracción 0.72747400 1338999158 de segundo de la hora actual mktime (hora, minuto, segundo, mes, día, año)

Devuelve el tiempo Unix de la fecha pasada como parámetro a la función. Es fundamental mantener la secuencia de los datos. Si se omiten argumentos (sólo pueden omitirse por la derecha) tomará los de la fecha actual.

Page 197: Manual PHP

Cuando el parámetro día es cero devuelve el último día del mes anterior, pero si pasamos cero como parámetro de mes nos dará un error.

Ejemplos de mktime()

H Min Sec Mes Día Año Tiempo Unix Fecha

23 12 57 6 16 1973 109116777 23:12:57 16-Jun-1973

23 12 57 6 16 1973 109116777 23:12:57 16-Jun-1973

25 12 57 6 16 1973 109123977 01:12:57 17-Jun-1973

23 97 57 6 16 1973 109121877 00:37:57 17-Jun-1973

23 12 57 14 16 1973 130284777 23:12:57 16-Feb-1974

23 12 57 14 0 1973 128902377 23:12:57 31-Jan-1974

En los ejemplos de esta tabla puede verse cómo para valores fuera de rango (mes mayor de 12, minutos mayor de 60, etcétera) la función realiza la corrección correspondiente.

¡Cuidado! En versiones anteriores a PHP 5.3.3 la función mktime incluía un séptimo parámetro (que se agregaba detrás de valor año) relativo a horarios de verano ó invierno. Ese parámetro era 0 si la fecha corresponde a horario de invierno y 1 ó -1 (según versiones) en el caso de horario de verano. Actualmente esa opción es considerada obsoleta por PHP. Por esta razón no usamos ese séptimo parámetro en nuestros ejemplos. Funciones de calendario

Los distintos calendarios

Días julianos

El sistema de días julianos fue creado por Joseph Justus Scaliger en 1582 y fue llamado así en recuerdo de su padre, Julius Cesar Scaliger. Se trata de un sistema de cuenta de días que tiene su origen en el 1 de Enero del año 4713 a.C. y que acabará el 31 de Diciembre de 3267. Este ciclo es producto de multiplicar tres ciclos menores: uno de 28 años denominado solar, otro de 19 años, que incorpora las fases lunares y uno de 15 años denominado de indicción.

El día juliano es el número resultante de contar los días transcurridos desde la fecha definida por Scaliger como comienzo del ciclo.

Calendario juliano

El emperador romano Julio Cesar ordenó en el año 44 a.C. la reforma del calendario. Sustituyó el lunar adoptando, con modificaciones, uno solar de origen egipcio que data del 4000 a.C. Con la asesoría de Sosígenes de Alejandría fijó la duración de cada año en 365,25 días, insertando un día suplementario –en febrero– cada cuatro años, -bis sextus dies ante calendas Martii- (dos sextos días antes de las calendas de marzo), haciendo bisiestos a todos los años cuyo número de orden sea divisible por cuatro.

Calendario gregoriano

A lo largo la Edad Media se siguió manteniendo en gran parte de Europa el calendario juliano con la única adaptación de fijar la fecha de referencia de la cuenta de años en el nacimiento de Cristo. Pero dado que la duración real del ciclo de translación de la tierra alrededor del sol es de 365,2422 días solares medios, el calendario juliano –con años de 365,25 días– empezaba a acumular un error importante.

El Papa Gregorio XIII realizó la corrección en el año 1582. Se descontaron diez días y es por eso que en 1582, al cuatro de octubre le siguió el día quince (viernes).

Page 198: Manual PHP

Para evitar sucesivos desfases se modificaron las condiciones de los años bisiestos que, en lo sucesivo, habrían de cumplir la condición de que su ordinal sea divisible por 4 y que no acabe en 00 con la excepción de los múltiplos de 400 que tendrían condición bisiestos.

Calendario judío

La era judía comienza a contar desde un supuesto año de la creación del mundo, que se calcula sumando las edades de las distintas generaciones mencionadas en la Biblia. El año judío se corresponde con el cristiano sumándole a éste 3.760 años.

El año judío es solar como el cristiano, pero sus meses son lunares, por lo que cada dos o tres años tiene que añadirse un mes bisiesto para adecuar al año solar el cómputo de los meses lunares.

Calendario republicano francés

El Calendario Republicano fue adoptado por la Convención Francesa partiendo de las propuestas técnicas formuladas por el matemático Lagrange. Es un intento de adaptar el calendario al sistema decimal y eliminar referencias religiosas. El comienzo del año coincidía con el día 22 de Septiembre, equinoccio de otoño, y se fijó su día uno del año uno el 22 de Septiembre de 1792, día de la proclamación de la República.

Consta 12 meses de 30 días, a los que se añaden cinco días complementarios (seis en los años que son divisibles por 4 y no por 100) que son festivos y no se asignan a ningún mes. Los meses se dividen en tres décadas de 10 días. El calendario fue de aplicación civil en Francia y sus colonias americanas y africanas hasta 1806.

Funciones PHP para el manejo de calendarios

PHP dispone de una serie de funciones que permiten convertir fechas según los distintos calendarios. Son estas:

gregoriantojd(mes,día,año)

Realiza la cuenta de días julianos correspondiente a la fecha gregoriana pasada en los parámetros mes, día y año.

El script <?php echo gregoriantojd (9, 27, 1999) ?> nos devolverá: 2451449 que es el día juliano correspondiente a la fecha gregoriana:27 de setiembre de 1999.

Este otro script <?php echo gregoriantojd (date("n"), date("j"), date("Y")) ?> nos devolverá el día juliano correspondiente a la fecha actual. Así que, para tu conocimiento y efectos pertinentes, hoy día 6 -6-2012 estamos celebrando el día juliano número 2456085.

jdtogregorian(nº de días julianos)

Esta función devuelve en fecha gregoriana –con formato: mes, día y año– el día juliano pasado como parámetro. Por si quieres ir preparando las celebraciones del 2.500.000 día juliano debes saber que coincidirá con la fecha 8/31/2132

jdtojulian(nº de días julianos)

Con esta función puedes obtener la fecha juliana a partir de un valor de la Cuenta de Días Juliana . Este script <?php echo jdtojulian(gregoriantojd (date("n"), date("j"), date("Y"))) ?> nos devuelve la fecha actual según el calendario julianoque es 5/24/2012 según el calendario juliano.

juliantojd(mes,día,año)

Convierte a Cuenta de días Julianos la fecha pasada (mes, día y año) del calendario juliano. Por ejemplo <?php echo juliantojd(7,25,2011) ?> nos devolverá 2455781 que corresponde a la cuenta de días correspondiente a la fecha 25/7/2001 expresada según el calendario juliano.

jdtojewish(nº de días julianos)

Page 199: Manual PHP

Esta función nos devuelve la fecha (mes, día y año) según el calendario judío a partir de una fecha expresada en días julianos. <?php echo jdtojewish (gregoriantojd (date("n"), date("j"), date("Y"))) ?> nos dará la fecha actual según el calendario judío que es: 10/16/5772.

jewishtojd(mes,día,año)

Nos devuelve el día juliano correspondiente a una determinada fecha del calendario judío. Por ejemplo: <?php echo jdtogregorian(jewishtojd(7,21,5758)) ?> nos devolverá 3/19/1998 que es la fecha gregoriana correspondiente al día 21 del séptimo mes del año 5758 según el calendario judío.

jdtofrench(nº de días julianos)

Esta función nos devuelve la fecha según el calendario republicano francés correspondiente al día juliano especificado como parámetro. <?php echo jdtofrench (gregoriantojd (5, 7, 1796)) ?> nos dará la fecha del calendario republicano francés que se corresponde con el 7 de Mayo de 1796 (gregoriano) que según parece es: 8/18/4.

Sólo convierte fechas comprendidas entre los años 1 y 14 (fechas Gregorianas del 22 de septiembre de 1792 al 22 de septiembre de 1806) que se corresponden con el período de vigencia oficial de este calendario.

frenchtojd(mes,día,año)

Convierte una fecha del calendario republicano francés en su equivalente en días julianos. Por ejemplo: <?php echo jdtogregorian(frenchtojd(6,7,8)) ?> nos devolverá 2/26/1800, que es la fecha gregoriana correspondiente al día 7 del sexto mes del año 8según el calendario republicano francés. Igual que la función anterior sólo convierte fechas comprendidas entres los años 1 y 14 del calendario francés.

jdmonthname(día juliano, calendario)

Devuelve del nombre del mes correspondiente al día juliano en el calendario señalado.

Ejemplos de jdmonthname()

Fecha gregoriana

Gregoriano abreviado

Gregoriano Juliano abreviado

Juliano Judío Republicano francés

3/1/1803 Jan January Dec December Tevet Nivose

3/2/1803 Feb February Jan January Shevat Pluviose

3/3/1803 Mar March Feb February AdarI Ventose

3/4/1803 Apr April Mar March Nisan Germinal

3/5/1803 May May Apr April Iyyar Floreal

3/6/1803 Jun June May May Sivan Prairial

3/7/1803 Jul July Jun June Tammuz Messidor

3/8/1803 Aug August Jul July Av Thermidor

3/9/1803 Sep September Aug August Elul Fructidor

3/10/1803 Oct October Sep September Tishri Vendemiaire

3/11/1803 Nov November Oct October Heshvan Brumaire

3/12/1803 Dec December Nov November Kislev Frimaire

Parámetro calendario 0 1 2 3 4 5

Los parámetros señalados en la fila inferior son los correspondientes a los tipos de nombres de mes señalados en los encabezados de la tabla.

Día de Pascua

El Día de Pascua fue fijado en el Concilio de Nicea (año 325) como el domingo siguiente a la primera luna llena posterior al equinoccio de Primavera. Este equinoccio se supone que siempre coincide con el 21 de marzo.

Page 200: Manual PHP

El algoritmo que usa PHP para su cálculo se basa en el que desarrolló Dionisio Exiguo en el año 532. Para los años anteriores a 1753, (calendario Juliano) usa un ciclo simple de 19 años para calcular las fases de la luna. En los años posteriores a esa fecha (Calendario Gregoriano) se añaden dos factores de corrección que tratan de hacer ese ciclo más preciso. Mediante la función:

easter_date(año)

Devuelve -en tiempo Unix- la media noche del día de Pascua del año establecido como parámetro. Esta función sólo es válida cuando los valores del año están comprendidos entre 1970 y 2037 (tiempo UNIX).

El script echo date( "j-n-Y", easter_date(2011)) nos señala que la Pascua del año 2006 ha sido el día 24-4-2011.

easter_days(año)

Devuelve el número de días del período comprendido entre el 21 de marzo y el día de Pascua. Si no se especifica el año, se asume el actual. No tiene las limitaciones de la función anterior y es aplicable a años fuera del intervalo de tiempo UNIX.

El script easter_days(2011) nos señala que la Pascua del año 2011 ha sido 34 días después del 21 de Marzo.

¡Cuidado! Si piensas en la posibilidad de utilizar estas funciones en alguna aplicación concreta que pretendas publicar en unhosting de la red cerciórate antes de que estén habilitadas. La opción INCLUDE

Utilización de ficheros externos

PHP dispone de funciones que permiten insertar en un documento una parte o la totalidad de los contenidos de otro. Esta opción resulta muy interesante, tanto desde el punto de vista operativo como en lo relativo a la seguridad.

Estos son algunos de los recursos que permiten ese tipo de inclusiones:

include("nombre del fichero")

El parámetro nombre del fichero es una cadena que contiene el path y el nombre del fichero cuyos contenidos pretendemos incluir. Pueden incluirse ficheros con cualquier extensión aunque es muy habitual utilizar archivos con extensión .inc.php.

La primera parte (inc) nos permitirá identificar este tipo de ficheros mientras que la extensión php obligaría a que (si un usuario malicioso pretende visualizar el contenido del fichero) fuera interpretado por PHP y, como consecuencia de ello, solo devolvería el resultado sin permitir la visualización de informaciones privadas (contraseñas por ejemplo) que pudiera contener.

Este primer ejemplo de fichero a incluir contiene únicamente texto y etiquetas HTML pero no contiene ninguna llamada a ninguna función PHP, ni tampoco ningún script de este lenguaje. Lo hemos guardado con dos extensiones: ejemplo1.inc.php y ejemplo1.inc.

Si pulsas sobre las opciones Ver ejemplo podrás comprobar que los resultados de visualización son distintos dependiendo de la extensión de cada fichero.

<h3><font color="#ff0000">Este sería un texto que se incluiría dentro de la página mediante las funciones include o require</font></h3><br>

Ver ejemplo1.inc Ver ejemplo1.inc.php

Este tipo de ficheros pueden contener: texto, etiquetas HTML y funciones. Si no contiene funciones se podrá insertar tantas veces como se invoque y se insertará, además, todo su contenido tal como puedes ver en el ejemplo.

Page 201: Manual PHP

Si el fichero contiene funciones solo podrá ser invocado una vez ya que si se hiciera una segunda llamada se produciría un error por duplicidad en los nombres de las funciones. Como verás en el ejemplo, es posible incluir cualquier tipo de funciones tanto las de la forma:

<?php function nombre { ?> ..... ... código HTML ... ...... <?php } ?>

con las que se pueden incluir porciones de código HTML en cualquier script, como las del tipo:

function nombre { ..... ... instrucciones PHP ... ...... }

que permiten invocar funciones repetitivas a partir de cualquier documento PHP.

<?php function Encabezado() { ?> <html> <head> <title> Pruebas con la función include </title> </head> <BODY> <center><img src="./images/cabina.jpg"><br> <font size=6 face="Times" color="#0000ff">Pruebas PHP</font><br> <hr width=75%> <?php } ?> <?php function Pie() { ?> <center><hr width=50%> <font size=2 face="Arial" color="#ff0000">Luchando con PHP</font> <hr width=50%></center> </body> </html> <?php } ?> <?php function Calcula($a,$b) { return $a*$b; } ?>

Ver ejemplo2.inc Ver ejemplo2.inc.php

Definidas las funciones en el fichero a incluir y colocado al comienzo de la página un script que contenga el include y la ruta de ese fichero, bastará con invocar cualquiera de las funciones, en cualquier punto del documento, para que esa llamada sea sustituida por el resultado de la ejecución de la función.

Como es lógico, solo serán visualizados en el navegador del cliente los resultados de la ejecución de las funciones que hayan sidoinvocadas.

<!-- empezaríamos incluyendo el fichero que contiene las funciones. No escribiría nada hasta que las funciones que contiene no fueran invocadas //-->

Page 202: Manual PHP

<?php include("ejemplo2.inc.php") ?> <!-- Insertaremos un script PHP que invoque la función encabezado. Debe llevar las etiquetas de apertura y cierre <?php y ?> de PHP //--> <?php Encabezado() ?> <!-- Insertaremos código HTML según nuestra conveniencia //--> Aquí iría el contenido de la página<br> .... esto es texto HTML................<br> ...........................<br><br><br> <!-- Incluimos el fichero ejemplo1.inc.php y dado que no contiene ninguna función, insertará aquí todo su contenidos //--> <?php include("ejemplo1.inc.php") ?> <!-- Insertaremos más código HTML --> ....................<br> ...........................<br><br><br> <!-- Incluimos nuevamente el fichero ejemplo1.inc.php Puede repetirse la inclusión porque no contiene funciones, si las contuviera habría un conflicto de duplicidad porque una funcion no puede estar definida dos veces con el mismo nombre. La instrucción include como en todos los casos deberá ir dentro de un script PHP y por tanto entre <?php y ?> //--> <?php include("ejemplo1.inc.php") ?> <!-- Ahora ejecutaremos la funcion PHP Calcula() pasando como parámetros 7 y 9. El return de la función nos devolverá el resultado que imprimiremos aquí //--> <?php print "Aquí va el resultado de la multiplicación: ".Calcula(7,9); ?> <!-- Por último invocaremos la función Pie() --> <?php Pie() ?>

Ver ejemplo162.php

La función require()

Tiene la misma sintaxis que include y una funcionalidad similar, aunque con algunas diferencias. Igual que ocurría con aquél, cuando un fichero es invocado por require esa llamada lo que hace es sustituirse a sí misma por el contenido del fichero especificado.

A diferencia de include, la etiqueta require lee y escribe –en el documento a partir del que es invocada– el archivo referenciado completo y no acepta condicionales que sí son aceptados por include.

Evitar errores por duplicidad de llamadas

Tanto en el caso de usar la instrucción include como con require, si se intenta incluir dos o más veces un fichero que contenga funciones, se producirá un error (PHP no permite que dos funciones tengan el mismo nombre) y se interrumpirá la ejecución del script.

Los errores de ese tipo pueden evitarse usando las funciones:

include_once("fichero") y require_once("fichero")

que a diferencia de include y requiere van a impedir que un mismo fichero pueda incluirse dos veces.

En los ejemplos vas a poder comprobar que no aparece el mensaje de error cuando se utiliza esta función y que el texto que –anteriormente– era incluido dos veces ahora solo aparece una vez.

Page 203: Manual PHP

<!-- Repetimos la inclusión de ambos ficheros pero veremos que no aparece mensaje de error por duplicidad de funciones y además pese a hacer una doble inserción de ejemplo1 solo se visualiza una vez por efecto del filtro establecido por include_onde //--> <?php include_once("ejemplo2.inc.php") ?> <?php Encabezado() ?> Aquí iría el contenido de la página<br> ....................<br> ...........................<br><br><br> <?php include_once("ejemplo1.inc.php") ?> ....................<br> no aparecerá nada aquí debajo<br><br><br> <?php include_once("ejemplo1.inc.php") ?> <?php include_once("ejemplo2.inc.php") ?>

Ver ejemplo163.php

El resultado de este otro ejemplo es idéntico al anterior. Sólo hemos sustituido include_once por require_once, que tiene una funcionalidad similar a la anterior.

<?php require_once("ejemplo2.inc.php") ?> <?php Encabezado() ?> Aquí iría el contenido de la página<br> ....................<br> ...........................<br><br><br> <?php require_once("ejemplo1.inc.php") ?> ....................<br> ....nada de aquí en adelante ...<br><br><br> <?php require_once("ejemplo2.inc.php") ?> <?php require_once("ejemplo1.inc.php") ?>

Ver ejemplo164.php

Comprobando los ficheros incluidos

PHP dispone de dos funciones que permiten recoger en un array la lista de ficheros que se han insertado en el documento por medio de alguna de las instrucciones: require_once, require, include_once e include. Tales funciones son estas:

$v=get_included_files()

Recoge en un array escalar (contenido en la variable $v) los nombres de los ficheros incluidos en el archivo en uso por include_once.

$v=get_required_files()

Igual que la función anterior recoge en un array escalar (contenido en la variable $v) los nombres de los ficheros incluidos en el archivo en uso mediante require_once.

<?php include_once("ejemplo2.inc.php") ?> <?php Encabezado() ?> <?php include_once("ejemplo1.inc.php") ?> <?php Pie() ?>

Page 204: Manual PHP

Lista de fichero utilizados por include <?php $z= get_included_files(); foreach($z as $clave=>$valor) { echo "Clave: ",$clave," Valor: ",$valor,"<br>"; }; ?>

Ver ejemplo165.php

Mejorando la seguridad

Hemos hablado de la función show_source, que permitía visualizar el código fuente de los scripts –no solo locales sino de cualquier URL- si esta función no estaba desactivada en el php.ini.

Esa posibilidad de ver no sólo permite el espionaje industrial (ver la técnica de construcción de los scripts, etcétera) sino que permite ver también las claves y contraseñas de acceso a las bases de datos que pudieran contener los scripts.

Aparte de simplificar el trabajo la opción de incluir ficheros externos permite guardar la información confidencial fuera del root del servidor y usarla mediante estas llamadas. De ese modo –show_source– permitiría visualizar el nombre de ese fichero externo pero no su contenido.

Si creáramos un directorio –por ejemplo como subdirectorio de c:\Apache (fuera del root del servidor)– y le ponemos como nombre sg, podríamos guardar allí los ficheros ejemplo2 y ejemplo1, con lo cual sus contenidos no serían visibles con show_source.

En este caso la instrucción include ha de contener el path y sería la siguiente:

include("C:\Apache\sg\fichero"). ¡Cuidado! La manera de escribir los path difiere de un sistema operativo a otro. Bajo Windows debemos usar \ como separador, mientras que otros S.O. (Unix, Linux, etcétera) requieren utilizar /. Para publicar tus páginas en un hosting no Windows tendrías que modificar tus scripts. Tenlo en cuenta

Utilizando include para gestión de fechas

Las funciones que incluye PHP para el manejo de fechas solo contemplan periodos posteriores a 1970. Para el caso de fechas anteriores a esta, existen funciones que pueden descargarse desde: http://phplens.com/lens/dl/adodb-time.zip.

El archivo comprimido contiene un fichero -adodb-time.inc.php- con funciones PHP que se comportan de forma idéntica a las nativas de PHP y que, además, permiten utilizar fechas anteriores a 1970 y valores negativos del tiempo Unix.

Para utilizar estas funciones bastaría con poner en el script include("adodb_time.inc.php"); y sustituir las funciones de fecha de acuerdo con lo que se indica en esta tabla:

Función PHP Función Adodb-time

getdate() adodb_getdate()

date() adodb_date()

gmdate() adodb_gmdate()

mktime() adodb_mktime()

En este enlace puedes comprobar los resultados de la aplicación de estas funciones que, como verás, son idénticos a los que

Page 205: Manual PHP

hemos visto en el tema Funciones de fecha con la salvedad de que en este caso se admiten fechas anteriores a 1970 y tiempos Unix negativos.

Manejo de ficheros externos

Utilización de ficheros externos

PHP dispone de funciones mediante las cuales se pueden crear, modificar, borrar y leer ficheros de cualquier tipo así como extraer información sobre ellos y sus contenidos.

Abrir o crear ficheros

Para crear o modificar ficheros se utiliza la instrucción:

$f1=fopen(fichero,modo)

dónde $f1 es una variable que recoge el identificador del recurso, un valor importante (será utilizado para referirnos a este fichero en instrucciones posteriores), fichero es el nombre (con extensión) del fichero a abrir o crear y deberá escribirse entre comillas, y modo, que es una cadena que debemos poner entre comillas, el indicador del modo de apertura elegido.

En la tabla hemos enumerado las opciones de ese parámetro.

Valores del parámetro modo de la función fopen

Valor Funcionalidad

r Abre el fichero en modo lectura y coloca el puntero al comienzo del fichero

r+ Abre el fichero en modo lectura y escritura y coloca el puntero al comienzo del fichero

w Abre el fichero en modo escritura y coloca el puntero al comienzo del fichero, reduce su tamaño a cero y si el fichero no existe intenta crearlo

w+ Abre el fichero en modo lectura y escritura y coloca el puntero al comienzo del fichero, reduce su tamaño a cero y si el fichero no existe intenta crearlo

a Abre el fichero en modo escritura y coloca el puntero al final del fichero y si no existe intenta crearlo

a+ Abre el fichero en modo lectura y escritura y coloca el puntero al final del fichero y si no existe intenta crearlo

Si el fichero que pretendemos abrir está en un directorio distinto al del script, debe incluirse el path completo delante del nombre del fichero y la cadena resultante debe ir entre comillas.

¡Cuidado! Si incluimos, junto con el nombre del fichero, un path hay que tener muy presente que bajo Windows hemos de utilizar siempre el separador anti-slash (\).

Cerrar ficheros

Una vez finalizado el uso de un fichero es necesario cerrarlo. Para ello PHP dispone de la siguiente intrucción:

fclose($f1)

Esta función -que devuelve un valor booleano- permite cerrar el fichero especificado en $f1 que, como recordarás, es el valor delidentificador de recurso que le fue asignado automaticamente por PHP en el momento de la apertura.

Punteros internos

Page 206: Manual PHP

PHP dispone de funciones para situar sus punteros internos y también para determinar la posición a la que apuntan en un momento determinado. Se trata de las siguientes:

feof($f1)

Es un operador booleano que devuelve CIERTO (1) si el puntero señala el final del fichero y FALSO si no lo hace.

rewind($f1)

Coloca el puntero interno al comienzo del fichero indicado por el identificador del recurso $f1.

fseek($f1, posición)

Sitúa el apuntador del fichero señalado por el identificador del recurso $f1 en la posición (expresada en bytes) señalada por posición.

ftell($f1)

Devuelve (expresada en bytes) la posición actual del puntero interno del fichero.

¡Cuidado! Antes de utilizar funciones es necesario que el fichero que señala el identificador de recursos haya sido abierto.

Lectura de ficheros

La lectura de los contenidos de un fichero puede hacerse de dos maneras: sin apertura previa o con apertura previa del mismo.

Lectura de ficheros sin apertura previa

Las funciones que permiten la lectura de ficheros sin haber sido abiertos previamente son las siguientes:

$t=file_get_contents(fichero)

Recoge en la variable $t el contenido del fichero cuyo nombre y eventual ruta se especifican en el parámetro fichero.

readfile(fichero)

Escribe directamente en el punto de inserción del script el contenido completo del fichero.

¡Cuidado! La función readfile escribe el contenido del fichero sin necesidad de ir precedido por echo ni print. Si se pone echo o se recoge en una variable, además de su contenido añadirá un número que indica el tamaño del fichero expresado en bytes. $var=file(fichero)

Crea $var –un array escalar– cuyos elementos tienen como valores los contenidos de cada una de las líneas del fichero. Una línea termina allí donde se haya insertado un salto de línea en el fichero original.

Page 207: Manual PHP

Lectura de ficheros con apertura previa

Para la utilización de estas funciones los ficheros han de ser abiertos en un modo que permita la lectura.

fpassthru($f1)

Hace la lectura completa del fichero. Esta función presenta algunas peculiaridades importantes:

� • Cierra el fichero de forma automática después de la lectura. Por esa razón, si se escribe la función fclose a continuación defpassthru, se produce un error.

� • Si el resultado se recoge en una variable, o si va precedido de echo, además de escribir el contenido del mismo, añadirá el número de bytes que indican su tamaño.

fgets($f1,long)

Extrae del fichero señalado por el $f1 una cadena –que comienza en la posición actual del puntero– y cuya longitud está limitada por el menor de estos tres valores:

� • El valor (en bytes) indicado en long.

� • La distancia (también en bytes) desde la posición actual del puntero hasta el final del fichero.

� • La distancia que hay entre la posición actual del puntero y el primer salto de línea.

fgetc($f1)

Extrae el caracter siguiente al señalado por la posición actual del puntero.

Escribir en un fichero

Una vez abierto un fichero -en modo que permita escritura- la función PHP que nos permite escribir en el es la siguiente:

fwrite($f1,"texto",long)

donde: $f1 sigue siendo el identificador de recurso, texto la cadena de texto a insertar en el fichero y long el número máximo de caracteres que han de insertarse. Si la cadena de texto tiene menor o igual longitud que el parámetro long la escribirá en su totalidad, en caso contrario sólo escribirá el número de caracteres indicados.

fputs($f1,"texto",long)

Es un alias de la función anterior.

file_put_contents(nombre_fichero,$cadena)

Crea un fichero cuya ruta, nombre y extensión se definen mediante la cadena nombre_fichero e incluye en él el contenido de la variable$cadena. Esta función es idéntica a llamar a fopen(), fwrite() y fclose() sucesivamente para escribir información en un archivo.

¡Cuidado! Estas funciones realizan la inserción de la cadena a partir de la posición a la que apunte el puntero en el momento de ser invocadas. Si el fichero ya existiera y contuviera datos los nuevos datos se sobrescribirían sobre el contenido anterior. Para poder añadir contenidos a un fichero el puntero deberá apuntar el final del fichero preexistente y estar abierto en un modo que permita añadir contenidos.

Page 208: Manual PHP

Borrado de ficheros

Para borrar ficheros se utiliza la siguiente instrucción:

unlink(fichero)

Dónde fichero ha de ser una cadena que contenga el nombre y la extensión del fichero y, en su caso, también el path.

Duplicado de ficheros

La función:

copy(fichero1, fichero2)

Copia el fichero fichero1 (debe indicarse nombre y extensión) en otro fichero cuyo nombre y extensión se establecen en la cadenafichero2. Esta función devuelve un valor booleano indicando si la copia se ha realizado con éxito TRUE (1) o FALSE (nul) si por alguna razón no ha podido realizarse la copia.

Renombrar ficheros

La función: rename(fichero1, fichero2)

cambia el nombre del fichero fichero1 (hay que poner nombre y extensión) por el indicado en la cadena fichero2. Igual que la anterior, devuelve TRUE o FALSE. Si intentamos cambiar el nombre a un fichero inexistente nos dará error.

Funciones informativas

PHP dispone de funciones que nos facilitan información sobre ficheros. Algunas de ellas son las siguientes:

file_exists(fichero)

Esta función devuelve TRUE si el fichero existe, en caso contrario devuelve FALSE.

filesize(fichero)

Devuelve el tamaño del fichero expresándolo en bytes. En caso de que el fichero no existiera nos dará un error.

filetype(fichero)

Devuelve una cadena en la que se indica el tipo del fichero. En caso de que el fichero no existiera nos dará un error.

filemtime(fichero)

Devuelve –en tiempo Unix– la fecha de la última modificación del fichero.

stat(fichero)

Page 209: Manual PHP

Devuelve un array que contiene información sobre el fichero.

Hemos creado un fichero llamado domingo.txt para poder utilizarlo en los ejemplos. Su contenido es exactamente el siguiente (incluidos los saltos de línea):

Esto es un ejemplo para comprobar si funcionan o no los saltos de línea en un documento de texto que será leído desde php

En la tabla puedes ver los contenidos asociados a cada uno los índices del array que contiene resultado de aplicar la función stat al fichero domingo.txt.

Indice Significado Sintaxis Resultado

0 Dispositivo <? echo $d[0] ?> 2056

1 I node <? echo $d[1] ?> 28607187

2 Modo de protección de I node <? echo $d[2] ?> 33279

3 Número de enlaces <? echo $d[3] ?> 1

4 Id de usuario del propietario <? echo $d[4] ?> 32031

5 Id de grupo del propietario <? echo $d[5] ?> 32033

6 tipo de dispositivo si es un inode device * <? echo $d[6] ?> 0

7 Tamaño en bytes <? echo $d[7] ?> 126

8 Fecha del último acceso <? echo $d[8] ?> 1338948639

9 Fecha de la última modificación <? echo $d[9] ?> 1312376374

10 Fecha del último cambio <? echo $d[10] ?> 1312526279

11 Tamaño del bloque para el sistema I/O * <? echo $d[11] ?> 4096

12 Número de bloques ocupados * <? echo $d[12] ?> 8

Los valores señalados con * devuelven -1 en algunos sistemas operativos, entre ellos Windows>

Otras funciones

Existen otras muchas funciones relacionadas con el manejo de ficheros y directorios, así como con los permisos de acceso, etcétera. Hemos resumido únicamente las de mayor interés.

Si quieres profundizar en este tema a través de este enlace podrás acceder al capítulo del Manual de Referencia oficial de PHP, en el que se describen las funciones relacionadas con el manejo de ficheros.

Ejemplos del uso de algunas funciones sobre ficheros

<?php /* abrimos con w+ con lo cual borramos el contenido y creamos el fichero en el caso de que no existiera */ $f1=fopen("sabado.txt","w+"); # escribimos en el fichero vacío fwrite($f1,"Esta es la primera linea que escribimos en el fichero<br>"); #cerramos el fichero fclose($f1); echo "<H2>Este es el resultado después del primer fwrite</H2><br>"; include("sabado.txt"); # abrimos con r+ con lo cual sobreescribiremos # en el fichero preexistente $f1=fopen("sabado.txt","r+"); # escribimos en al principio del fichero preexistente # ya que al abrir un fichero en este modo el puntero # se sitúa al comienzo del fichero

Page 210: Manual PHP

fputs($f1,"Esto se sobreescribe"); #cerramos el fichero fclose($f1); echo "<H2>Este es el resultado después del segundo fwrite</H2><br>"; include("sabado.txt"); # abrimos con a+ con lo cual AÑADIREMOS # al fichero preexistente ya que el modo de apertura # sitúa el puntero al final del fichero $f1=fopen("sabado.txt","a+"); # escribimos al final del fichero preexistente fputs($f1," Esto se añadirá al final<br>"); #cerramos el fichero fclose($f1); echo "<H2>Este es el resultado después del tercer fwrite</H2><br>"; include("sabado.txt"); echo "<h2>Leyendo con fgetc</h2><br>"; # abrimos con r+ con lo cual podemos LEER y AÑADIR # al fichero preexistente $f1=fopen("sabado.txt","r+"); # leemos el primer carácter del fichero # ya que el apuntador esta el principio $z=fgetc($f1); # imprimimos el primer carácter echo "He leido el primer carácter: ",$z,"<br>"; /* leemos el segundo caracter del fichero ya que el apuntador se ha movido a esa posición al leer anteriormente el primer carácter. OBSERVA que NO HEMOS CERRADO AUN EL FICHERO */ $z=fgetc($f1); # este es el nuevo valor de la variable $z echo "He leido el segundo carácter: ",$z,"<br>"; /* leemos el siguiente caracter del fichero ya que el apuntador se ha movido a una nueva posición Recuerda que NO HEMOS CERRADO AUN EL FICHERO */ $z=fgetc($f1); # este es ahora el valor de la variable $z echo "He leido el tercer carácter: ",$z,"<br>"; echo "<h2>Ahora el puntero está en el tercer caracter<br>"; echo "fgets empezará a leer a partir de el</H2>";; $z=fgets($f1,200); echo "Con fgets he leido esto: ",$z,"<br>"; #Ahora cerramos el fichero fclose($f1); echo "<br><H2>Al abrir el fichero de nuevo fgets comienza desde el principio</h2><br>"; #Abrimos de nuevo el fichero $f1=fopen("sabado.txt","r"); #Leemos su contenido $za=fgets($f1,5000); #Presentamos el contenido echo $za; #Ahora cerramos el fichero fclose($f1); echo "--------------------------------------------------------------<br>"; ?> <h2>Aqui veremos el contenido (sin etiquetas HTML)de una pagina web</H2> <?php # Escribimos la dirección completa de la página que puede ser # el resultado de unir el valor de la variable $_SERVER['DOCUMENT_ROOT'] # (ruta completa del directorio raíz de servidor) # con el nombre del directorio que la contiene y nombre del fichero # la abrimos en modo solo lectura

Page 211: Manual PHP

$f1=fopen($_SERVER['DOCUMENT_ROOT']."/php/php24.php","r"); # Escribimos un bucle para que vaya leyendo # cada una de las líneas hasta llegar al final del fichero while (!feof($f1)) { $z = fgetss($f1, 1024); echo $z,"<br>"; } #Cerramos el fichero fclose($f1); #Borramos el fichero antes de salir unlink("sabado.txt"); ?>

Ver ejemplo166.php

Otro ejemplo completando el anterior

<?php # Abrimos el fichero en modo lectura $f1=fopen("domingo.txt","r"); # Al anteponer echo a fpassthru # NOS APARECERÁ AL FINAL EL TAMAÑO DEL FICHERO echo fpassthru($f1),"<br>"; /* Abrimos de nuevo el fichero RECUERDA QUE FPASSHRU LO CIERRA AUTOMÁTICAMENTE DESPUÉS DE EJECUTAR LA INSTRUCCIÓN */ $f1=fopen("domingo.txt","r"); # Este bucle nos escribirá cada una de las #líneas del fichero while(!feof($f1)){ $z=fgets($f1,4000); echo $z,"<br>"; } # Situamos el puntero #al comienzo del fichero rewind($f1); # Reescribimos el fichero while(!feof($f1)){ $z=fgets($f1,4000); echo $z,"<br>"; } # Situamos de nuevo el puntero #al comienzo del fichero rewind($f1); # Situamos el puntero #señalando el byte número 15 del fichero fseek($f1,15); # Releemos el fichero #ahora la primera línea estará incompleta #LE FALTARÁN LOS 15 PRIMEROS CARACTERES while(!feof($f1)){ $z=fgets($f1,4000); echo $z,"<br>"; } # volvemos el puntero al comienzo del fichero rewind($f1); #leemos la primera línea $z=fgets($f1,4000); echo $z,"<br>"; # Determinamos LA POSICIÓN ACTUAL DEL PUNTERO echo ftell($f1),"<br>";

Page 212: Manual PHP

# Cerramos el fichero fclose($f1); echo "_________________________________________<br>"; # leemos el fichero y lo presentamos # en diferentes modalidades $pepe=readfile("domingo.txt"); readfile("domingo.txt"); echo $pepe, "<br>"; #leemos el fichero y lo recogemos #en un array $z=file("domingo.txt"); #Al presentar la variable solo #nos aparecerá la palabra array echo $z,"<br>"; # presentamos el contenido del array foreach($z as $linea=>$texto) { echo "Linea: ",$linea," Texto: ",$texto,"<br>"; }; # copiamos el fichero con mensaje de resultado if (!copy("domingo.txt", "otrodomingo.txt")) { print("Error en el proceso de copia<br>\n"); }else{ print "<br>Fichero copiado con exito"; } # renombramos un fichero con mensaje de resultado if (!rename("otrodomingo.txt", "otrolunes.txt")) { print("Error en el proceso de renombrado<br>"); }else{ print "<br>Fichero renombrado con exito"; } unlink("otrolunes.txt"); echo "Ultima modificación a las: ",date("h:i:s A", filemtime ("domingo.txt"))," del día ", date("j-n-Y", filemtime ("domingo.txt")); echo "<br>El tamaño del fichero es: ", filesize("domingo.txt")," bytes<br>"; echo "<br>El fichero es tipo: ", filetype("domingo.txt")," <br>"; echo "<br>Saldrá un 1 si el fichero existe: ",file_exists("domingo.txt"); ?>

Ver ejemplo167.php

Ejemplo de un contador de visitas

<?php /* comprobamos si existe el fichero contador. Si existe leemos las visitas registradas e incrementamos su valor en una unidad Si no existe, registramos 1 como valor de número de visitas*/ if(file_exists("contador.txt")){ /* abrimos el fichero en modo lectura y escritura (r+) con lo que el puntero se colocará al comienzo del fichero */ $f1=fopen("contador.txt","r+"); # leemos el contenido del fichero $visitas=(int)(fgets($f1,10)); # lo aumentamos en una unidad $visitas++; # colocamos el puntero al comienzo del fichero para que # al guardar en nuevo valor sobreescriba el anterior rewind($f1); }else{ /*abrimos el fichero en modo lectura y escritura con (w+)

Page 213: Manual PHP

de modo que se cree automaticamente al no existir*/ $f1=fopen("contador.txt","w+"); #asignamos uno como valor a número de visitas $visitas=1; } /* escribimos el número de visitas en el fichero. En cualquiera de los casos el puntero estará al comienzo del fichero, por tanto cuando existan valores serán sobreescritos */ fwrite($f1,$visitas,10); print("Esta página ha sido visitada ".$visitas." veces"); fclose($f1); ?>

Ver contador

Guardar y leer datos transferidos mediante un formulario

Aunque el modo más habitual de guardar información suele ser los servidores de bases de datos (MySQL, por ejemplo) la utilización de ficheros ofrece interesantes posibilidades de almacenamiento de información.

Este es un ejemplo muy sencillo, en el que mediante un formulario tal como el que aparece en el recuadro puede transferirse y almacenarse la información en un fichero.

<form name="fichero" method="post" action="escribe.php"> <input type="text" name="nombre"> <input type="text" name="apellido"> <input type="edad" name="edad"> <input type="submit" value="enviar"> </form>

Los datos transferidos mediante un formulario como el anterior podrían ser registrados y visualizados mediante un script como este:

<?php /*abrimos el fichero en modo a+ para permitir que se cree en caso de no existir, que permita los modos lectura y escritura y que escriba al final del fichero */ $f1=fopen("escribiente.txt","a+"); # hacemos un bucle para leer los valores transferidos # desde el formulario y recogidos en el array $_POST foreach($_POST as $v){ /* añadimos "\r\n" a cada valor para que se inserte un salto de línea y que cada valor sea recogido en una línea distinta en el fichero Limitamos las entradas a 150 caracteres*/ fwrite($f1,$v."\r\n",150); } /* para comprobar que los nuevos datos han sido agregados y visualizar el contenido íntegro del fichero situamos el puntero interno al comienzo del mismo */ rewind($f1); /* creamos un bucle que vaya leyendo todas las líneas hasta encontrar el final del fichero */ while (!feof($f1)) { /* vamos leyendo el contenido de cada línea del fichero y aunque establecemos en 250 el número de carácteres dado que los saltos del línea aparecerán antes serán ellos los puntos de interrupción de cada lectura*/ $z = fgets($f1,250); #presentamos el resultado de las lecturas echo $z,"<br>";

Page 214: Manual PHP

} # cerramos el fichero fclose($f1); ?>

Transferencia de ficheros

Comprobación de la configuración

Antes de empezar con este tema debemos comprobar cuál es la configuración de nuestro php.ini. Si por alguna circunstancia los valores no coincidieran con los que tenemos aquí debajo, tendríamos que abrir php.ini y modificar aquellas directivas. Cuando publicamos en un hosting no tenemos acceso al fichero de configuración php.ini pero sí podemos conocer su configuración mediante el script info.php.

Recordemos que ese fue nuestro primer script –lo hemos creado y utilizado– para comprobar nuestra instalación y lo hemos guardado con ese nombre en el root de nuestro servidor –/home/rinconas/public_html– así que podremos acceder a él escribiendo como dirección http://localhost/info.php.

La abundante lista que nos muestra info.php contiene las siguientes líneas (las podemos localizar fácilmente porque están ordenadas alfabéticamente), que en nuestro caso –configuración por defecto– tendrán los valores que vemos en la imagen.

Es imprescindible que file_uploads=On (tal como aparece en la imagen) y resulta muy útil también conocer el valor deupload_max_filesize que por defecto –tal como ves en la imagen– es de 2Mb. La primera directiva nos dice que PHP sí permite subir ficheros al servidor y la segunda nos indica el tamaño máximo (en Mbytes) de los ficheros que pueden ser objeto de esa transferencia.

Si te apetece, y como simple experimento, podemos cambiar el límite del tamaño máximo de transferencia poniendo un valor más reducido: upload_max_filesize=500K que nos servirá para hacer alguna prueba sencilla.

Transferencia de ficheros

La transferencia de un fichero requiere dos documentos: un formulario que la inicie y un script que la recoja.

El formulario

Se diferencia del que hemos visto en páginas anteriores en tres aspectos. Dos de ellos se refieren a cambios dentro de la etiqueta<form> y el tercero es un nuevo tipo de input del que aún no hemos hablado. En la etiqueta <form> hemos de incluir –obligatoriamente–method='POST' y ENCTYPE = "multipart/form-data" ya que no soporta ni otro método ni otra forma de codificación.

El cuerpo del formulario ha de contener un nuevo tipo de input que utiliza la siguiente sintaxis: <input type='file' name='nombre'>

Observa que en el formulario hemos insertado una variable oculta (hidden) con el fin de limitar el tamaño máximo e impedir la transferencia de ficheros que excedan ese tamaño.

<html> <body> <form enctype="multipart/form-data" action="ejemplo169.php" method="post"> # con este input "oculto" establecemos el límite máximo

Page 215: Manual PHP

# del tamaño del fichero a transferir. En este ejemplo 1.000.000 bytes <INPUT type="hidden" name="lim_tamano" value="1000000"> <p><b>Archivo a transferir<b><br> <input type="file" name="archivo"></p> <p><input type="submit" name="enviar" value="Aceptar"></p> </form> </body> </html>

La tranferencia

Una vez enviado el formulario, el fichero transferido se guarda en un directorio temporal del servidor –salvo que php.ini especifique una cosa distinta– con un nombre que le es asignado de forma automática, y, además, se recogerán todos los datos relativos al contenido del fichero y a los resultados de la transferencia en la variable predefinida $_FILES.

El la variable $_FILES tiene formato de array bidimensional. El primero de sus índices es el nombre de variable usado para la transferencia (el especificado como name='nombre' en el input type='file').

Los segundos índices –se trata de un array asociativo– tiene como valores: name, type, tmp_name, error y size. En ellos se recogen: el nombre original del fichero transferido, su formato, el nombre con el que ha sido guardado en el directorio temporal, el tipo de error de transferencia y el tamaño del archivo.

El error puede ser CERO o UNO. Si es CERO indica que la transferencia se ha realizado con éxito. En caso contrario, el valor de ese error es UNO.

Este script, al que hemos llamado ejemplo169.php es el que recoge la action de formulario que hemos incluido unas líneas más arriba.

<?php /* Mediante el bucle foreach leemos el array $_FILES. Observa la sintaxis. Escribimos como nombre del array $_['archivo'] con lo cual foreach leerá los elementos del array que tienen 'archivo" como primer índice (coincide con el name que hemos puesto en la etiqueta input=file del formulario) */ foreach ($_FILES['archivo'] as $indice=>$valor){ print $indice."--->".$valor."<br>"; } /*Dependiendo del navegador que estés utilizando puede ocurrir que varían los valores del índice type sean distintos. Cuando se trata de un fichero jpg, con IE devolverá image/pjpeg, mientras que con Mozilla, Firefox, Opera y Netscape devolverá image/jpeg.*/ ?>

ejemplo168.php

Envía un fichero cualquiera –de menos de 2Mb– y verás que el error es 0. Repite el envío, ahora con un fichero que sobrepase ese tamaño, y comprobarás que el error toma valor 1 dado que la directiva upload_max_filesize=2M del fichero php.ini habrá bloqueado la transferencia.

Puedes modificar la configuración de php.ini y modificar esos límites de transferencia. Al hacerlo incrementarás o reducirás el límite máximo de transferencia.

Copia del fichero

Tal como hemos visto, el fichero transferido aún no está en el servidor. Por el momento se encuentra en un directorio temporal y será preciso hacer una copia en nuestro espacio de servidor. Para este proceso puede utilizarse una función que ya hemos visto en páginas anteriores:

Page 216: Manual PHP

copy(fichero1, fichero2)

donde fichero1 sería el fichero temporal y fichero2 el del nuevo fichero. El primero de los nombres es el valor contenido en:$_FILES['nm']['tmp_name'] donde nm es el valor incluido como name en el formulario usado para la transferencia y tmp_name es unapalabra reservada que debe escribirse exactamente con esa sintaxis.

El valor fichero2 podría ser un nombre cualquiera asignado en el propio script –podemos verlo en el ejemplo– o el nombre original del fichero transferido. En este caso habría que recogerlo del elemento del array anterior cuyo segundo índice es name.

En la cadena fichero2 podría incluirse –recuerda que debes ponerlo entre comillas– un path señalando el directorio o subdirectorio donde queremos que guarde la copia. De no incluirlo, el fichero se copiaría en el directorio desde el que se está ejecutando el script.

Sintaxis alternativa

La opción anterior tiene una alternativa, igual de eficiente y mucho más segura. Se trata de:

move_uploaded_file(fich1, fich2)

que tiene la misma utilidad que copy y añade algunas ventajas tales como:

� – Comprobar que el fichero ha sido transferido mediante el método POST e impedir que se copie en el servidor en caso de no cumplirse esa condición.

� – Si la opción safe mode=on (configuración de php.ini en modo seguro) –por defecto está Off– comprueba, además, que la transferencia del fichero ha sido realizada por el mismo usuario que hace la petición de ejecución del script, evitando que alguien,maliciosamente, pueda mover ficheros desde el directorio temporal hasta el espacio de servidor.

� – A diferencia de la anterior esta función mueve el fichero y por lo tanto no queda copia del mismo en el fichero temporal

¡Cuidado! Al usar esta función bajo Windows conviene indicar en el parámetro fichero2 la ruta absoluta completa junto con el nombre del fichero ya que –de no hacerlo así– en algunas ocasiones la imagen no será transferida al directorio desde el que se ejecuta el script.

Mejorando las trasnferencias

Cuando está habilitada la opción de transferencias de ficheros es conveniente –en previsión de sorpresas desagradables– tomar algunas cautelas. Una de ellas sería limitar la posibilidad de transferencia a determinados tipos de archivos –imágenes, por ejemplo– impidiendo con ello que pudieran transferirse al servidor ficheros de riesgo, tales como: ejecutables, virus, etcétera.

Cuando se establece este tipo de limitaciones, PHP comprueba los contenidos de los ficheros sin tomar en consideración la extensión de los mismos. Con ello se evita el riesgo de que puedan esconderse –cambiando la extensión– ficheros distintos de los permitidos.

Aquí tienes un ejemplo de script que impide la transferencia de ficheros con extensión distinta a .jpg o .gif.

<?php /* filtramos el tipo de archivos recibidos de forma que solo se permitan imagenes en formato jpg ó gif. Si el fichero transferido tuviera formato distinto, la función exit() acabaría la ejecución del script */ if(!($_FILES['archivo']['type']=="image/pjpeg" OR $_FILES['archivo']['type']=="image/jpeg" OR $_FILES['archivo']['type']=="image/gif")){ print "El formato ".$FILES['archivo']['type'].

Page 217: Manual PHP

" no está permitido"; exit(); }else{ # anidamos este segundo condicional # para guardar en una variable # la extensión real del fichero # mas adelante la utilizaremos if ($_FILES['archivo']['type']=="image/pjpeg" OR $_FILES['archivo']['type']=="image/jpeg" ){ $extension=".jpg"; }else{ $extension=".gif"; } } /* filtremos ahora el tamaño de modo que no supere el máximo establecido en el hidden del formulario (lógicamente ese valor no puede superar el valor máximo de la configuración de php, pero si puede ser menor) y también evitaremos archivos sin contenido, es decir con tamaño CERO */ if($_FILES['archivo']['size']>$_POST['lim_tamano'] OR $_FILES['archivo']['size']==0){ print "El tamaño ".$FILES['archivo']['size']." excede el límite"; exit(); } # asignemos un nombre a la imagen transferida # de modo que se guarde en el servidor # con un nombre distinto, asignado por nosotros # con ello, podemos evitar duplicidades de nombres # ya que si existiera un fichero con el mismo nombre # que el enviado por el cliente, se sobreescribiría $nuevo_nombre="foto_abuelita"; # añadámosle la extensión real de fichero que teníamos # recogida en la variable nuevo_nombre $nuevo_nombre .=$extension; # aceptemos la transferencia siempre que el archivo tenga nombre if ($_FILES['archivo']['tmp_name'] != "none" ){ /* con la función copy pasaremos el archivo que está en el directorio temporal al subdirectorio que contiene el script que estamos ejecutando. Podríamos incluir un path y copiarlo a otro directorio */ if (copy($_FILES['archivo']['tmp_name'], $nuevo_nombre)) { echo "<h2>Se ha transferido el archivo</h2>"; } }else{ echo "<h2>No ha podido transferirse el fichero</h2>"; } ?>

Funciones de compresión

Herramientas de compresión

Existen varias herramientas para compresión de ficheros. Las más populares son las funciones de la biblioteca bzip2 de Julian Seward que generan ficheros comprimidos que se reconocen por su extensión (bz2) y la función de zlib de Jean-loup Gailly y Mark Adler para leer y grabar archivos comprimidos con extensión gz. En esta página veremos el uso

Page 218: Manual PHP

de la segunda de las opciones. Empezaremos comprobando en info.php que la opción está activada. Deberemos ver algo como esto:

En la versión de PHP que estamos utilizando esta opción se activa por defecto y no es necesario modificar ningún parámetro enphp.ini.

Funciones zlib para compresión de ficheros

Algunas de esas funciones son estas:

$f=gzopen(fichero,modo, ruta)

Abre el fichero identificado por el parámetro fichero y lo hace en el modo especificado en el parámetro modo (r o w según se trate de modo lectura o escritura). Cuando se trata del modo de escritura el parámetro w debe ir seguido de un número comprendido entre cero ynueve que especifica el grado de compresión pretendido.

El parámetro ruta es opcional y puede contener un valor lógico (cero ó uno). Cuando el valor de este parámetro es 1 permite incluir en el parámetro fichero la ruta del directorio o subdirectorio que alberga el fichero que tratamos de abrir.

Si se incluye un path sin especificar el valor 1 en el parámetro ruta aparecerá un error.

gzclose($f)

Cierra el fichero asociado al identificador de recurso $f. Esta función devuelve TRUE en caso de éxito o FALSE si se produjera un error.

gzeof($f)

Esta función devuelve 1 (TRUE) en el caso de que el puntero apunte al final del fichero abierto e identificado mediante $f. También devuelve TRUE en caso de error. Si el fichero estuviera abierto y el puntero apunta a una posición distinta del final del fichero devolverá FALSE.

gzseek($f,desplaza)

Desplaza –dentro del fichero identificado por $f– el puntero –a partir de su posición actual– la cantidad de bytes indicados en el parámetro desplaza.

gztell($f)

Devuelve la posición actual del puntero.

Page 219: Manual PHP

gzrewind($f)

Coloca el puntero al comienzo del fichero

gzread($f, longitud)

Devuelve una cadena -después de descomprimida- de longitud igual a la indicada en el parámetro longitud. La lectura comienza en laposición actual del puntero y acaba cuando la longitud de la cadena leída y descomprimida sea igual al valor del parámetro longitud o cuando se haya alcanzado el final del fichero.

gzpassthru ($f)

Esta función escribe en la salida (no necesita la función echo) el contenido del fichero desde la posición actual del puntero hasta el final del fichero. Como es lógico, si estuviera precedida de gzrewind escribiría el fichero completo.

¡Cuidado! La función gzpassthru cierra automáticamente el fichero después de escribir su contenido. Si pones gzclose después de esta función te dará error y si quieres seguir utilizando el fichero tendrás que volver a abrirlo con la función gzopen. gzwrite($f, cadena, long)

Esta función escribe en el fichero comprimido que se identifica por $f la cadena contenida en el parámetro cadena. Opcionalmente puede llevar el tercer parámetro (longitud) en cuyo caso solo escribirá los primeros longitud bytes de la cadena. Si el parámetro longitudexiste y es mayor que la longitud de la cadena, insertará la cadena completa.

gzputs($f, cadena, longitud)

Esta función es idéntica a gzwrite.

readgzfile($fichero,path)

Esta función abre de forma automática el fichero indicado como parámetro fichero, además lo lee y lo escribe de forma automáticasin necesidad de usar echo ni ninguna otra función de salida. Si el fichero no está en el mismo directorio que el script -además de incluir la ruta en la cadena fichero- es necesario añadir el segundo parámetro -path- con valor 1.

Ejemplo de compresión y lectura de un fichero

En este ejemplo trataremos de utilizar las funciones de compresión comentadas al margen. Si observas las formas de apertura de los ficheros verás que son similares a las utilizadas para la gestión de ficheros. Los modos de apertura para escritura son: "w0" a "w9" siendo los valores de cero a nueve los indicadores de los niveles de compresión. Para lectura debe usarse el modo "r" sin indicar ningún nivel de compresión.

<?php # asignamos un nombre al fichero con extensión "gz" $fichero ='prueba.gz'; # abrimos el fichero en modo escritura (w) # con el nivel máximo de compresión (9) $f=gzopen($fichero,"w9",0); $cadena="Este es el primer bloque de texto que hemos introducido en el fichero comprimido. "; $cadena .="Añadimos este segundo bloque"; echo "<i>Esta es la cadena inicial:</i> ".$cadena."<br>";

Page 220: Manual PHP

# escribimos (comprimida) la cadena en el fichero gzwrite($f,$cadena); # cerramos el fichero gzclose($f); #abrimos el fichero en modo lectura $f=gzopen($fichero,"r"); echo "<i>Estos son los tres primeros caracteres de la cadena:</i> "; # escribimos los tres primeros caracteres, el puntero (por defecto) # apunta al comienzo de la cadena echo gzread($f, 3)."<br>"; # desplazamos el puntero hasta el carácter nº 8 gzseek($f,8); echo "<i>Estos son los seis caracteres siguientes al octavo:</i> "; # escribimos seis caracteres a partir del octavo echo gzread($f, 6)."<br>"; echo "<i>Ahora el puntero está en:</i> "; # buscamos la posición actual de puntero echo gztell($f)."<br>"; # movemos el puntero hasta el comienzo del fichero gzrewind($f); echo "<i>Estos son los diez primeros caracteres de la cadena:</i> "; # escribimos los diez primeros caracteres del fichero echo gzread($f, 10)."<br>"; # volvemos el puntero al comienzo del fichero gzrewind($f); echo "<i>Escribimos el fichero completo:</i> "; # con gzpasthru escribimos el fichero completo # el puntero está al principio porque allí lo ha situado gzrewind # no necesitamos utilizar "echo" ni "print" ya que gzpassthru # escribe directamente el contenido del fichero gzpassthru($f); # tenemos que volver a abrir el fichero ya que gzpassthru # se encargó de cerrarlo después de leerlo $f=gzopen($fichero,"r"); echo "<br><i>Aquí estará todo el fichero:</i> "; gzpassthru ($f); # la función readgzfile abre el fichero, imprime su contenido y lo cierra echo "<br><i>Aqui se imprime la cadena completa usando readgzfile</i>: <br>"; readgzfile($fichero); /* con gzfile también se abre el fichero, pero ahora el contenido no se presenta directamente. Es recogido en un array. Para visualizarlo debemos imprimir el primer elemento del array. */ $z=gzfile($fichero); echo "<br><i>Este es el primer elemento (0) del array generado por gzfile</i>: ".$z[0]; # gzfile cierra el fichero. # No podemos poner gzclose porque nos daría error ?>

ejemplo174.php

Utilizando un directorio distinto

El ejemplo anterior está desarrollado para el supuesto que el script y el fichero comprimido estén en el mismo directorio. Si quieres utilizar estas funciones utilizando ficheros alojados en un directorio distinto, solo tendrás que recordar que algunas funciones deben incluir el parámetro complementario 1.- Estos son las modificaciones que deberías efectuar:

� – La variable que recoge el nombre del fichero debe incluir el path, por ejemplo: $fichero ='/subdirectorio/prueba.gz'

Page 221: Manual PHP

� – La función gzopen debe incluir el tercer parámetro (path) con valor 1, por ejemplo: $f=gzopen($fichero,"r",1);

� – También las funciones gzfile y readgzfile -que abren automáticamente el fichero- deberán incluir ese valor 1 como parámetro añadido. Por ejemplo: readgzfile($fichero,1) ó $z=gzfile($fichero,1)

Elección del grado óptimo de compresión

Puede parecer que la condición óptima de compresión sería elegir el nivel 9 y eso es cierto si tomamos únicamente en consideración el tamaño final del fichero comprimido. Sin embargo no existe una relación lineal entre reducción de tamaño/nivel de compresión. Sin que pueda considerarse ninguna referencia exacta –la compresión alcanzable depende del contenido del fichero y en consecuencia no puede establecerse una relación funcional– puede comprobarse experimentalmente que -aparentemente- a partir del grado 2 la reducción de tamaño del fichero es mínima y que cuando se aumenta el grado de compresión a niveles máximos (tratándose de ficheros de un cierto tamaño) el tiempo de ejecución aumenta sustancialmente como consecuencia de la reiteración de la ejecución de los algoritmos de compresión.

Comprimiendo cadenas

Las funciones anteriores permiten la creación, lectura y modificación de ficheros comprimidos. Sin embargo, existen otras funciones PHP que permiten comprimir cadenas. Aquí tienes algunas de ellas.

gzcompress(cadena, nivel)

Esta función devuelve una cadena comprimida a partir de una original especificada en el parámetro cadena. El nivel de compresión (valores entre 0 y 9) se especifica en el parámetro nivel. Las cadenas resultantes de esta función pueden descomprimirse aplicando la función gzuncompress que referimos más abajo.

gzdeflate(cadena, nivel)

Se comporta de forma idéntica a la función anterior. La única salvedad parece ser que utiliza un algoritmo de compresión distinto. Las cadenas resultantes de esta función también pueden descomprimirse aplicando la función gzdeflate.

gzencode(cadena, nivel, opciones)

Esta función devuelve la cadena cadena comprimida con el nivel especificado en nivel y permite dos opciones de compresión:FORCE_GZIP ó FORCE_DEFLATE que se pueden especificarse como tercer parámetro (opciones) sin encerrar entre comillas.

El valor por defecto (cuando no se especifica el parámetro opción) es FORCE_GZIP.

Descomprimiendo cadenas

gzuncompress(cadena)

Con esta función se obtiene la cadena original a partir de la cadena comprimida indicada en el parámetro cadena siempre que esta hubiera sido comprimida usando la función gzcompress.

gzinflate(cadena)

Funciona igual que la anterior. La única diferencia es que esta descomprime las cadenas que han sido comprimidas con gzdeflate.

Ejemplo de compresión y descompresión de cadenas

En este ejemplo utilizamos las tres funciones de compresión de cadenas así como las opciones de descompresión y lectura de cada una de ellas.

Page 222: Manual PHP

<?php # creamos una cadena de ejemplo $cadena="Esta es la cadena a comprimir. Intentaremos que sea larga porque parece que si la hacemos muy corta en vez de reducirse su tamaño parece que aumenta. Y como sigue siendo enormemente grande la cadena comprimida intentaremos hacerla aun mayor a ver que pasa "; # comprimimos con la función gzcompress $c=gzcompress($cadena,9); echo "<br>".$c; # descomprimimos con la función gzcompress $dc=gzuncompress($c); echo "<br>".$dc."<br>"; # ahora utilizamos la función gzencode $c1=gzencode($cadena,9,FORCE_GZIP); echo "<br>".$c1."<br>"; /* el resultado lo guardamos en un fichero con extensión gz pero abierto en modo "normal", es decir escribiendo dentro del fichero la cadena "tal cual" fue devuelta por gzencode*/ $f=fopen("pepe.gz","w"); fwrite($f,$c1); fclose($f); # abrimos el fichero anterior utilizando las funciones # de lectura de fichero comprimidos $f=gzopen("pepe.gz","r"); readgzfile("pepe.gz"); gzclose($f); # borramos el fichero una vez leído unlink("pepe.gz"); # otra opción de compresión de cadenas utilizando la función # gzdeflate $c2= gzdeflate($cadena,9); echo "<br><BR>".$c2; # con la función gzinflate podemos descomprimir la cadena # comprimida generada por gzdeflate $dc2=gzinflate($c2); echo "<br>".$dc2; ?>

ejemplo175.php

Funciones para buferización de salidas

ob_start()

Esta función activa la buferización de las salidas generadas por el script de PHP a partir de su activación. Dicho de otra forma, impide que las salidas generadas por el script se envíen al cliente impidiendo que sean visualizadas en el navegador. A partir del momento de activar esa buferización, todas las salidas generadas se almacenan en una variable específica llamada: ob_get_contents()

ob_end_clean()

Esta función desactiva la buferización iniciada por ob_start y borra los contenidos de la variable ob_get_contents()

ob_clean()

Page 223: Manual PHP

Esta función vacía el buffer de salida pero sin desactivar la bufferización. Las salidas posteriores a esta función seguirían siendo recogidas en el buffer.

Cabeceras para transferir información comprimida

Cuando un servidor recibe una petición de una página web el navegador del cliente siempre envía información sobre su disposición a aceptar diferentes tipos de contenidos. Una de las informaciones que suelen recibirse con la petición del navegador se refiere a su capacidad para aceptar contenidos codificados y esto suelen hacerlo mediante el envio de una cabecera que diría algo similar a esto: Accept-Encoding:gzip,deflate o Accept-Encoding: gzip.

Esta posibilidad es una característica común a todas las versiones modernas de los navegadores (es posible que algunas versiones antiguas no acepten esta codificación) y bastará con que se incluya en la respuesta (el documento transferido por el servidor al cliente) la cabecera: Header('Content-Encoding: gzip') para que el navegador sepa que la información llegará codificada y que debe activar -de forma automática- sus mecanismos de traducción de ese tipo de contenidos.

Algunas limitaciones

En todos estos ejemplos hemos dado por supuesto que los navegadores de los clientes aceptan la codificación gzip, pero es evidente que si eso no ocurriera la página se visualizaría erróneamente.

Economizando espacio en el servidor

Las opciones de compresión pueden permitirnos un cierto ahorro de espacio de servidor. Las páginas HTML podrían almacenarse comprimidas y ser llamadas a través de un script de descompresión que permita visualizarlas.

En este ejemplo se efectúa la compresión de una página web (una de las páginas de estos materiales guardada en formato HTML) cuyo tamaño original es de 57.105 bytes. El fichero comprimido resultante ocupa 12.371 bytes. Como verás, el fichero se reduce a poco más del 20% del original.

<? # Creamos una variable "vacia" $cadena=""; # Abrimos el fichero en modo lectura (r) $f1=fopen("prueba.html","r"); /* hacemos un bucle para leer el fichero hasta encontrar el final (feof) y vamos recogiendo el contenido en la variable */ while (!feof($f1)) { $cadena .= fgets($f1, 1024); } /*comprimimos la cadena con gzencode con lo cual la propia función añade los "encabezados" de formato gzip*/ $c1=gzencode($cadena,3,FORCE_GZIP); /* abrimos un nuevo fichero modo escritura (w) con "fopen", es decir como un fichero normal con extensión GZ */ $f=fopen("prueba.html.gz","w"); /* escribimos la cadena "tal cual" en este fichero */ fwrite($f,$c1); # cerramos el fichero comprimido fclose($f); echo "La compresión ha terminado"; ?>

ejemplo176.php

Page 224: Manual PHP

El fichero comprimido mediante el script anterior no puede ser visualizado directamente. Requiere ser descomprimido antes de ser enviado al navegador. Y eso podría hacerse mediante un script como este:

<?php #abrimos el fichero comprimido con "gzopen" $f=gzopen("prueba.html.gz","r"); /* leemos el contenido completo en forma transparente ya que readgzfile descomprime la salida*/ readgzfile("prueba.html.gz"); # cerramos el fichero gzclose($f); ?>

Visualizar fichero comprimido

Economizando tiempo de transferencia

No solo se puede economizar espacio en el servidor. También es posible enviar comprimidas -desde el servidor hasta el cliente- las páginas web. En ese caso, será el propio navegador el que interprete la información comprimida y la presente de una manera transparente. Lo que habremos ahorrado habrá sido tiempo de transferencia pero, igual que ocurría en el comentario anterior, esa reducción del volumen de información a transferir afecta únicamente al contenido de la página y no a otros elementos que puede incluir, tales como imágenes, etcétera.

Este es un ejemplo de un script que comprime una página web y la envía comprimida al cliente.

<?php /* activamos la bufferización de la salida para que no se presenten los resultados del script directamente en la página ¡¡Cuidado con no dejar líneas en blanco delante del script ya que vamos a insertar luego Headers!! */ ob_start(); # abrimos y leemos el fichero html $f1=fopen("prueba.html","r"); fpassthru($f1); # recogemos el contenido del buffer en la variable $cadena $cadena = ob_get_contents(); # comprimimos la cadea con gzencode # para que incluya los encabezados "gzip" $cd=gzencode($cadena,3,FORCE_GZIP); # desactivamos la "buferización" # y borramos el contenido del buffer ob_end_clean(); # insertamos la cabeceras # indicando el tipo de contenido y el tamaño Header('Content-Encoding: gzip'); Header('Content-Length: ' . strlen($cd));; # presentamos el contenido (cadena comprimida) que será # "traducido" automáticamente por el navegador echo $cd; ?>

Ejecutar script

El ejemplo anterior comprimía el contenido del fichero antes de enviarlo. En este que incluimos a continuación partimos del supuesto de que la página ya está comprimida en el servidor. Por tanto, tendremos que leer el fichero comprimido y enviarlo, de igual forma, al cliente.

Page 225: Manual PHP

<?php ob_start(); /* En este caso abrimos el fichero con "gzopen" ya que se trata de un fichero comprimido # todo lo demás es idéntico al ejemplo anterior*/ $f1=gzopen("prueba.html.gz","r"); gzpassthru($f1); $cadena = ob_get_contents(); $cd=gzencode($cadena,3,FORCE_GZIP); ob_end_clean(); Header('Content-Encoding: gzip'); Header('Content-Length: ' . strlen($cd)); echo $cd; ?>

Ejecutar script

Funciones FTP

Requisitos del sistema

El uso de estas funciones requiere que PHP tenga activada la opción FTP (enabled), que en nuestro caso está activada en la configuración por defecto, tal como puedes ver a través de tu info.php, que tendrá un apartado idéntico al que observamos en la siguiente imagen.

Esto en cuanto a PHP. Pero además de esta configuración será imprescindible disponer de un servidor FTP accesible y activo. En este tipo de transferencias intervienen dos servidores: el servidor HTTP (nuestro Apache) y el servidor FTP, cuyo procedimiento de instalación y configuración hemos descrito en el apartado Servidor de FTP. Antes de poder utilizar las funciones que aquí describimos deberás tener instalado y activo el servidor FTP que allí se describe.

Nos conviene tener muy presente que esta versión de PHP parece no admitir localhost como nombre de servidor (en la versión 4 esto no ocurría) y que por esa razón hemos de referirnos al servidor FTP mediante su dirección IP (127.0.0.1, valor por defecto para nuestro servidor de pruebas en modo local) y que hemos creado diferentes usuarios (con privilegios distintos) entre ellos admin.

Transferencias FTP

En la página anterior hemos hablado de la manera de transferir información entre el ordenador de un usuario y un servidor web. Aquí trataremos algo –similar a primera vista– un poco distinto. Es el caso de las transferencias –en los dos sentidos– entre servidores (un servidor HTTP y un servidor FTP).

En la configuración descrita en la instalación del servidor FTP hemos establecido que ambos servidores tengan sus root en el mismo equipo, pero esa no es la única opción posible. Es totalmente factible que uno de los servidores esté alojado en un equipo situado físicamente en Londres y el otro lo esté en Sydney, por poner un ejemplo de lugares distantes. Imaginemos que todo esto es cierto.

Los dos primeros pasos para poder utilizar las funciones FTP han de ser: abrir la conexión y pasar el login. El último sería cerrar la conexión.

Abrir la conexión

Page 226: Manual PHP

$identificador=ftp_connect (host,puerto)

Esta función –en la que host es una cadena con el nombre del servidor FTP (no te olvides de ponerlo entre comillas) y puerto es el número del puerto a través del cual se efectúa la conexión FTP– abre una conexión con el servidor FTP.

Si se omite puerto se asigna por defecto el valor 21 que es el habitual para este tipo de servidores.

La variable $identificador recogerá un identificador de conexión que será utilizado por las demás funciones.

«Loguearse»

Utilizaremos este término del argot informático –¿horrible, verdad?– para referirnos al hecho de que el usuario se acredite comoautorizado en el servidor FTP.

ftp_login($identificador,usuario,contraseña)

Una vez abierta la conexión es preciso comenzar la sesión utilizando la función ftp_login con los siguientes parámetros:

� • $identificador, que es la variable en la que se recogía el resultado de ftp_connect.

� • usuario, que es el nombre de usuario.

� • contraseña, que es la contraseña de acceso del usuario.

Esta función devuelve un valor booleano que será 1 en el caso en que se inicie la sesión correctamente o NUL si no lo hace.

Cerrar la conexión

Mediante la función:

ftp_quit($x)

se cierra la conexión abierta con el identificador indicado en la variable $identificador.

<?php # conexión con el servidor FTP if($x=@ftp_connect ("127.0.0.1",21)){ echo "Conexión FTP activada<br>"; }else{ echo "No se activo lo conexión FTP<br>"; } # registro de usuario if(@ftp_login($x,"super","superi")){ echo "El login y la password han sido aceptados"; }else{ echo "Error en login o password"; } #desconexión ftp_quit($x); ?>

ejemplo180.php

Gestión de directorios en el servidor FTP

Una vez logueados y con la conexión activa, ya podremos utilizar funciones FTP tales como:

Page 227: Manual PHP

ftp_cdup($identificador)

Nos sitúa en el directorio raíz del servidor FTP.

ftp_pwd($identificador)

Devuelve una cadena con el nombre del directorio actual.

ftp_chdir($identificador, otro_directorio)

Cambia el acceso del directorio actual al especificado por la cadena otro_directorio, en caso de que exista.

ftp_pwd($identificador)

Devuelve una cadena que contiene el nombre del directorio actual.

ftp_mkdir($identificador, nuevo_directorio)

Crea un subdirectorio –en el directorio actual– cuyo nombre es el nombre indicado en la cadena nuevo_directorio.

ftp_rmdir($identificador, nombre_directorio)

Borra el directorio especificado en la cadena nombre_directorio. Para que un directorio pueda ser borrado se requiere que esté vacíoy que sea un subdirectorio del directorio actual.

Información sobre los contenidos de los directorios del servidor FTP

ftp_nlist($identificador, nombre_directorio)

Devuelve una array escalar con los nombres de los ficheros y subdirectorios contenidos en el directorio que se indica ennombre_directorio. Si se trata del directorio actual, el parámetro nombre_directorio puede especificarse como una cadena vacía ("").

Si la información se refiere a un subdirectorio del actual bastará con poner su nombre como valor del parámetro nombre_directorio. En cualquier otro caso nombre_directorio contendrá la ruta completa.

ftp_rawlist($identificador, nombre_directorio)

Igual que la función anterior, ftp_rawlist también devuelve un array escalar, pero en este caso con información ampliada. Este array detalla, además del nombre del fichero, el tamaño, el tipo, la fecha de la última modificación y los permisos de lectura y/o escritura.

<?php if($x=@ftp_connect ("127.0.0.1",21)){ echo "Conexión FTP activada<br>"; }else{ echo "No se activo lo conexión FTP"; } if(@ftp_login($x,"webmaster","webmaster")){ echo "El login y la password han sido aceptados<BR><BR>"; }else{ echo "Error en login o password";

Page 228: Manual PHP

} $lista=ftp_nlist($x,"/php/images"); foreach($lista as $c=>$v){ print "Indice: ".$c." Valor: ".$v."<br>"; } print "<H1>Lista completa</H1>"; $listacompleta=ftp_rawlist($x,"/php/images"); foreach($listacompleta as $c=>$v){ print "Indice: ".$c." Valor:".$v."</br>"; } ftp_quit($x); ?>

ejemplo181.php

El resultado de la ejecución del script anterior podría producir una salida similar a esta:

Tal como puedes ver en la imagen, la cadena devuelta por la función ftp_rawlist tiene dos resultados distintos. La primera de las cadenas comienza por – lo cual indica que se trata de un archivo y documento. En el segundo de los casos, se primer carácter es d e indica que se trata de un directorio.

Los nueve caracteres siguientes especifican los permisos de acceso a los ficheros y/o directorios. Se subdividen en tres bloques de igual tamaño que corresponden a los tres niveles de usuarios habituales en sistemas Unix/Linux (propietario, grupo y resto de usuarios). Para nuestros propósitos bastará con que consideremos los privilegios del primer bloque, es decir los del propietario.

El primero carácter de cada bloque sólo puede ser r ó –. Si se trata de un fichero y está marcado con r indica que se permite el acceso a él en modo lectura y si se trata de un directorio indica que está permitida la visualización de su contenido.

El segundo de los caracteres (puede ser w ó –) indica, si se trata de un fichero, que está permitida la modificación del fichero. Cuando se trata de un directorio significa que se pueden añadir o suprimir ficheros.

El tercero de los caracteres indicaría (x ó –) que el fichero -si se trata de un ejecutable- tiene permisos para ser ejecutado. Cuando se trata de un directorio, indica que pueden conocerse los atributos de los ficheros que contiene y que está permitido el acceso a él y a sus subdirectorios.

El signo – significa la negación del atributo en todas las opciones.

El siguiente carácter, el número 1, está asociado con sistemas Linux/Unix e indicaría el número de vínculos duros contra el archivo, que es otra cosa que una forma de asignar nombres distintos a un mismo fichero.

Page 229: Manual PHP

Los dos grupos siguientes -parece que no demasiado relevantes para nuestros propósitos- son los nombres del usuario y grupo al que pertenece. A continuación aparece el tamaño del archivo (cero si se trata de un directorio), la fecha y hora de su creación y el nombre del archivo o directorio.

Transferencia de ficheros

Las transferencias de ficheros pueden realizarse en ambos sentidos.

Desde el servidor FTP hasta el servidor HTTP

Mediante la función:

ftp_get( $identificador, nombre_en_servidor_web, nombre_en_servidor_ftp, modo)

se transfiere un fichero desde un servidor FTP hasta un directorio del servidor HTTP en el que se está ejecutando PHP. La cadenanombre_en_servidor_web contiene el nombre con el que el fichero será copiado en el directorio actual del servidor web y la cadenanombre_en_servidor_ftp contiene el nombre (incluyendo el path) que tiene (en el servidor FTP) el fichero que debe ser trasferido. El parámetromodo puede contener uno de estos valores: FTP_ASCII o FTP_BINARY

Desde el servidor HTTP hasta el servidor FTP

Para realizar transferencias en sentido opuesto al anterior se utiliza la siguiente sintaxis:

ftp_put($identificador, nombre_en_servidor_ftp, nombre_en_servidor_web, modo)

Se comporta de forma idéntica a la función anterior. La cadena nombre_en_servidor_ftp sigue siendo el nombre y el path del servidor FTP (donde vamos a copiar el fichero) y nombre_en_servidor_web contiene el nombre del fichero en el servidor web (origen de la transferencia).

Modificación de ficheros en el servidor FTP

ftp_rename($identificador,nombre_actual,nuevo_nombre)

Cambia el nombre del fichero nombre_actual por el indicado en la cadena nuevo_nombre.

ftp_delete($identificador,nombre_fichero)

Elimina -en el servidor FTP- el fichero indicado en la cadena nombre_fichero.

Información sobre ficheros del en el servidor FTP

ftp_size($identificador,nombre_fichero)

Devuelve el tamaño (en bytes) del fichero que se indica en la cadena nombre_fichero.

ftp_mdtm($identificador,nombre_fichero)

Esta función devuelve la fecha de la última modificación del fichero indicado en la cadena nombre_fichero. Esta fecha se indica entiempo Unix.

Un ejemplo de uso de las funciones FTP

Page 230: Manual PHP

<?php # Conexión con el el servidor ftp utilizando su dirección IP if(!$x=@ftp_connect ("127.0.0.1",21)){ echo "No se activo lo conexión FTP"; exit(); } # Identificación de usuario webmaster (manejaremos ficheros en Apache) if(!@ftp_login($x,"webmaster","webmaster")){ echo "Error en login o password"; exit(); } /* comprobamos el nombre del directorio actual del servidor FTP que será el root correspondiente al usuario registrado (aparecrá /) */ echo "El directorio actual es: ",ftp_pwd($x),"<br>"; /* intentamos cambiar a un subdirectorio indicando la ruta absoluta partiendo del directorio root del usuario actual. En caso de error (ruta incorrecta o falta de permisos de accesos nos daría un mensaje de error. Si el cambio tiene éxito nos indicaría el nombre del nuevo directorio */ if(!@ftp_chdir($x,"/miphp/pdf")){ print "No tienes permisos de acceso a este directorio<br>"; print "o la ruta es incorrecta.¡Comprueba los datos!<br>"; }else{ echo "Hemos cambiado al directorio: ",ftp_pwd($x),"<br>"; } # comprobamos el nombre del sistema operativo del servidor de FTP echo "El S.O: del servidor FTP es: ",ftp_systype ($x),"<br>"; /* obtenemos una matriz conteniendo la lista de ficheros y directorios del subdirectorio "miphp/fuentes" del del directorio actual*/ $lista=ftp_nlist($x,"/miphp/fuentes"); # escribimos la lista de ficheros contenidos en ese directorio echo "Lista de ficheros del subdirectorio miphp/fuentes<br>"; foreach ($lista as $valor){ echo $valor,"<br>"; } # obtenemos una lista completa de los contenidos de ese subdirectorio $lista=ftp_rawlist($x,"/miphp/fuentes"); # ordenamos el array que contiene la lista anterior sort($lista); echo "Contenidos del subdirectorio miphp/fuentes<br>"; /* extrae los elementos del array eliminando los espacios repetidos mediante la funcion preg_replace en la que \s+ indica uno o más espacios que serán sustituidos por uno solo (' ') */ foreach($lista as $v){ $v=preg_replace('/\s+/', ' ', $v); # imprimimos la cadena completa print "<br><br><br>".$v."<br>"; # convertimos la cadena en un array # utilizando los espacios como separadores $extrae=explode(" ",$v); # leemos los elementos del array y comentamos sus valores foreach($extrae as $indice=>$cont){ switch($indice){ case 0: print "El elemento de indice".$indice." es: ".$cont."<br>"; if (substr($cont,0,1)=="d"){ print "Es un directorio<br>"; }elseif(substr($cont,0,1)=="-"){ print "Es un fichero<br>"; } if (substr($cont,1,1)=="r"){ print "Tiene permisos de LECTURA<br>";

Page 231: Manual PHP

}elseif(substr($cont,1,1)=="-"){ print "No tiene permisos de LECTURA<br>"; } if (substr($cont,2,1)=="w"){ print "Tiene permisos de ESCRITURA<br>"; }elseif(substr($cont,2,1)=="-"){ print "No tiene permisos de ESCRITURA<br>"; } break; case 4: print "El tamaño de este fichero es: ".$cont." bytes<br>"; break; case 8: print "El nombre del fichero o directorio es: ".$cont."<br>"; break; } } } # regresamos al directorio miphp ftp_chdir($x,"/miphp/"); /* creamos un subdirectorio (del directorio actual que es miphp) con nombre experimento anteponiendo @# para evitar mensajes de error en caso de que ya existiera */ @ftp_mkdir($x,"experimento"); /* copiamos el fichero enol.jpg desde el directorio que se indica en el tercer parámetro (miphp) al directorio del servidor FTP que se indica en el segundo parámetro. Le ponemos por nombre lago_enol.jpg */ ftp_put($x, "../miphp/experimento/lago_enol.jpg", "../miphp/enol.jpg",FTP_BINARY); # obtenemos el tamaño del fichero transferido echo "El tamaño de fichero tranferidos es: ", ftp_size($x,"../miphp/experimento/lago_enol.jpg")," bytes<br>"; /* escribimos la fecha de la última modificación del fichero transferido que coincidirá con la fecha y hora en la que se realizó la transferencia. Convertimos a formato de fecha convencional el tiempo UNIX que devuelve la función ftp_mdtm */ print "La fecha de modificacion del fichero es:"; print date("d-m-Y H:i:s",ftp_mdtm($x,"./experimento/lago_enol.jpg")); # cambiamos el nombre del fichero lago_enol.jpg por lago_covadonga.jpg # en el servidor FTP @ftp_rename($x,"./experimento/lago_enol.jpg", "./experimento/lago_covadonga.jpg"); /* creamos un enlace de descarga directa del fichero haciendo una llamada mediante el protocolo ftp:// utilizando la sintaxis: ftp://usuario:contraseña@nombre del servidor seguidos de la ruta (en el servidor FTP) y el nombre del fichero */ print "<BR><a href='ftp://webmaster:webmaster@localhost"; print "/miphp/experimento/lago_covadonga.jpg'> Descargar</a>"; /* transferimos al directorio miphp con nombre liborio.jpg un fichero procedente del directorio experimento cuyo nombre es lago_covadonga.jpg*/ ftp_get($x,"../miphp/liborio.jpg", "./experimento/lago_covadonga.jpg",FTP_ASCII); /* comprimimos un fichero alojado en miphp para transferirlo comprimido al servidor FTP */ #empezamos leyendo el fichero y guardándolo en una cadena $cadena=""; // inicializamos la variable con una cadena vacía $f1=fopen("cabina.jpg","r"); while (!feof($f1)) { $cadena .= fgets($f1,1024); }

Page 232: Manual PHP

fclose($f1); # comprimimos la cadena obtenida del fichero anterior $c1=gzencode($cadena,3,FORCE_GZIP); # guardamos la cadena comprimida en un fichero $f=fopen("cabina.jpg.gz","w"); fwrite($f,$c1); fclose($f); /* al servidor el fichero comprimido. No es necesario indicar la ruta actual ya que ha sido creado en el mismo directorio en el que se está ejecutando el script */ ftp_put($x, "./experimento/cabina.jpg.gz", "cabina.jpg.gz",FTP_BINARY); #eliminamos el fichero comprimido del directorio miphp unlink("cabina.jpg.gz"); # cerramos la conexión con el servidor ftp ftp_quit($x); # establecemos un enlace de descarga para el fichero comprimido print "<BR><a href='ftp://webmaster:webmaster@localhost"; print "/miphp/experimento/cabina.jpg.gz'>Descarga comprimido</a>"; ?>

¡Cuidado! Observa los path de los ejemplos. Al anteponer ../ estaremos indicando una ruta absoluta desde al root de servidor FTP y con ./ aludimos a un subdirectorio del actual. Si vas a utilizar el ejemplo anterior presta mucha atención a los nombres de los directorios y adecúalos a la configuración de tu servidor. Al ejecutar el script del ejemplo anterior por segunda vez (sobre Linux Ubuntu) puede aparecerte un mensaje de error del tipo overwrite permission denied. Este problema puede ser causado por un defecto de configuración del sevidor FTP. Hemos podido comprobar que, algunas veces, por una extraña razón, aparecen en el fichero de configuración dos líneas segudas dicendoAllowOverwrite Off y AllowOverwrite On. La configuración correcta es AllowOverwrite On (para permitir sobreescribir). Bastaría con eliminar la línea marcada con Off (o reemplazar el Off por On) para solventar el problema. Mensajes de correo

Correo electrónico

PHP dispone de una función que permite el envío de todo tipo de mensajes de correo electrónico desde una página web. Son escasos los hosting que tienen activada esta función. El motivo aducido por muchos de ellos para establecer esta restricción es el riesgo de abusosmediante los mensajes conocidos como spam. Como siempre, si tu interés es publicar y tienes necesidad de este servicio, procura consultar sobre la disponibilidad de este servicio.

Requisitos del sistema

La utilización de las funciones de correo electrónico requiere disponer de un servidor de correo electrónico instalado y activo y la modificación de la configuración inicial del fichero php.ini.

Si no tienes instalado este servidor puedes hacerlo ahora. En la página Servidor de correo tienes detallados ambos procesos (instalación y modificación de php.ini).

Sintaxis

La forma más simple de la función de correo es esta:

Page 233: Manual PHP

mail(destinatario,asunto,mensaje)

Dónde destinatario es la dirección del destinatario, asunto es el texto que aparecerá como Asunto en el encabezado que recibirá el destinatario y mensaje el texto que aparecerá en el cuerpo del mensaje.

No te olvides de escribir entre comillas esos tres parámetros de la función.

<? # insertamos la función mail (rojo) dentro de un condicional # para tener una confirmación de envio y/o aviso de error # es algo muy habitual para conocer el exito de la ejecución # de funciones if(mail("[email protected]", "Mi primer mensaje","Este es el texto")){ print "mensaje enviado"; }else{ print "el mensaje no ha podido enviarse"; } ?>

Mensaje con cabeceras MIME

Una forma más completa es la siguiente:

mail(destario,asunto,mensaje,cabecera)

Como puedes ver, en este caso añadimos un nuevo parámetro a la función (cabez) que debe estar entre comillas y que puede contener, separando con comas, lo siguiente:

� From: Nombre <e-mail> El texto que escribas en el parámetro Nombre (¡cuidado, no lleva comillas!) será el nombre del remitente, que aparecerá en el campo De: cuando el destinatario reciba el mensaje. Donde dice e-mail es obligado escribir una dirección de correo que debe ir contenida entre < y >, tal como puedes ver en el ejemplo.

� Reply-To: correo En una nueva línea, y sin cerrar las comillas de la cadena iniciada en el encabezado anterior, podemos indicar la dirección de respuesta del mensaje. La dirección que escribamos donde dice correo (fíjate que no va entre comillas) será la dirección a la que se enviará la respuesta si el destinatario una vez recibido tu correo desea responder mediante la opción Responder de su programa de correo electrónico.

� Cc: correo1,correo2,... De igual forma que en el caso anterior –en una nueva línea y sin cerrar comillas– podemos incluir en correo1, correo2, etcétera, las direcciones de correo de las personas a las que deseamos enviar copia del mensaje. No te olvides de separar con comas cada una de las direcciones que, como puedes ver en los ejemplos, no van entre comillas.

� Bcc: correo1,correo2,... Esta opción es idéntica a la anterior en cuanto a su funcionamiento, con la única diferencia de que esas direcciones no serán visibles por los destinatarios de los mensajes.

� X-Mailer:PHP/".phpversion() Es una frivolidad que se puede incluir en el encabezado del mensaje y que indica que versión de PHP con que ha sido creado.

¡Cuidado! Debemos insistir en sugerirte una especial atención a la sintaxis de este tipo de scripts. Los contenidos generados por la función mail deben ser interpretados por el servidor de correo y –tanto en este caso como en los que veremos a continuación– los requisitos de formato de estos servidores son muy estrictos. De no adaptarse exactamente a sus especificaciones pueden producirse efectos extraños tales como: envíos incorrectos y/o apariencias indeseadas en los mensajes. <?

Page 234: Manual PHP

mail("[email protected]","Varios destinatarios","Cuerpo del mensaje", "From: php <[email protected]> Reply-To: [email protected] Cc: [email protected],[email protected] Bcc:[email protected],[email protected] X-Mailer: PHP/" . phpversion()); ?>

En el ejemplo anterior insertaremos los nuevos elementos. Observa con mucha atención la estructura del código. Fíjate que hemos insertado en líneas diferentes cada uno de los conceptos: From, Reply-To, etcétera y que no hemos dejado ningún espacio al comienzo de esas líneas.

No es por capricho ni por afán estético. Si insertáramos algún carácter delante de esas líneas se plantearían problemas en la estructura del mensaje y si no incluyéramos un salto de línea para cada uno de los conceptos también tendríamos ese mismo problema.

La sintaxis MIME es muy estricta en este sentido. ¡Tengamos mucho cuidado en esto!

Hay otra posibilidad sintáctica –como alternativa a los saltos de línea– ya conocida. Podríamos escribir todo en una sola línea sustituyendo los saltos de línea que ves aquí por \n, de forma que el script tuviera un aspecto similar al siguiente:

mail("juan@localhost","Asunto","Contenido","\nReply-To: ...\nCc:.....\nBcc: ...") donde, como ves, los \n sustituyen a los saltos de línea.

El mismo ejemplo, utilizando variables

Aquí tenemos un ejemplo donde los parámetros de envío proceden de variables PHP. Recordemos que esas variables pueden transferirse –mediante un formulario– a un script que se encargue de su envío.

Como puedes observar, hemos puesto las direcciones de los destinatarios de las copias –visibles y ocultas– en sendos arrays y hemos añadido una función que: lee los array, los une en una cadena separándolos con comas y, por último, quita la última coma añadidautilizando la función substr.

<? #variables destinatario, asunto, texto, etc. $destino="[email protected]"; $envia="Andrés PHP"; $remite="[email protected]"; $asunto="Mensaje experimental"; $texto="Esto es una prueba. No es spam"; #array de destinatarios de copias visibles $c[0]="[email protected]"; $c[1]="[email protected]"; #crear la cadena con las direcciones # y añadir las comas de separación $cco=""; //creamos la variable (vacia) foreach($c as $pegar) { $cco .=$pegar; $cco.=","; }; #quitamos la coma del final de la cadena $l=strlen($cco);

Page 235: Manual PHP

$cco=substr($cco,0,$l-1); #array de destinatarios de copias OCULTAS $b[0]="[email protected]"; $b[1]="[email protected]"; #crear la cadena con las direcciones # y añadir las comas de separación $bco=""; //creamos la variable (vacia) foreach($b as $pegar) { $bco .=$pegar; $bco.=","; }; #quitamos la coma del final de la cadena $l=strlen($bco); $cco=substr($bco,0,$l-1); mail($destino, $asunto, $texto, "From: $envia <$remite> Reply-To: $remite Cc: $cco Bcc:$bco X-Mailer: PHP/" . phpversion()); ?>

Formatos MIME

Funciones PHP requeridas para el envío de mensajes

Podrás ver a lo largo de los ejemplos de envío de mensajes de correo electrónico algunas funciones raras que vamos a comentar seguidamente:

uniqid(prefijo,booleano)

Genera un identificador único basado en la hora actual del sistema, expresada en microsegundos y con una longitud de 13 caracteres. El parámetro prefijo permite establecer una cadena o número (puede ser una cadena vacía) que se antepone al identificador generado por la función. Opcionalmente permite el segundo parámetro booleano que debe ser un valor booleano (TRUE ó FALSE) o también 0 ó 1. Cuando este parámetro es TRUE añade al final de la cadena generada anteriormente otra subcadena numérica -generada aleatoriamente- de nueve dígitos, que refuerza la unicidad del identificador.

preg_replace(busca, reemplaza, cadena)

Busca en la cadena especificada en el parámetro cadena (que puede ser una cadena o una variable que contenga una cadena) las subcadenas especificadas en busca (recuerda que debe llevar delante y detrás un carácter delimitador tal como puedes ver en lasexpresiones regulares) y sustituye esas subcadenas por el contenido del parámetro reemplaza. Devuelve la cadena modificada.

strip_tags(cadena, excepciones)

Suprime todas las etiquetas HTML contenidas en cadena salvo las que se indiquen en excepciones. Por ejemplo: strip_tags($cadena, '<i><u><b>') eliminaría todas las etiquetas HTML, salvo las indicadas aquí y sus correspondientes cierres. Si no se especifican excepcioneselimina todas las etiquetas.

Page 236: Manual PHP

base64_encode(cadena)

Devuelve una cadena codificada en base64. Esta codificación se hace para permitir que las informaciones binarias puedan ser correctamente manipuladas por sistemas que no generan correctamente los 8 bits, tal como ocurre frecuentemente en los cuerpos de los mensajes de correo electrónico.

base64_decode(cadena)

Realiza el proceso inverso a la anterior. Decodifica una cadena previamente codificada en base64.

chunk_split(cadena, longitud, separador)

Devuelve una cadena obtenida al insertar en la cadena especificada -a intervalos del número de caracteres especificados en el parámetro numérico longitud- el contenido de una subcadena indicada en el parámetro separador. Por defecto -cuando no se especifican los parámetros- longitud es igual a 76 caracteres y el separador es la cadena \r\n (retorno y salto de línea).

Esta función se utiliza para convertir al formato especificado en la RFC 2045 (especificación para MIME) las cadenas obtenidas porbase64_encode. Es el formato habitual de los ficheros adjuntos de los e-mail.

Formato de los mensajes de correo electrónico

En la página anterior hemos hablado acerca de la manera de enviar un e-mail y veíamos la forma de insertar el cuarto parámetro de la función mail para incluir algunos elementos de los encabezados MIME. El formato de los mensajes está especificado en una serie de normas conocidas como el MIME (Multipurpose Internet Mail Extensions) en las que se establecen los contenidos y la sintaxis de las diferentes partes de un mensaje.

Recordemos que la función

mail(destinatario, asunto, mensaje, cabecera)

tiene cuatro parámetros y que las especificaciones del MIME aluden a los dos últimos, es decir a mensaje (el cuerpo del mensaje) ycabecera que es el encabezado del mismo. Respecto a destinatario y asunto no se requieren más comentarios que reiterar la necesidad de incluir esos valores (e-mail del destinatario y asunto) bien directamente, como parámetro en la función, o a través de una variable tal como hemos comentado en la página anterior.

Cabeceras de los mensajes

Los diferentes elementos de la cabecera de un mensaje deben insertarse siempre separados por saltos de línea bien pulsandoEnter o incluyendo la secuencia \n dentro de la misma de línea. No pueden incluirse espacios, ni al comiezo de las nuevas líneas ni después de \n, y las comillas –que han de contener todo el encabezado– se abren delante del primero de ellos y no se cierran hasta después de haber escrito el último. Pueden contener lo siguiente:

Date: xxxxx

Date: debe escribirse con esta sintaxis exactamente. El parámetro xxxxx es una cadena que contendrá la fecha de envío del mensaje y que puede obtenerse a través de una de las funciones de fecha de PHP tal como puedes ver en el ejemplo.

MIME-Version: 1.0

Este elemento de la cabecera especificará la versión MIME que ha de utilizar el cliente de correo para poder interpretar adecuadamente el contenido de los mensajes.

From: remitente<e-mail>

Page 237: Manual PHP

Este elemento de la cabecera permite indicar el nombre del remitente (remitente) y su dirección e-mail siguiendo la sintaxis que se especifica. El nombre, como un elemento independiente y la dirección e-mail dentro de < >.

¡Cuidado!

No debemos poner comillas ni en el nombre del remitente, ni en la dirección e-mail, ni en la fecha, etcétera.

Respecto a Cc: y Bcc: ; Reply-To: y X-Mailer: son válidos los comentarios que hemos hecho en la página anterior.

Si no se especifica lo contrario, los mensajes se envían como texto sin formato, pero existen opciones que permiten especificar el formato que ha de tener un mensaje. La especificación de un formato obliga a incluir otro elemento en cabecera del mensaje:

Content-Type:

Este elemento debe ir seguido de la especificación en la que se indique el tipo de contenido. Tiene la sintaxis: tipo/subtipo. El MIME establece un gran variedad de opciones para este propósito. Hablaremos de dos de ellas:

� text/plain El text/plain es la opción por defecto y señala que el contenido del mensaje es de tipo texto (text) y del subtipo sin formato (plain)

� text/html Como la opción anterior, es tipo texto, pero en este caso, el subtipo es html con lo cual el mensaje se visualizará en formato htmlsiempre que el cliente de correo permita esa posibilidad.

Los tipos anteriores permiten enviar mensajes simples (sin ficheros adjuntos) en uno u otro formato, pero el MIME nos da opciones para insertar dentro de un mismo mensaje elementos de diferentes tipos y subtipos. Las opciones de mayor interés son las siguientes:

� multipart/alternative Es la forma de especificar que el mensaje tiene varias partes (multipart) de las que el destinatario ha de ver una sola (alternative). Se podría utilizar en casos en los que sea necesario prever la posibilidad de que un mensaje con formato HTML pueda ser visualizado como texto plano cuando el cliente de correo no soporte HTML. Podemos hacer un mensaje a medida que se presentará de una forma u otra según el cliente utilizado para leerlo.

� multipart/mixed Cuando en el Content-Type se establece el tipo multiparte y el subtipo mezclado (mixed) será cuando tengamos la posibilidad deadjuntar ficheros al mensaje. Las diferentes partes de un mensaje deben ir separadas – tanto en modo alternativo como mezclado– y para ello hay que incluir un nuevo elemento en el encabezado. Se trata de un separador al que se llama boundary.

boundary=cadena

Dentro del encabezado y siempre en línea aparte (fíjate que en los ejemplos o está en línea aparte o aparece el \n) debemos incluir el elemento boundary= (sin símbolo de $ delante) y detrás del signo igual una cadena (en este caso entre comillas) que en principio puede ser una cadena cualquiera que no contenga espacios, aunque lo habitual es incluirla con el formato que podemos ver en los ejemplos.

El cuerpo del mensaje

En su formato más simple el cuerpo del mensaje contiene únicamente texto, pero cuando se trata de multipartes deberá contener necesariamente: los separadores de las diferentes partes, los encabezados de cada una de las partes y sus respectivos contenidos. La secuencia habría de ser de este tipo:

� Separador

� Content-type – Los tipos y subtipos más habituales son los siguientes. Para incluir textos: los ya mencionados text/plain y text/html. Para imágenes y según el tipo de imagen: image/jpeg, image/gif. Para sonidos: audio/basic. Para vídeo: video/mpeg. Para ejecutables,

Page 238: Manual PHP

comprimidos y otros ficheros adjuntos: application/octet-stream. – En cualquier caso, si quieres utilizar algún otro tipo de archivo puedes consultar en la web las especificaciones del MIME. – Aparte de tipo/subtipo puede añadirse a Content-type -en el caso de texto- separado por punto y coma, la especificación del tipo de alfabeto (charset=) seguida del tipo de codificación (te sugerimos el "ISO-8859-1" que hace alusión al alfabeto latino). – Cuando se trata de ficheros adjuntos deberemos poner, después del punto y coma, name= seguido del nombre y extensión del fichero que se adjunta.

� Content-Transfer-Encoding – Este apartado del encabezado puede especificar una de los siguientes codificaciones: 7BIT, 8BIT, BASE64, BINARY,QUOTED-PRINTABLE – La transferencia codificada en 7bit representa la codificación habitual en el formato ASCII de 7 bits. No permite caracteres ASCII con un código mayor que 127. – Quoted-printable constituye una de las alternativas al formato ASCII de 7 bits. Esta codificación suele usarse cuando la mayoría de los caracteres del mensaje puede escribirse con formato US ASCII de 7 bits. Prevé que los caracteres con códigos ASCII superiores a 127 se expresen mediante un mecanismo especial evitando, entre otras cosas, que las letras con tilde y algunos otros caracteres especiales se visualicen incorrectamente. Es la forma de codificación más recomendable para textos. – La codificación en base64 convierte cadenas binarias en cadenas de texto, con lo cual pueden ser enviadas de forma más segura. Es la forma de codificación habitual de las imágenes y los ficheros exe, zip, etcétera.

� Content-Disposition – Se utiliza únicamente cuando se insertan ficheros adjuntos. Permite dos opciones: inline o attachment. La primera permite que los contenidos se visualicen junto con el cuerpo del mensaje mientras que bajo la segunda aparecerían como ficheros adjuntos. – Este elemento del encabezado lleva –separada por punto y coma– una segunda parte. El filename=, donde se puede especificar entre comillas un nombre y una extensión (igual o distinta de la original) con la que se denominará al fichero en el mensaje recibido.

� Lectura del fichero Cuando se trata de insertar un fichero el proceso es el típico de lectura de ficheros, es decir:

� – Hay que crear el identificador de recurso del fichero en modo lectura.

� – Recoger en una variable el buffer de lectura.

� – Cerrar el fichero.

� Codificación Una vez recogido en el fichero a transmitir en una variable, el paso siguiente es codificar esa variable.Utilizaremos la codificación más habitual y flexible –base64– que requerirá el uso de las funciones base64_encode y chunk_split.

� Cuerpo del mensaje La fase final del proceso es la de agrupar los diferentes trozos en una sola variable, que será la que se insertará como parámetro texto en la función e-mail.

� Separador

� .....

� otra parte

� ...

� Separador final

¡Cuidado! La inserción de ficheros adjuntos requiere que éstos estén disponibles en el servidor por lo que, antes de enviarlos, habrá que subirlos al servidor utilizando un proceso como el que hemos analizado cuando hablábamos deTransferencia de ficheros.

Las cabeceras MIME de un mensaje

Page 239: Manual PHP

Aquí tienes un ejemplo con los diferentes elementos del encabezado de un mensaje. Como ves, hemos incluido todos los elementos dentro de la función mail.

<? mail("[email protected]", "Cabeceras", "Prueba de cabeceras", "Date: 24 de Junio de 2001 MIME-Version: 1.0 From: Estudiante Perico<[email protected]> Cc:[email protected] Bcc:[email protected] Reply-To: [email protected] X-Mailer: PHP/".phpversion()); ?>

Una forma un poco más depurada del script anterior podría ser esta que incluimos aquí debajo. Sus particularidades son las siguientes:

� – Recogemos los datos en variables e insertamos en la función mail esas variables

� – La variable $cabecera tiene algunas singularidades:

� – La vamos construyendo añadiendo subcadenas: date, from, etc. etc.

� – En cada subcadena dejamos pegado el contenido a las comillas iniciales

� – Al final de cada subcadena (cada una contiene un elemento del encabezado) insertamos \n para el carácter especial que indica a PHP un salto de línea imprescindible

<? # datos del mensaje $destinatario="[email protected]"; $titulo="Cabeceras en variables"; $mensaje="Nueva prueba de cabeceras"; $responder="[email protected]"; $remite="[email protected]"; $remitente="Otra vez Andres"; //sin tilde para evitar errores de servidor # cabeceras $cabecera ="Date: ".date("l j F Y, G:i")."\n"; $cabecera .="MIME-Version: 1.0\n"; $cabecera .="From: ".$remitente."<".$remite.">\n"; $cabecera .="Return-path: ". $remite."\n"; $cabecera .="X-Mailer: PHP/". phpversion()."\n"; if( mail($destinatario, $titulo, $mensaje,$cabecera)){ echo "mensaje enviado";}else{print "el mensaje no ha podido enviarse"; } ?>

Mensaje con contenido alternativo

<? # creamos la variables "salto" para "mayor comodidad # un salto es la secuencia retorno de carro-nueva línea # dos saltos es algo similar pero duplicado $UN_SALTO="\r\n"; $DOS_SALTOS="\r\n\r\n"; # creamos el remitente, etc. y también la que parte que

Page 240: Manual PHP

# contiene el código HTML del mensaje $destinatario="[email protected]"; $titulo="Mensaje alternativo Texto Plano - HTML "; $mensaje="<html><head></head><body bgcolor='#ff0000'>"; $mensaje .="<font face='Arial' size=6>Prueba HTML. </font>"; $mensaje .="aquí pueden ir tildes: á, é, í, ó, ú, ñ</body></html>"; $responder="[email protected]"; $remite="[email protected]"; $remitente="Andres Perez y Perez"; // omitimos las tildes en encabezados para evitar errores de servidor # creamos el separador de bloques del mensaje # anteponiento "_separador" aunque podríamos haber puesto "tiburcio" # generamos un identificador unico utilizando un numero aleatorio # como "semilla" y luego lo codificamos con la función md5 $separador ="_separador".md5 (uniqid (rand())); # creamos la variable cabecera con los elementos # ya utilizados en los ejemplos anteriores y ponemos al final # de cada elemento UN SALTO DE LINEA $cabecera = "Date: ".date("l j F Y, G:i").$UN_SALTO; $cabecera .="MIME-Version: 1.0\n"; $cabecera .="From: ".$remitente."<".$remite.">".$UN_SALTO; $cabecera .= "Return-path: ". $remite.$UN_SALTO; $cabecera .="Cc:[email protected]".$UN_SALTO; $cabecera .="Reply-To: ".$remite.$UN_SALTO; $cabecera .="X-Mailer: PHP/". phpversion().$UN_SALTO; # AQUÍ DEFINIMOS EL CONTENIDO MULTIPART, fíjate que lo acabamos con ";" $cabecera .="Content-Type: multipart/alternative;".$UN_SALTO; # insertamos BOUNDARY (fíjate que dejo un espacio # en BLANCO DELANTE y ponemos al FINAL los DOS SALTOS DE LINEA $cabecera .=" boundary=$separador".$DOS_SALTOS; # colocamos el primer separador(con los dos guiones delante) # antes de insertar la primera parte del mensaje # que es el texto plano para el caso de que el cliente de correo # no soporte HTML $texto_plano ="--$separador".$UN_SALTO; # especificamos el tipo de contenido y la codificación # e inserto DOS SALTOS AL FINAL ya que ahi acaba la cabecera de esta parte $texto_plano .="Content-Type:text/plain; charset=\"ISO-8859-1\"".$UN_SALTO; $texto_plano .="Content-Transfer-Encoding: 7bit".$DOS_SALTOS; # cambiamos las etiquetas "<br>" por saltos de línea # y luego quitamos todas las etiquetas HTML del cuerpo del mensaje # ya que el texto plano no debe llevar ese tipo de etiquetas $extractor= strip_tags(preg_replace("/<br>/", $UN_SALTO, $mensaje)); $texto_plano .=$extractor; # insertamos un nuevo separador para señalar el final # de la primera parte del mensaje y el comienzo de la segunda

Page 241: Manual PHP

# en este caso ponemos UN SALTO delante del separador ya que de lo contrario # al componer el mensaje se uniría con la cadena texto_plano anterior # que no tiene SALTO DE LINEA AL FINAL $texto_html =$UN_SALTO."--$separador".$UN_SALTO; # especificamos el encabezado HTML para el siguiente bloque # y ponemos en la ultima línea los DOS SALTOS DE LINEA $texto_html .="Content-Type:text/html; charset=\"ISO-8859-1\"".$UN_SALTO; $texto_html .="Content-Transfer-Encoding: 7bit".$DOS_SALTOS; #añado la cadena que contiene el mensaje $texto_html .= $mensaje; # insertamos SOLAMENTE un SALTO DE LINEA # estamos al funal del mensaje $texto_html .=$UN_SALTO; # unimos ambas cadenas para crear el cuerpo del mensaje $mensaje=$texto_plano.$texto_html; # enviamos el mensaje utilizando if( mail($destinatario, $titulo, $mensaje,$cabecera)){ echo "mensaje enviado ";}else{print "ha habido errores en el envio"; } ?>

Mensaje con ficheros adjuntos

<? # definimos estas variables igual que en el ejemplo anterior $UN_SALTO="\r\n"; $DOS_SALTOS="\r\n\r\n"; #incluimos en varias, asunto, un texto en HTML # remitente, etc. etc. $destinatario="[email protected]"; $titulo="Mensaje con dos fichero adjuntos"; $mensaje="<html><head></head><body bgcolor=\"#ff0000\">"; $mensaje .="<font face=\"Arial\" size=6>Prueba HTML </font>"; $mensaje .="</body></html>"; $responder="[email protected]"; $remite="[email protected]"; $remitente="Andres otra vez"; # definimos el separador de parte # con el mismo procedimiento del ejemplo anterior $separador = "_separador_de_trozos_".md5 (uniqid (rand())); # insertamos los datos de la cabecera del mensaje $cabecera = "Date: ".date("l j F Y, G:i").$UN_SALTO;

Page 242: Manual PHP

$cabecera .= "MIME-Version: 1.0".$UN_SALTO; $cabecera .= "From: ".$remitente."<".$remite.">".$UN_SALTO; $cabecera .= "Return-path: ". $remite.$UN_SALTO; $cabecera .= "Reply-To: ".$remite.$UN_SALTO; $cabecera .="X-Mailer: PHP/". phpversion().$UN_SALTO; # especificamos el tipo de contenido mutipart/mixed # ya que ahora insertaremos ficheros de distinto tipo $cabecera .= "Content-Type: multipart/mixed;".$UN_SALTO; # insertamos el valor de boundary haciéndola igual a $separador # y acabamos con DOS SALTOS porque es el FINAL DE LA CABECERA $cabecera .= " boundary=$separador".$DOS_SALTOS; /* Parte primera del envio -Mensaje en formato HTML ================================================ Separador inicial ------------------------------- */ $texto ="--$separador".$UN_SALTO; /* Encabezado parcial ------------------ */ /* especificamos que este primer elemento será texto y que irá codificado en formato 7 bits */ $texto .="Content-Type: text/html; charset=\"ISO-8859-1\"".$UN_SALTO; $texto .="Content-Transfer-Encoding: 7bit".$DOS_SALTOS; /* Contenido de esta parte del mensaje -----------------------------------*/ # ya teniamos escrito el texto del mensaje más arriba # simplemente lo añadimos a la cadena de texto $texto .= $mensaje; #la variable $texto recoge esta parte del documento # la uniremos al final con las siguientes /* Separador de partes -------------------- */ $adj1 = $UN_SALTO."--$separador".$UN_SALTO; /* Parte segunda de mensaje -Fichero adjunto nº 1 ==================================================== */ /* Encabezado parcial ------------------ */ # especificamos el tipo de contenido image/jpeg # ya que ese será el documento que vamos a enviar # ponemos el nombre del fichero (debemos tenerlo en el servidor # con ese mismo nombre) # establecemos in line como disposición para que pueda ser visualizado # directamente en el cuerpo del mensajes # en filename le asignamos el nombre con el que queremos que sea # recibido por el destinatario # por ultimo especificamos la codificacion como base64 $adj1 .="Content-Type: image/jpeg;";

Page 243: Manual PHP

$adj1 .=" name=\"casa08.jpg\"".$UN_SALTO; $adj1 .="Content-Disposition: inline; "; $adj1 .="filename=\"leoncio.jpg\"".$UN_SALTO; $adj1 .="Content-Transfer-Encoding: base64".$DOS_SALTOS; /* Lectura previa del fichero a adjuntar ------------------------------------------ */ # abrimos el fichero en modo lectura (r) # y leemos todo su contenido midiendo previamente # su longitud con filesize # recogemos en $buff el contenido del fichero # y cerramos después $fp = fopen("casa08.jpg", "r"); $buff = fread($fp, filesize("casa08.jpg")); fclose($fp); /* Codificación del fichero a adjuntar ------------------------------------------ */ # codificamos en base 64 y troceamos en lineas de 76 caracteres # y añadimos el resultado a la variable adj1 $adj1 .=chunk_split(base64_encode($buff)); /* Separador de partes -------------------- */ $adj2 = $UN_SALTO."--$separador".$UN_SALTO; /* Tercera parte de mensaje -Fichero adjunto nº 2 ==================================================== */ /* Encabezado parcial ------------------ */ # los contenidos del encabezado son similares al caso anterior # con la salvedad de que el contenido es ahora # application/octet-stream ya que contiene un fichero ejecutable # y la disposicion es attachment, no tiene sentido tratar # de visualizar un fichero zip $adj2 .="Content-Type: application/octet-stream;"; $adj2 .=" name=\"apachito.zip\"".$UN_SALTO; $adj2 .="Content-Disposition: attachment; filename=\"apachito.zip\"".$UN_SALTO; $adj2 .="Content-Transfer-Encoding: base64".$DOS_SALTOS; /* Lectura previa del fichero a adjuntar ------------------------------------------ */ # abrimos el fichero en modo lectura (r) # y leemos todo su contenido midiendo previamente # su longitud con filesize # recogemos en $buff el contenido del fichero # y cerramos después $fp = fopen("apachito.zip", "r"); $buff = fread($fp, filesize("apachito.zip")); fclose($fp); /* Codificación del fichero a adjuntar ------------------------------------------ */ $adj2 .=chunk_split(base64_encode($buff));

Page 244: Manual PHP

/* Separador final YA NO HAY MAS PARTES ---------------------------------------- */ $adj2 .=$UN_SALTO."--$separador".$UN_SALTO; /* Unión de todas las PARTES ---------------------------------------- */ # unimos en la variable mensaje todos los elementos # y lo hacemos por el orden en el que fueron creados $mensaje=$texto.$adj1.$adj2; /* Envio del mensaje ---------------------------------------- */ if(mail($destinatario, $titulo, $mensaje,$cabecera)){ echo "mensaje enviado";}else{print "ha habido problemas"; } ?>

Imágenes dinámicas

Imágenes dinámicas

PHP permite la creación dinámica de imágenes. Quiere esto decir que una imagen puede ser presentada en la página web sin necesidad de ser almacenada previamente en el servidor y, además, con un contenido que puede ser modificado en cada instante. Esta posibilidad que ofrece PHP puede resultar muy útil a la hora de presentar gráficos estadísticos ya que permitiría utilizar valores actualesobtenidos, por ejemplo, de una base de datos.

Requisitos del sistema

El manejo de imágenes dinámicas requiere que esté instalada la librería de PHP llamada php_gd2.dll. En la versión de PHP que estamos manejando se instala por defecto, pero requiere que la configuración de fichero php.ini tenga activada esta extensión. La hemos activado durante el proceso de configuración de PHP (para usuarios de Windows) y en instalación de Apache + PHP (para usuarios de Ubuntu).

Podemos probar a abrir este enlace, info.php en el que habremos de encontrar algo similar a lo que ves en esta imagen:

Si eso ocurre habremos comprobado nuestra configuración es la adecuada para utilizar las funciones PHP de este ámbito y estaremos en disposición de poder generar imágenes dinámicas.

Page 245: Manual PHP

Formatos GIF

Aunque son abundantes los materiales que aluden a este formato gráfico -incluso en las páginas oficiales PHP- los formatos GIF sólo funcionan en modo lectura. Parece ser que existe un conflicto sobre los derechos de propiedad del algoritmo de compresión que se utiliza en los ficheros .gif y eso está obligando a los desarrolladores de PHP a abandonar este tipo de formato.

Formatos PNG

El formato de imágenes PNG (Portable Network Graphic) nos permite seguir disponiendo de un formato gráfico de difusión gratuitacon una funcionalidad similar al GIF en lo que se refiere a transparencias y que junto con la posibilidad de usar también el formato JPG va a cubrir las necesidades gráficas de esta utilidad de PHP.

Scripts para gráficos estadísticos

Si en algún momento tienes interés en insertar en tus páginas gráficos estadísticos, en esta direcciónhttp://www.aditus.nu/jpgraph/index.php podrás encontrar una interesante colección de scripts listos para usar, con licencia gratuita para usos no comerciales.

Formatos soportados

Formatos de imágenes

Aunque podemos obtener desde info.php información sobre los tipos de imágenes soportados por la versión en uso de PHP, existe una función que permite determinarlos.

imagetypes()

Devuelve un campo de bits correspondiente a los formatos soportados por la versión de GD que estamos utilizando. Los formatos de imagen que PHP soporta actualmente son: GIF, JPG, PNG y WBMP. El conocimiento de estas posibilidades gráficas puede sernos muy útil a la hora de elegir entre los diferentes formatos gráficos disponibles. Aquí tienes el código fuente de un fichero que permite obtener información sobre los formatos soportados por tu versión de PHP.

Formatos soportados <?php if (imagetypes() & IMG_GIF) { echo "El tipo GIF es soportado<br>"; }else{ echo "El tipo GIF NO ES SOPORTADO<BR>"; } if (imagetypes() & IMG_PNG) { echo "El tipo PNG es soportado<br>"; }else{ echo "El tipo PNG NO ES SOPORTADO<BR>"; } if (imagetypes() & IMG_JPG) { echo "El tipo JPG es soportado<br>"; }else{ echo "El tipo JPG NO ES SOPORTADO<BR>"; } if (imagetypes() & IMG_WBMP) { echo "El tipo WBMP es soportado<br>"; }else{ echo "El tipo WBMP NO ES SOPORTADO<BR>"; } ?>

ejemplo191.php

Page 246: Manual PHP

Creando imágenes

Creación de imágenes dinámicas

Una imagen dinámica es tan sólo un script que contiene las instrucciones para la creación de esa imagen. Para visualizar una imagen dinámica desde una página web basta con invocar el fichero que contiene el script desde la etiqueta clásica de inserción de imágenes de HTML <img src="imgxx.php"> donde imgxx.php será el nombre del script que genera la imagen.

Primera etiqueta

Una vez conocidos los formatos que soporta nuestra versión, ya podemos generar imágenes utilizando cualquiera de esos formatos. Trabajaremos con dos de ellos: JPG y PNG.

La primera instrucción que ha de contener cualquier script que deba generar imágenes ha de ser la siguiente:

Header("Content-type: image/jpeg") si se trata de crear una imagen JPG o: Header("Content-type: image/png") si pretendemos que la imagen tenga formato PNG. ¡Cuidado! Cualquier etiqueta header (cabecera) ha de incluirse obligatoriamente al comienzo del script antes que ninguna otra instrucción y sin ninguna línea en blanco que la preceda. Pondremos siempre estas instrucciones inmediatamente debajo de <? sin que las separe ninguna línea en blanco.

Creación de imágenes

Definida la etiqueta anterior tenemos que: crear la imagen, dibujarla y luego enviarla al navegador para que pueda ser visualizada y, por último, (no es imprescindible pero si muy conveniente) borrarla, con el fin de liberar la memoria del servidor ocupada durante el proceso de generación de la misma. Estas son las funciones PHP para esos procesos:

$nombre = imagecreate(ancho, alto)

Con esta función se crea una imagen del tamaño indicado en los parámetros ancho y alto (en pixels) que será recogida en la variable$nombre. Esta función es idéntica para cualquier formato de imagen.

Envío de imágenes al navegador

Para enviar imágenes al navegador (visualización) se usan funciones diferentes según el tipo de imagen definida en Header. Si pretendemos que la imagen tenga formato JPG habremos puesto en Header la indicación jpeg (¡cuidado! observa la sintaxis... jpeg). En este caso la función de visualización será:

Imagejpeg($nombre)

Si se tratara de una imagen en formato PNG (recuerda que debe estar definido en Header) la sintaxis sería:

Imagepng($nombre)

Page 247: Manual PHP

Eliminando imágenes de la memoria

Para borrar imágenes de la memoria del servidor (que no del navegador) se utiliza la siguiente sintaxis: Imagedestroy($nombre)

El primer ejemplo

<?php /* insertamos la cabecera sin ninguna contenido (ni siquiera línea en blanco) qantes <php */ Header("Content-type: image/jpeg"); /* establecemos las dimensiones de la imagne y la creamos */ $im = imagecreate(200,200); /* mostramos las imagen */ Imagejpeg($im); /* la eliminamos de la memoria */ Imagedestroy($im); ?>

Ver ejemplo192.php

Colores

Creación de colores

PHP permite crear una paleta de colores. Para ello se pueden crear variables de color (con independencia del formato utilizado) mediante la siguiente función:

$color=imagecolorallocate ($nombre, rojo, verde, azul)

donde la variable $color recoge el color resultante de mezclar los colores primarios indicados en rojo, verde y azul que serán números enteros comprendidos entre 0 y 255 y que especifican la intensidad de las luces roja, verde y azul utilizadas para la obtención del color y donde $nombre es la variable utilizada para la creación de la imagen por imagecreate

Se pueden definir tantos colores como se deseen tan sólo con utilizar nombres de variables distintos para cada uno de ellos.

Aplicar colores de fondo

Para aplicar un color de fondo a una imagen (no importa el tipo del formato) se utiliza la siguiente función:

Imagefill($nombre,x,y,$color)

donde $nombre es la variable que contiene la imagen, x e y son las coordenadas del punto de la imagen a partir del cual se aplica el relleno y $color el color (previamente definido) que se pretende aplicar a la imagen. Mediante esta función todos los puntos colindantes con el de coordenadas x,y que tengan su mismo color serán rellenados con el color especificado en la variable $color.

Aquí tienes dos ejemplos de creación de una imagen con un color de fondo. Se diferencian únicamente en el formato. La primera será una imagen JPG y la segunda será PNG.

<?php Header("Content-type: image/jpeg"); $im = imagecreate(200,200); /* creamos un color de fondo con nombre $fondo */ $fondo=imagecolorallocate ($im, 0, 0, 200); /* aplicamos el clor al fondo */

Page 248: Manual PHP

Imagefill ($im, 0, 0, $fondo); Imagejpeg($im); Imagedestroy($im); ?>

Ver ejemplo193.php

<?php Header("Content-type: image/png"); $im = imagecreate(200,200); /* creamos un color de fondo con nombre $fondo */ $fondo=imagecolorallocate ($im, 0, 0, 200); /* aplicamos el clor al fondo */ Imagefill ($im, 0, 0, $fondo); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo194.php

Figuras

Rectángulos sin relleno

Para dibujar un rectángulo sin relleno (solo las líneas) se utiliza la siguiente función:

imagerectangle($nombre, x0, y0, x1, y1, $color)

Dónde $nombre es el nombre de la imagen, x0, y0 son las coordenadas del vértice superior izquierdo y x1, y1 las coordenadas delvértice inferior derecho y $color el color que pretendemos asignar a los lados del rectángulo.

El punto (0,0) siempre es la esquina superior izquierda de la imagen y recuerda que las líneas no tienen un color distinto al del fondo no se visualizará el rectángulo.

<?php Header("Content-type: image/jpeg"); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $linea=imagecolorallocate ($im, 255, 255, 255); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $linea); Imagejpeg($im); Imagedestroy($im); ?>

Ver ejemplo195.php

<?php Header("Content-type: image/png"); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $linea=imagecolorallocate ($im, 255, 255, 255); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $linea); Imagepng($im); Imagedestroy($im); ?>

Page 249: Manual PHP

Ver ejemplo196.php

Rectángulos con relleno

Para dibujar un rectángulo con relleno se utiliza la siguiente función:

imagefilledrectangle($nombre, x0, y0, x1, y1, $color)

Los parámetros son idénticos a los del caso anterior con la única diferencia de que en este caso el rectángulo aparecerá relleno con el color elegido.

<?php Header("Content-type: image/jpeg"); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0); /*aplicamos un color al fondo de la imagen */ Imagefill ($im, 0, 0, $fondo); /* trazamos un rectangulo en blanco */ imagerectangle ($im, 10, 10, 190, 190, $blanco); /* dentro del rectángulo anterior insertamos otro coloreado */ imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); Imagejpeg($im); Imagedestroy($im); ?>

Ver ejemplo197.php

<?php Header("Content-type: image/png"); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0); /*aplicamos un color al fondo de la imagen */ Imagefill ($im, 0, 0, $fondo); /* trazamos un rectangulo en blanco */ imagerectangle ($im, 10, 10, 190, 190, $blanco); /* dentro del rectángulo anterior insertamos otro coloreado */ imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo198.php

Polígonos con relleno

Para trazar un polígono con un color de fondo son necesarias dos operaciones:

� – Crear un array con las coordenadas de cada uno de sus vértices.

� – Aplicar la función que dibuja polígonos de este tipo.

La creación del array podría hacerse así:

$vertices=(x0, y0, x1, y1,... xn, yn )

Page 250: Manual PHP

dónde se irían introduciendo las coordenadas de los sucesivos vértices del polígono (x e y de cada vértice). Una vez creados los vértices aplicaríanos la siguiente función:

imagefilledpolygon($nombre, $vertices, nº vertices , $color)

donde $nombre es el nombre de la imagen, $vertices es el array que contiene las coordenadas de los vértices, nº vertices es el número de vértices del polígono y $color es el color de relleno.

<?php Header("Content-type: image/jpeg"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagefilledpolygon ($im, $esquinas, 4, $blanco); Imagejpeg($im); Imagedestroy($im); ?>

Ver ejemplo199.php

<?php Header("Content-type: image/png"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagefilledpolygon ($im, $esquinas, 4, $blanco); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo200.php

Polígonos sin relleno

Su funcionamiento es idéntico al anterior en tanto requiere que se defina el array de coordenadas de los vértices y los parámetros de la función son los mismos indicados en el caso anterior. Sólo se modifica el nombre de la función que en este caso es:

imagepolygon($nombre, $vertices, nº vertices , $color) <?php Header("Content-type: image/jpeg"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $rojo=imagecolorallocate ($im, 255,0, 0); $amarillo=imagecolorallocate ($im, 255, 255,0); Imagefill ($im, 0, 0, $fondo);

Page 251: Manual PHP

imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagepolygon ($im, $esquinas, 4, $rojo); Imagejpeg($im); Imagedestroy($im); ?>

Ver ejemplo201.php

<?php Header("Content-type: image/png"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $rojo=imagecolorallocate ($im, 255,0, 0); $amarillo=imagecolorallocate ($im, 255, 255,0); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagepolygon ($im, $esquinas, 4, $rojo); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo202.php

Elipses, circunferencias y arcos

Mediante una única función podremos dibujar elipses, circunferencias y arcos. Es la siguiente:

imagearc($nom, Xcentro, Ycentro , a, b, anguloinicial, angulofinal, $color)

Los parámetros de esta función son los siguientes:

� $nombre es el nombre de la imagen.

� Xcentro e Ycentro las coordenadas del centro de la elipse o circunferencia.

� a es la longitud del semieje horizontal de la elipse.

� b es la longitud del semieje vertical de la elipse.

� anguloinicial es la posición angular del radio inicial del arco y se expresa en grados sexagesimales.

� angulofinal es la posición angular del radio final del arco también en grados sexagesimales.

� $color es el color con el que se dibujará la línea.

Respecto a los ángulos, CERO GRADOS coincide con el cero trigonométrico pero el sentido es contrario, es decir, el de las agujas del reloj.

Obviamente, para dibujar una circunferencia basta con hacer iguales los valores de a y de b y fijar los puntos inicial y final en 0º y 360º respectivamente.

<?php Header("Content-type: image/jpeg"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0);

Page 252: Manual PHP

$rojo=imagecolorallocate ($im, 255, 0,0); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagepolygon ($im, $esquinas, 4, $blanco); imagearc ($im, 100, 100, 160, 160, 0, 360, $fondo); imagearc ($im, 100, 100, 160, 100, 0, 360, $rojo); imagearc ($im, 100, 100, 100, 160, 0, 360, $rojo); Imagejpeg($im); Imagedestroy($im); ?>

Ver ejemplo203.php

<?php Header("Content-type: image/png"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreate(200,200); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0); $rojo=imagecolorallocate ($im, 255, 0,0); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagepolygon ($im, $esquinas, 4, $blanco); imagearc ($im, 100, 100, 160, 160, 0, 360, $fondo); imagearc ($im, 100, 100, 160, 100, 0, 360, $rojo); imagearc ($im, 100, 100, 100, 160, 0, 360, $rojo); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo204.php

Dibujando sobre una imagen de fondo

PHP permite crear imágenes utilizando como fondo una prEexistente. Para ello es necesario cambiar la instrucción $nom = imagecreate(x,y) por $nom= imagecreatefrompng (nombre_de_la_imagen_de_fondo) si se trata de utilizar una imagen con formato PNG o$nom= imagecreatefromjpeg (nombre_de_la_imagen_de_fondo) cuando se trata de usar una imagen JPG.

La imagen resultante puede tener formato JPG o PNG dependiendo de lo que se especifique en Header y como formato de salida independientemente del formato que pudiera tener la imagen utilizada como fondo de la composición

<?php Header("Content-type: image/jpeg"); $esquinas=array(20,100,100,180,180,100,100,20); $im = imagecreatefrompng('./images/cruz.png'); $fondo=imagecolorallocate ($im, 0, 0, 200); $blanco=imagecolorallocate ($im, 255, 255, 255); $amarillo=imagecolorallocate ($im, 255, 255,0); $rojo=imagecolorallocate ($im, 255, 0,0); Imagefill ($im, 0, 0, $fondo); imagerectangle ($im, 10, 10, 190, 190, $blanco); imagefilledrectangle ($im, 20, 20, 180, 180, $amarillo); imagepolygon ($im, $esquinas, 4, $blanco); imagearc ($im, 100, 100, 160, 160, 0, 360, $fondo); imagearc ($im, 100, 100, 160, 100, 0, 360, $rojo); imagearc ($im, 100, 100, 100, 160, 0, 360, $rojo); Imagejpeg($im); Imagedestroy($im); ?>

Page 253: Manual PHP

Ver ejemplo204a.php Imagen original PNG Imagen resultante JPG

Como puedes comprobar en los ejemplos, el tamaño de la imagen es el mismo de la utilizada como fondo.

Guardando imágenes

Las imágenes que son creadas mediante la sintaxis anterior no se guardan en el servidor. Si se pretendiera guardarlas, hay que modificar la sintaxis de las etiquetas:

Imagepng($nombre) o Imagejpeg($nombre) añadiendo un segundo parámetro con el nombre y la extensión del fichero que vamos de guardar.

Así por ejemplo: Imagepng($nombre, "mi_imagen.png") o Imagejpeg($nombre, "mi_imagen.jpg") guardarían en el servidor las imágenes creadas con los nombres mi_imagen.png o mi_imagen.jpg en el directorio actual del servidor.>

¡Cuidado! No dejes NUNCA líneas en blanco entre la etiqueta <? de comienzo del script y la línea que contiene Header Si escribiéramos el script anterior sustituyendo image/jpeg por image/png e Imagejpeg($im) por Imagepng($im)no visualizaríamos nada. El formato jpg –a falta de especificaciones– considera la imagen con negro como color de fondo, pero png requiere que ese color sea especificado.

Utilización de imágenes dinámicas

En todos los ejemplos anteriores hemos podido visualizar las imágenes con sólo llamarlas desde el navegador, de la misma forma que podríamos visualizar cualquier otra imagen. Pero las imágenes dinámicas pueden ser insertadas en una página web de la misma forma que cualquier otra imagen.

Aquí tienes un ejemplo donde se recogen en una página web todas las imágenes dinámicas creadas anteriormente.

ejemplo205.php Ver código fuente

Si observas el código fuente verás que es exactamente el mismo que se utiliza para insertar una imagen normal, con la única diferencia de que aquí el nombre de la imagen será el mismo que el del script PHP que la genera.

Imágenes con líneas y textos

Trazando segmentos

La función PHP que permite dibujar segmentos rectilíneos es la siguiente:

imageline($nombre, x0, y0, x1,y1,$color)

donde: $nombre es el nombre de la variable definida mediante imagecreate, x0 e y0 son las coordenadas de uno de los extremos; x1e y1 son las coordenadas del otro extremo y $color es la variable de color con el que será dibujada la línea.

Tal como puedes observar en los primeros ejemplos de esta página, PHP utiliza como fondo de la imagen el primer color definido por la función: ImageColorAllocate. Esta opción de PHP nos obliga a definir dos colores distintos para conseguir la visibilidad de las líneas.

Page 254: Manual PHP

Aunque presentaremos únicamente el código fuente de una de las imágenes, para hacer la comprobación de las funciones y las diferencias de visualización insertaremos dos ejemplos, uno en formato PGN y otro en JPG.

Recuerda que las únicas diferencias entre ambos radican en utilizar: Header("Content-type: image/png") o Header("Content-type: image/jpeg") y en las funciones Imagepng ó Imagejpeg.

<?php Header("Content-type: image/png"); $im = imagecreate(200,200); $fondo=ImageColorAllocate ($im,0,0,255); $linea=ImageColorAllocate ($im,255,255,255); imageline($im,0,0,200,200,$linea); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo211.php Formato PNG

Ver ejemplo212.php Formato JPG

Crear transparencias

Si deseamos que un color determinado se comporte como si fuera transparente debemos utilizar la función:

imagecolortransparent ($nombre ,$color).

donde: $nombre es el nombre de la variable definida mediante imagecreate, y $color es el color que pretendemos hacer transparente.

No olvides estos dos pequeños detalles:

� • Si pretendes lograr un fondo transparente debes hacer transparente el primero de los colores definidos.

� • Esta función sólo tiene sentido en imágenes PNG que son las únicas que permiten zonas transparentes. Recuerda que JPG no las permite.

<?php Header("Content-type: image/png"); $im = imagecreate(200,200); $fondo=ImageColorAllocate ($im,0,0,255); $linea=ImageColorAllocate ($im,255,0,0); imagecolortransparent ($im ,$fondo); imageline($im,0,0,200,200,$linea); Imagepng($im); Imagedestroy($im); ?>

Ver ejemplo213.php Formato PNG

Ver ejemplo214.php Formato JPG

Insertando textos

Para insertar textos dentro de una imagen hemos de recurrir a una de estas funciones:

imagechar ($imagen, tamaño, x, y, $texto, $color)

Page 255: Manual PHP

Requiere que la variable $texto contenga una cadena definida con anterioridad. Mediante esta función se inserta el primer carácter de la cadena con orientación horizontal. Los parámetros de la función son los siguientes:

� $nombre el nombre de la variable con la que fue definida por imagecreate

� tamaño es un número comprendido entre UNO y CINCO que asigna el tamaño de la letra de menor a mayor.

� x e y son las coordenadas del punto donde se colocará la esquina superior izquierda del carácter a representar.

� $texto es la cadena de texto de la que se extraerá el primer carácter, el único que se verá en la imagen.

� $color es el color del carácter a representar.

<?php Header("Content-type: image/png"); $im = imagecreate(150,150); $t1="Tamaño 1"; $t2="Tamaño 2"; $t3="Tamaño 3"; $t4="Tamaño 4"; $t5="Tamaño 5"; $fondo=imagecolorallocate ($im, 0, 0, 200); $amarillo=imagecolorallocate ($im, 255, 255,0); imagechar ($im, 1, 0, 0, $t1, $amarillo); imagechar ($im, 2, 20, 20, $t2, $amarillo); imagechar ($im, 3, 40, 40, $t2, $amarillo); imagechar ($im, 4, 60, 60, $t2, $amarillo); imagechar ($im, 5, 80, 80, $t2, $amarillo); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo216.php Formato PNG

Ver ejemplo217.php Formato JPG

imagecharup ($imagen, tamaño, x, y, $texto, $color)

Su funcionamiento es similar al de la función anterior, con la única diferencia de que inserta el carácter con orientación vertical. Lascoordenadas de inserción también se corresponden con las de la esquina superior izquierda del carácter pero, recuerda que ahora estará girado y que, por lo tanto, ese punto coincidirá con parte inferior izquierda de la imagen del carácter.

<?php Header("Content-type: image/png"); $im = imagecreate(150,150); $t1="Tamaño 1"; $t2="Tamaño 2"; $t3="Tamaño 3"; $t4="Tamaño 4"; $t5="Tamaño 5"; $fondo=imagecolorallocate ($im, 0, 0, 200); $amarillo=imagecolorallocate ($im, 255, 255,0); imagecharup ($im, 1, 10, 10, $t1, $amarillo); imagecharup ($im, 2, 20, 20, $t2, $amarillo); imagecharup ($im, 3, 40, 40, $t2, $amarillo); imagecharup ($im, 4, 60, 60, $t2, $amarillo); imagecharup ($im, 5, 80, 80, $t2, $amarillo); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo218.php Formato PNG

Ver ejemplo219.php Formato JPG

Page 256: Manual PHP

imagestring ($imagen, tamaño, x, y, $texto, $color)

Esta función se comporta de forma similar a imagechar. La única diferencia entre ambas es que mientras imagechar inserta sólo el primer carácter, en el caso de imagestring se inserta la cadena completa. Los parámetros de ambas funciones son los mismos.

Si la cadena desborda los límites de la imagen sólo se visualizará la parte de la misma contenida dentro de éstos.

<?php Header("Content-type: image/png"); $im = imagecreate(150,150); $t1="Tamaño 1"; $t2="Tamaño 2"; $t3="Tamaño 3"; $t4="Tamaño 4"; $t5="Tamaño 5"; $fondo=imagecolorallocate ($im, 0, 0, 200); $amarillo=imagecolorallocate ($im, 255, 255,0); imagestring ($im, 1, 10, 20, $t1, $amarillo); imagestring ($im, 2, 10, 40, $t2, $amarillo); imagestring ($im, 3, 10, 60, $t3, $amarillo); imagestring ($im, 4, 10, 80, $t4, $amarillo); imagestring ($im, 5, 10, 100, $t5, $amarillo); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo220.php Formato PNG

Ver ejemplo221.php Formato JPG

imagestringup ($imagen, tamaño, x, y, $texto, $color)

Inserta una cadena completa con orientación vertical y sus parámetros son idénticos a los comentados cuando nos hemos referido aimagecharup.

<?php Header("Content-type: image/png"); $im = imagecreate(150,150); $t1="Tamaño 1"; $t2="Tamaño 2"; $t3="Tamaño 3"; $t4="Tamaño 4"; $t5="Tamaño 5"; $fondo=imagecolorallocate ($im, 0, 0, 200); $amarillo=imagecolorallocate ($im, 255, 255,0); imagestringup ($im, 1, 10, 100, $t1, $amarillo); imagestringup ($im, 2, 20, 100, $t2, $amarillo); imagestringup ($im, 3, 40, 100, $t3, $amarillo); imagestringup ($im, 4, 60, 100, $t4, $amarillo); imagestringup ($im, 5, 80, 100, $t5, $amarillo); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo222.php Formato PNG

Ver ejemplo223.php Formato JPG

Page 257: Manual PHP

Tipos de letra

Todas las funciones anteriores utilizan siempre la fuente predefinida por PHP y sólo permiten los cinco tamaños que hemos podido ver en los ejemplos. Afortunadamente –lo veremos en la página siguiente– PHP también permite usar fuentes TrueType y aplicarlas en la creación de imágenes.

Manejando fuentes

Utilizando fuentes TrueType

Si has leído los comentarios de la página anterior recordarás que para usar estas funciones es preciso que estén instaladas las librerías FreeType y que, además, conozcamos el path de directorio que contiene las fuentes TrueType.

Hemos creado un subdirectorio llamado fuentes y lo hemos incluido en directorio donde están alojadas estas páginas. En ese subdirectorio hemos incluido dos fuentes TrueType manteniendo en una de ellas el nombre original (arial.ttf) y renombrado la otra comofuente2.ttf.

Como podrás comprobar en los ejemplos, no hay problema alguno por el hecho de renombrar las fuentes.

Escribiendo con fuentes TrueType

La función PHP que nos permite insertar este tipo de textos en imágenes dinámicas es la siguiente:

Imagettftext($nombre, tamaño, angulo, x, y, $color, $fuente, $texto)

donde:

� $nombre es, como siempre, el nombre de la imagen.

� tamaño es un número entero que indica el el tamaño de la fuente.

� angulo es el giro expresado en grados sexagesimales que pretendemos que tenga la cadena de texto. Si ang=0 el texto aparecerá escrito en horizontal.

� x e y son las coordenadas del punto de inicio de la inserción del texto. Ese punto coincide con la esquina inferior izquierda del rectángulo imaginario que contiene el texto.

� $color es el color a utilizar en el texto.

� fuente es una cadena de texto que contiene el path y el nombre de la fuente. Observa los ejemplos.

� $texto es el nombre de la variable que contiene el texto a insertar.

Texto True Type horizontal

<?php Header("Content-type: image/png"); $im = imagecreate(400,300); $fondo=imagecolorallocate ($im, 255, 255, 210); $rojo=imagecolorallocate ($im, 255, 0, 0); $texto="PHP"; Imagettftext($im, 40, 0, 100, 270, $rojo,"./fuentes/fuente2.ttf", $texto); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo224.php Formato PNG

Ver ejemplo225.php Formato JPG

Page 258: Manual PHP

Texto True Type girado

<?php Header("Content-type: image/png"); $im = imagecreate(400,300); $fondo=imagecolorallocate ($im, 255, 255, 210); $rojo=imagecolorallocate ($im, 255, 0, 0); $texto="Me gusta PHP"; Imagettftext($im, 40, 30, 100, 270, $rojo, "./fuentes/fuente2.ttf", $texto); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo226.php Formato PNG

Ver ejemplo227.php Formato JPG

¡Cuidado! Las rutas (path) de la las fuentes utilizadas por Imagettftext pueden darte un montón de quebraderos de cabeza. Las rutas relativas que ves en estos ejemplos no parecen dar problema alguno. Sin embargo si hiciéramos algo como crear una variable previa del tipo: $ruta="fuentes"; y luego sustituir "./fuentes/fuente2.ttf" por: "./". $ruta."/fuente2.ttf" que aparentemente da el mismo resultado ./fuentes/fuente2.ttf se nos genera un error de fuente no encontrada. La solución que parece ser más efectiva es huir como alma que lleva el diablo del uso de variables en la asignación de rutas de las fuentes. Vamos, ¡escribir la ruta completa a pico y pala!

Colocando los textos

PHP dispone de una función que permite determinar las dimensiones de una caja de texto (el rectángulo imaginario que rodea el texto).

$rect= ImageTTFBBox (tamaño, angulo, fuente, $texto) donde tamaño es el tamaño de la fuente a utilizar, angulo es el ángulo de rotación del texto que tendría valor cero en el caso de orientación horizontal, fuente es el path y nombre de la fuente a a utilizar, $texto es el nombre de la variable que contiene el texto a incluir.

La variable $rect recoge un array escalar cuyos valores son las coordenadas de las cuatro esquinas de la caja de texto.

Los índices correspondientes a cada uno de los elementos de ese array son los siguientes:

� – Inferior izquierdo. Sus coordenadas son: $rect[0] y $rect[1]

� – Inferior derecho. Sus coordenadas son: $rect[2] y $rect[3]

� – Superior derecho. Sus coordenadas son: $rect[4] y $rect[5]

� – Superior izquierdo. Sus coordenadas son: $rect[6] y $rect[7]

Respecto a estas coordenadas, habremos de tener en cuenta lo siguiente:

� – Las correspondientes al vértice inferior izquierdo son siempre (0,0).

� –Los puntos situados por encima del (0,0) tienen ordenada negativa.

� –Las abscisas de los puntos situados a la izquierda del (0,0) son negativas.

Page 259: Manual PHP

Centrando textos

Aquí tienes un ejemplo donde utilizando ImageTTFBox e ImageTTFText se puede centrar un texto -tanto si es horizontal como si está girado- con relación a un punto. En este ejemplo, el punto de referencia para el centrado es (200,150) que es el centro de la imagen.

Las coordenadas de ImageTTFText, como puedes ver, están calculadas usando las coordenadas de ese punto de referencia y los valores del array generado por ImageTTFBox.

Si la variable que contiene el array generado por ImageTTFBox se llama $pepa, las coordenadas del centro del rectángulo imaginarioson $pepa[4]/2 y $pepa[5]/2.

Partiendo de esos valores, si queremos centrar el texto sobre un punto de la imagen cuyas coordenadas son (X,Y) nos basta con escribir como parámetros de la función ImageTTFText los siguientes:

� Abscisa= X - $pepa[4]/2

� Ordenada= Y - $pepa[5]/2

Este procedimiento es válido tanto para textos horizontales como para textos girados.

<?php Header("Content-type: image/png"); $im = imagecreate(400,300); $fondo=imagecolorallocate ($im, 255, 255, 210); $gris=imagecolorallocate ($im, 160, 160,160); $rojo=imagecolorallocate ($im, 255, 0, 0); $texto="El mundo del PHP"; $texto1="lleno de posibilidades"; $marco= ImageTTFBBox (40, 0, "./fuentes/arial.ttf", $texto); Imagettftext($im,40,0,200-$marco[4]/2,150-$marco[5]/2, $gris,"./fuentes/arial.ttf",$texto); $marco1= ImageTTFBBox (30, 30, "./fuentes/fuente2.ttf", $texto1); Imagettftext($im,30,30,200-$marco1[4]/2,150-$marco1[5]/2,$rojo,"./fuentes/fuente2.ttf", $texto1); Imagepng($im); imagedestroy($im); ?>

Ver ejemplo228.php Formato PNG

Ver ejemplo229.php Formato JPG

Un ejemplo resumen

Aquí tienes un ejemplo bastante completo de generación de imágenes dinámicas.

Ver ejemplo230.php Ver código fuente

Diagramas de sectores

Diagramas de sectores

Esta posibilidad gráfica de tratamiento de información estadística la proporciona la función

Page 260: Manual PHP

imagefilledarc( Xc, Yc, a, b,anguloi, angulof , $color, tipo )

dónde los parámetros son: Xc y Yc que son las coordenadas del centro de la elipse cuyo arco (o porción) tratamos de representar. Los números a y b son las longitudes (expresadas en pixels) de los semiejes horizontal y vertical de la elipse. Si ambos fueran iguales el resultado sería circular. Los parámetros anguloi y angulof son las posiciones (en grados sexagesimales) de los radios (inicial y final) que delimitan el sector que se trata de representar.

Los cero grados coinciden con el semieje horizontal positivo y el sentido del recorrido angular es el de las agujas del reloj.

El parámetro $color es la variable –ha de ser definida previamente mediante imagecolorallocate– que indica el color que ha de utilizarse en el gráfico. Por último el parámetro tipo un constante PHP que puede tomar uno de los siguientes valores: IMG_ARC_PIE,IMG_ARC_NOFILL, IMG_ARC_EDGED o IMG_ARC_CHORD

Con la primera de las constantes dibuja el sector de elipse delimitado por los radios indicados relleno con el color especificado. Mediante IMG_ARC_NOFILL únicamente dibuja la porción de arco, pero no incluye la representación de los radios en el dibujo. La opcionIMG_ARC_EDGED se comporta de forma idéntica a IMG_ARC_PIE cuando se utiliza de forma aislada aunque tiene una opción muy interesante que veremos un poco más abajo. Con IMG_ARC_CHORD el resultado es un triángulo –relleno con el color indicado– formado por los dos radios y la cuerda correspondiente al arco que delimitan.

Combinar dos constantes de tipo

Cuando en la función imagefilledarc() utilizamos como tipo una expresión tal como: IMG_ARC_NOFILL|IMG_ARC_EDGED (fíjate en el signo | que separa ambas constantes) lo que obtenemos es la representación gráfica del contorno del sector (incluye los radios que lo delimitan). Mediante esta opción -con dos llamadas a la función- tenemos la posibilidad de representar el sector con un color de relleno (usando IMG_ARC_PIE) y, luego, superponerle un contorno de distinto color. Puedes verlo en los ejemplos.

Efecto tridimensional

Tal como puedes ver en los ejemplos, resulta fácil lograr un efecto tridimensional en el dibujo de los sectores. Basta con crear un bucle que dibuje arcos sucesivos (separados verticalmente por un pixel) y posteriormente superponer un sector relleno con un color distinto.

Ejemplos de sectores

<?php $im = imagecreate (400, 400); $fondo = imagecolorallocate($im, 226, 226, 226); $col1=imagecolorallocate($im,255,255,0); $col2=imagecolorallocate($im,255,0,0); imagefilledarc($im, 200, 200, 350, 300, 20, 240, $col1, IMG_ARC_PIE); imagefilledarc($im, 200, 200, 350, 300, 10, 150, $col2, IMG_ARC_NOFILL); header('Content-type: image/png'); imagepng($im); imagedestroy($im); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

<?php $im = imagecreate (400, 400); $fondo = imagecolorallocate($im, 226, 226, 226); $col1=imagecolorallocate($im,255,255,0); $col2=imagecolorallocate($im,255,0,0); imagefilledarc($im, 200, 200, 350, 300, 20, 240, $col1, IMG_ARC_EDGED); imagefilledarc($im, 200, 200, 350, 300, 10, 150, $col2, IMG_ARC_NOFILL); header('Content-type: image/png'); imagepng($im); imagedestroy($im); ?>

Page 261: Manual PHP

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

<?php $im = imagecreate (400, 400); $fondo = imagecolorallocate($im, 226, 226, 226); $color1=imagecolorallocate($im,255,0,0); imagefilledarc ($im, 200, 200, 350, 300, 20, 240, $color1, IMG_ARC_NOFILL|IMG_ARC_EDGED); header('Content-type: image/gif'); imagegif($im); imagedestroy($im); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

<?php $im = imagecreate (400, 400); $fondo = imagecolorallocate($im, 226, 226, 226); $color1=imagecolorallocate($im,255,0,0); imagefilledarc ($im, 200, 200, 350, 300, 50, 200, $color1, IMG_ARC_CHORD); header('Content-type: image/gif'); imagegif($im); imagedestroy($im); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

<? $im = imagecreate (400, 400); $fondo = imagecolorallocate($im, 226, 226, 226); $color1=imagecolorallocate($im,200,0,0); $color2=imagecolorallocate($im,255,0,0); $color3=imagecolorallocate($im,255,255,255); for($i=200;$i<225;$i++){ imagefilledarc($im, 200, $i, 370, 270, 50, 330, $color1, IMG_ARC_NOFILL|IMG_ARC_EDGED); } imagefilledarc($im, 200, 200, 370, 270, 50, 330, $color2, IMG_ARC_EDGED); imagefilledarc($im, 200,200, 370, 270, 50, 330, $color3, IMG_ARC_NOFILL|IMG_ARC_EDGED); header('Content-type: image/gif'); imagegif($im); imagedestroy($im); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

Encuadre y redimensionado de imágenes

Lectura de imágenes externas

La visualización de imágenes no presenta ningún problema –lo hacemos habitualmente mediante etiquetas HTML– cuando se encuentran en el espacio del servidor, bien sea propio o ajeno. El problema puede surgir cuando tratemos de almacenar esas imágenes fuera del root del servidor (una forma de impedir la accesibilidad desde otras webs) y eso puede conseguirse mediante las funciones que veremos en este capítulo.

Page 262: Manual PHP

Se trata de utilizar PHP para hacer una copia de la imagen original. Para ello debemos conocer la ubicación de aquella y también sufomato. A partir de esos datos ya solo nos quedaría utilizar una de las funciones:

$copia=imagecreatefromjpeg($original) o $copia=imagecreatefrompng($original) o $copia=imagecreatefromgif($original)

que nos permitiría crear una copia de la imagen original utilizando la función adecuada al formato de aquella (jpg, png o gif). La visualización de la imagen requiere los mismos elementos de ejemplos anteriores:

� Header puede incluir como Content-type cualquiera de los valores: image/jpeg, image/png o image/gif

� imagecreate que ahora requeriría una de las opciones imagecreatefromjpeg, imagecreatefrompng ó imagecreatefromgif según sea el formato de la imagen original.

� image..($copia) que ahora requeriría una de las opciones imagejpeg($copia), imagepng($copia) ó imagegif($copia) según sea el formato establecido en Header.

En estos ejemplos puedes ver los resultados de la copia de los tres tipos de imagen.

<?php /* ruta completa hasta la imagen original. Aunque aqui usamos DOCUMENT_ROOT para establecer la ruta hasta la imagen ejemplo no sería preciso que estuviera en el root del servidor. Podría estar en cualquier directorio fuera de ese ámbito */ $original=$_SERVER['DOCUMENT_ROOT']."/php/images/caballos.jpg"; /* para conocer el formato de la imagen podemos extraer el nombre de la extensión de la imagen original que será lo que hay a partir del último "punto" de la cadena que contiene la ruta completa */ for($i=strlen($original)-1;$i>0;$i--){ if (substr($original,$i,1)=="."){ $tipo=substr($original,$i+1); break; } } /* insertamos el Header correspondiente al cualquiera de los tipos de imagen utilizables por PHP. Por ejemplo: jpeg */ Header("Content-type:image/jpeg"); /* el uso de switch puede sernos útil para elegir la función adecuada */ switch($tipo){ case "jpg": $copia=imagecreatefromjpeg($original); break; case "png": $copia=imagecreatefrompng($original); break; case "gif": $copia=imagecreatefromgif($original); break; } /* visualizamos la imagen ateniendonos al formato establecido en Header */ imagejpeg($copia); /* eliminamos la imagen de memoria */ ImageDestroy($copia); ?>

Page 263: Manual PHP

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

Redimensionar y recortar de imágenes externas

Puede parecer que esta posibilidad carece de utilidad. A fin de cuentas, con etiquetas HTML podemos asignar el ancho y el alto de una imagen. Pero enseguida veremos que esta opción no es tan trivial como parece. El objetivo es obtener una imagen con unas dimensiones determinadas y unos márgenes también establecidos a partir de una parte (o el todo) de un original.

Copia de la imagen original. Pretendemos recortar la parte marcada La imagen resultante tiene bordes en blanco y además contiene el recorte de la anterior ampliado y deformado

Lo que necesitamos es crear una imagen «en blanco» con las dimensiones elegidas y posteriormente incrustar en ella la copia de una imagen externa después de haberla deformado para adaptarla a unas nuevas dimensiones. Para lograr este propósito va a ser necesario que utilicemos estas funciones:

$dimensiones=getimagesize($original)

dónde $original es la variable que contiene el path y nombre del fichero externo que contiene la imagen y $dimensiones es un array escalar que contiene las dimensiones de la imagen analizada. El elemento del array $dimensiones[0] contiene el ancho y $dimensiones[1] el alto, ambos expresados en pixels.

$ampliacion=imagecreatetruecolor(x, y)

dónde $ampliacion es el identificador de una nueva imagen –en blanco– creada en color verdadero con dimensiones x e y

imagecopyresampled($ampliacion, $copia, Xampliacion, Yampliacion, Xcopia, Ycopia, Ax , Ay, Ox , Oy)

$ampliacion que es el identificador de la imagen destino, es decir la imagen en blanco sobre la que pretendemos insertar todo o parte de la original, $copia es el identificador de una copia de la imagen original previamente guardada en memoria, Xampliacion e Yampliacion son las coordenadas de un punto situado en la esquina superior izquierda del papel a partir del que queremos que se impresione la fotografía. Si queremos una foto a sangre pondremos 0,0 y, si quieres dejar márgenes en blanco, habrá que poner los anchos de esos márgenes (izquierdo y superior) respectivamente. Xcopia e Ycopia nos servirán para reencuadrar la foto original recortando por la izquierda y por arriba, respectivamente, los anchos que se indiquen aquí en pixels. Si no queremos recortar la imagen original ni por la izquierda ni por la derecha pondremos 0,0.

Page 264: Manual PHP

Ax y Ay indican el ancho y el alto (por este orden) que va a tener la mancha de imagen en la imagen resultante. Ten en cuenta que no podemos salirnos del papel así que esos valores sumados con los márgenes (izquierdo y superior) no podrán ser mayores que las dimensiones de la imagen ampliada.

Ox y Oy indican el ancho y el alto de la porción del original que tratamos de reproducir. Sumados con Xcopia e Ycopia no pueden exceder el tamaño de la imagen original.

Con estos parámetros la función ya se encarga de redimensionar la imagen (incluso distorsionarla, si no hay proporcionalidad entre los anchos y altos del original y del soporte. El proceso es el siguiente:

� – Hacemos un copia en memoria de la imagen original

� – Creamos una nueva imagen en blanco que será la imagen resultante

� – Insertamos en la nueva imagen el resultado de la transformación de la copia de la original

¡Cuidado! Observa que las imágenes en formato png se visualizan con deficiencias en los bordes de las áreas transparentes. Con el método que vemos a continuación ese problema se reduce considerablemente.

Lectura y redimensionado de imágenes externas

<?php /* indicamos la ruta de la imagen original */ $original=$_SERVER['DOCUMENT_ROOT']."/php/images/caballos.jpg"; /* determinamos el formato de esa imagen */ for($i=strlen($original)-1;$i>0;$i--){ if (substr($original,$i,1)=="."){ $tipo=substr($original,$i+1); break; } } /* determinamos las dimesiones de la imagen original */ $tamano=getimagesize($original); $orig_Ancho = $tamano[0]; $orig_Alto =$tamano[1]; /* vamos a tratar de obtener una imagen deformada para ello usaremos factores de ampliación distintos para para cada uno de los lados. Multiplicaremos por 2 el ancho y por 1.5 el alto */ $ampliacion_X=2; $ampliacion_Y=1.5; /* Pretendemos que la imagen resultante no tenga márgenes en blanco por tanto sus dimensiones van a ser las del original multiplicado por los factores de ampliacion */ $resultado_Ancho=$orig_Ancho*$ampliacion_X; $resultado_Alto= $orig_Alto*$ampliacion_Y; /* creamos una copia de la imagen original. Debemos elegir la funcion adecuada al tipo de aquella*/ switch($tipo){ case "jpg": $copia=imagecreatefromjpeg($original); break; case "png": $copia=imagecreatefrompng($original); break; case "gif": $copia=imagecreatefromgif($original);

Page 265: Manual PHP

break; } /* insertamos la cabecera de nuestra imagen final ampliada */ Header("Content-type:image/jpeg"); /* creamos una imagen nueva en color verdadero*/ $ampliada=imagecreatetruecolor($resultado_Ancho,$resultado_Alto); /* aplicamos un color de fondo a la nueva imagen para poder visualizar que incluye la transparencia del png y/o del gif */ if($tipo=="png" OR $tipo=="gif"){ $fondo=imagecolorAllocate($ampliada,255,255,200); imagefill($ampliada,0,0,$fondo); } /* incrustamos la imagen importada sobre la que acabamos de crear teniendo en cuenta los parámetros de la función. Los cuatro ceros se deben a que vamos a colocar toda la imagen original (sin recortes) sin dejar ningun margen en blanco) y los anchos y altos se mantienen dado que ni recortamos nada la imagen original ni vamos a dejar margen alguno en la resultante */ imagecopyresampled($ampliada,$copia,0,0,0,0, $resultado_Ancho, $resultado_Alto, $orig_Ancho,$orig_Alto); /* visualizamos la imagen resultante */ imagejpeg($ampliada); ImageDestroy(); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

Observa que –tanto en el ejemplo anterior como en el siguiente– solo hemos utilizado la extensión de la imagen original para elegir la función imagecreatefrom.... En el Header hemos puesto image/jpeg y, como es obvio, hemos utilizado la función asociada a este formato (imagejpeg). Si sustituimos ambos valores por los correspondientes a otro formato (gif, png) obtendríamos resultados similares.

Recortar y encuadrar imágenes externas

<?php /* indicamos la ruta de la imagen original */ $original=$_SERVER['DOCUMENT_ROOT']."/php/images/aviones4.jpg"; /* determinamos el formato de esa imagen */ for($i=strlen($original)-1;$i>0;$i--){ if (substr($original,$i,1)=="."){ $tipo=substr($original,$i+1); break; } } /* determinamos el tamaño de la imagen original */ $tamano=getimagesize($original); $orig_Ancho = $tamano[0]; $orig_Alto =$tamano[1]; /* asignamos a una variable el margen en blanco de la imagen igual por los cuatro lados */ $margen=15; /* establecemos los recortes para reencuadrar la imagen original */ $recorte_izq=50; $recorte_sup=80; $recorte_der=40; $recorte_inf=60; /* calculamos las dimensiones para utilizar como parámetros en la funcion imagecopyresampled ancho y alto de la imagen original después del recorte de encuadre */ $Ancho_recortado=$orig_Ancho-$recorte_izq-$recorte_der; $Alto_recortado=$orig_Alto-$recorte_sup-$recorte_inf; /* establecemos factores de ampliación iguales para evitar distorsiones */ $ampliacion_X=1.3; $ampliacion_Y=1.3;

Page 266: Manual PHP

/* determinamos las dimensiones de la imagen final teniendo en cuenta los margenes y los factores de ampliación */ $papel_Ancho=$Ancho_recortado*$ampliacion_X+ 2*$margen; $papel_Alto=$Alto_recortado*$ampliacion_Y+2*$margen; /* dimensiones del area impresa de la imagen resultante Hay que descontar los márgenes al tamaño de la imagen */ $resultado_Ancho=$papel_Ancho -2*$margen; $resultado_Alto=$papel_Alto -2*$margen; switch($tipo){ case "jpg": $copia=imagecreatefromjpeg($original); break; case "png": $copia=imagecreatefrompng($original); break; case "gif": $copia=imagecreatefromgif($original); break; } /* creamos la cabecera de la nueva imagen */ Header("Content-type:image/jpeg"); /* creamos la imagen final asignandole sus dimensiones */ $ampliada=imagecreatetruecolor($papel_Ancho,$papel_Alto); /*ponemos un color de fondo (rojo) que será con el que se visualizarán los márgenes*/ $fondo=imagecolorAllocate($ampliada,255,0,0); /* aplicamos el color de fondo */ imagefill($ampliada,0,0,$fondo); /* incrustamos la imagen */ imagecopyresampled($ampliada,$copia,$margen,$margen, $recorte_izq,$recorte_sup, $resultado_Ancho,$resultado_Alto, $Ancho_recortado,$Alto_recortado); /* visualizamos el resultado */ imagejpeg($ampliada); ImageDestroy(); ?>

Ver imágenes original y resultante

Superponer, rotar y dar transparencia a imágenes

Colores con transparencia

PHP permite crear colores con transparencia. Para ello se utiliza la función:

ImageColorAllocateAlpha($imagen, rojo, verde, azul, transparencia)

donde $imagen es el identificador de la imagen que ha sido creada previamente, rojo, verde, azul que son valores numéricos (o variables) que contienen –en una escala de 0 a 255– la intensidad luminosa de cada uno de los tres colores primarios y transparencia es un valor numérico comprendido entre 0 y 127 que indica el grado de transparencia de la tinta. El valor 0 indica opacidad total, mientras que 127 establece la transparencia total de ese color.

En el ejemplhemos incluido dos escalas de transparencias superpuestas a intervalos del 10% (desde 0 hasta 100%) transformados a la escala 0-127.

Page 267: Manual PHP

El orden de superposición –similar a las capas de otros programas gráficos– se corresponde con el orden de las instrucciones de creación. Los resultados de las últimas funciones se superponen siempre a los obtenidos como consecuencia de la ejecución de las anteriores.

<?php /* Creamos una imagen en color verdadero, le aplicamos un color de fondo (para evitar el negro por defecto) y creamos un nuevo color que utilizaremos para los bordes de rectangulos posteriores*/ Header("Content-type:image/jpeg"); $im_base=imagecreatetruecolor(610,140); $fondo=imagecolorAllocate($im_base,255,255,200); $negro=imagecolorAllocate($im_base,0,0,0); imagefill($im_base,0,0,$fondo); # definimos las componentes de un nuevo color $R=255; $G=00; $B=00; /* vamos a construir una escala de transparencias de 0 a 10 que correspondería con valores de transparencia de 0% al 100%. Crearemos un bucle que dibuje rectangulos rellenos con el color definido en la variable trans que irá aplicando al color básico los diferentes grados de transparencia y le pondremos un contorno negro para encuadrarlos*/ for($i=0;$i<=10;$i++){ $trans=ImageColorAllocateAlpha($im_base,$R,$G,$B,(int)($i*127/10)); imagefilledrectangle($im_base, 10+55*$i, 20, 50+55*$i, 80, $trans); imagerectangle($im_base, 10+55*$i, 20, 50+55*$i, 80, $negro); } #creamos un nuevo color y repetimos el proceso con nuevos rectangulos #superpuestos a los anteriores y con las transparencias en sentido opuesto # es decir, de 100 a 0% $R=0; $G=0; $B=255; for($i=0;$i<=10;$i++){ $trans=ImageColorAllocateAlpha($im_base,$R,$G,$B,127-(int)($i*127/10)); imagefilledrectangle($im_base, 10+55*$i, 60, 50+55*$i, 120, $trans); imagerectangle($im_base, 10+55*$i, 60, 50+55*$i, 120, $negro); } # visualizamos el resultado imagejpeg($im_base); ImageDestroy(); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

Transparencia en imágenes externas

También resulta posible ajustar el grado de transparencia de una imagen externa. Mediante la función

imagecopymerge($destino, $origen,Xd, Yd, Xf, Yf, Dx, Dy, opacidad)

dónde $destino es el identificador de la imagen sobre la que se va a colocar la transparencia. Como es lógico, deberá haber sido creada antes de incluir la función. $origen es el identificador de la imagen que pretendemos incluir con un determinado grado de transparencia, Xd e Yd son las coordenadas de un punto situado en la esquina superior izquierda de la imagen destino a partir del que queremos que se impresione la nueva imagen. Si queremos una imagen a sangre pondremos 0,0 y, si quieres dejar márgenes, habrá que poner los anchos de esos márgenes (izquierdo y superior) respectivamente. Xf e Yf nos servirán para reencuadrar la foto original recortandopor la izquiera y por arriba, respectivamente, los anchos en pixels que aquí se indiquen. Dx y Dy indican el ancho y el alto (por este orden) que va a tener la «mancha de imagen». Ten en cuenta que no puedes salirte del papel así que los valores anteriores sumados con los márgenes que hayas establecido no podrán ser mayores que las dimensiones que has elegido para la imagen destino. Por último, opacidad es un parámetro al que puede asignarse un valor comprendido entre 0 y 100 y representa el porcentaje de opacidad de la imagen superpuesta. Con un valor cien sería totalmente opaca y si fuera cero la transparencia sería total.

Page 268: Manual PHP

<?php # obtener la imagen $original=$_SERVER['DOCUMENT_ROOT']."/php/images/aviones4.jpg"; # buscar el formato de la imagen mediante su extensión for($i=strlen($original)-1;$i>0;$i--){ if (substr($original,$i,1)=="."){ $tipo=substr($original,$i+1); break; } } # tamaño del original extraido del array devuelto por getimagesize $tamano=getimagesize($original); $orig_Ancho = $tamano[0]; $orig_Alto =$tamano[1]; # estableceremos un margen en blanco alrededor de la imagen de 10 pixels # igual por los cuatro lados $borde=10; $Ancho=$orig_Ancho+2*$borde; $Alto=$orig_Alto+2*$borde; # creamos la imagen segun el formato original switch($tipo){ case "jpg": $importada=imagecreatefromjpeg($original); break; case "png": $importada=imagecreatefrompng($original); break; case "gif": $importada=imagecreatefromgif($original); break; } Header("Content-type:image/jpeg"); # creamos una imagen nueva, un color de fondo y la rellenamos con él $im_base=imagecreatetruecolor($Ancho,$Alto); $fondo=imagecolorAllocate($im_base,255,255,200); imagefill($im_base,0,0,$fondo); # superponemos la imagen importada posicionandola y aplicandole # una trasmparencia de 50 imagecopymerge( $im_base, $importada, $borde, $borde, 0, 0, $orig_Ancho, $orig_Alto ,50 ); imagejpeg($im_base); ImageDestroy(); ?>

Ver ejemplo .jpg Ver ejemplo .png Ver ejemplo .gif

Si observas los resultados obtenidos en el ejemplo en el que intentamos dar transparencia a una imagen en formato png podrás observar que deja bastante que desear y produce un efecto indeseado por el recorte de las zonas presuntamente transparentes.

Esta situación nos obliga a replantear la situación para prever esta circunstancia y recurrir a un truco que parece solventar ese problema. La modificación del código fuente es la incluimos aquí debajo.

Header("Content-type:image/jpeg"); $im_base=imagecreatetruecolor($Ancho,$Alto); $fondo=imagecolorAllocate($im_base,255,255,200); imagefill($im_base,0,0,$fondo); /* el truco consiste en crear una segunda imagen (im_truco)cuyas dimensiones coincidan con las de la imagen transparente que pretendemos colocar. Le asignamos como color el fondo el mismo

Page 269: Manual PHP

de la imagen destino y hacemos transparente ese color en esa imagen. Después hacemos una copia de la imagen original sobre la imagen im_truco y sustituimos en la función imagecopymerge la imagen original por la obtenida mediante esta chapucilla */ $im_truco=imagecreatetruecolor($orig_Ancho, $orig_Alto); $fondo1=imagecolorAllocate($im_truco,255,0,200); imagefill($im_truco,0,0,$fondo1); imagecolortransparent ($im_truco,$fondo1); imagecopy($im_truco, $importada, 0, 0, 0, 0, $orig_Ancho, $orig_Alto); imagecopymerge( $im_base , $im_truco, $borde , $borde , 0 , 0 ,$orig_Ancho, $orig_Alto ,60 ); imagejpeg($im_base); ImageDestroy();

Ver el nuevo resultado

Montaje de imágenes

Superponer una parte de una imagen

Mediante esta función se puede copiar sobre una imagen una parte de otra. Permite extraer porciones de imágenes con su tamaño original sin que permita ampliarlas ni reducirlas. Su sintaxis es la siguiente:

imagecopy($destino, $original, $x, $y, $X, $Y, $A, $H)

donde: $destino el identificador de la imagen destino, $original el identificador de la imagen original, $x y $y las coordenadas donde se posicionará –en la imagen destino– la esquina superior izquierda de la porción copiada, $X y $Y son los anchos de los recortes izquierdo y superior de la imagen a copiar y $A y $H el ancho y el alto del área de imagen que pretendemos copiar.

<?php /* definimos las imagenes */ $imagen_base=$_SERVER['DOCUMENT_ROOT']."/php/images/aviones1.jpg"; $imagen_superpuesta=$_SERVER['DOCUMENT_ROOT']."/php/images/aviones2.jpg"; Header("Content-type: image/jpeg"); /* creamos en memoria dos imagenes copia de las originales */ $capa_base =imagecreatefromjpeg($imagen_base); $capa_superpuesta =imagecreatefromjpeg($imagen_superpuesta); /* realizamos el montaje de la nueva imagen sobre la base */ imagecopy($capa_base,$capa_superpuesta, 30, 40, 70, 80, 195, 225); /* visualizamos la imagen */ imagejpeg($capa_base); imagedestroy(); ?>

Ver ejemplo

Rotación de imágenes

Es posible presentar imágenes rotadas por medio de la funcion:

imagerotate($imagen,angulo,$fondo)

dónde el parámetro $imagen es el identificador de la imagen a rotar, angulo es el ángulo de rotación (expresado en grados y tomado en sentido trigonométrico) y $fondo es un color de fondo asociado a la imagen a rotar que puede ser definido mediante la funciónimagecolorallocate u otra función que permita asociar colores a imágenes.

Page 270: Manual PHP

<?php # obtener la imagen $original=$_SERVER['DOCUMENT_ROOT']."/php/images/aviones3.jpg"; for($i=strlen($original)-1;$i>0;$i--){ if (substr($original,$i,1)=="."){ $tipo=substr($original,$i+1); break; } } switch($tipo){ case "jpg": $importada=imagecreatefromjpeg($original); break; case "png": $importada=imagecreatefrompng($original); break; case "gif": $importada=imagecreatefromgif($original); break; } Header("Content-type:image/jpeg"); $fondo=imagecolorallocatealpha($importada,255,255,0,40); $im_base=imagerotate($importada,30,$fondo); imagejpeg($im_base); ImageDestroy(); ?>

Ver ejemplo

Transparencia en capas rotadas

Cuando trabajamos con una imagen truecolor en la que vamos incluyendo en diferentes capas (mediante copy ó copymerge) otras imágenes –recortadas, rotadas, creadas a partir de otra imagen, etc.– la transparencia de los colores de los fondos de rotación no plantea ningún problema. Es suficiente usar la función imagecolortransparent, eso sí, aplicándola a la imagen correspondiente antes de insertarla mediante la opción copy en la imagen final.

¡Cuidado! La función imagerotate() no funciona con la versión de la librería GD que se instala con Ubuntu o Debian que es

diferente de la que utilizan otras distribuciones de PHP (la de Windows por ejemplo). La forma de resolverlo puedes encontrarla aquí Ejemplo resumen Aquí tienes un ejemplo en el que hemos utilizado superposiciones de imágenes, con giros, recortes y diferentes grados de transparencia.

Ver ejemplo resumen Ver codigo fuente

Efectos en imágenes y CAPTCHA

Aplicando filtros a imágenes

A partir de PHP 5 se han incluido funciones que permiten aplicar a las imágenes filtros modificadores que producen muchos de los efectos que hasta ahora estaban reservados al software gráfico del tipo Gimp o PhotoShop por poner dos ejemplos.

Al incluir la función:

imagefilter ( $imagen , tipo_filtro, param1, param2, ... )

Page 271: Manual PHP

Dónde $imagen es el identificador de imagen, tipo_filtro es un valor numérico entero (o una constante, en cuyo caso no deber su nombre entre comillas) y los parámetros cuyo número y valores dependen del tipo de filtro utilizado. Aquí debajo tienes una tabla que contiene los diferentes tipos de filtros disponibles así como los parámetros utilizados por cada uno de ellos.

Imagen original Imagen transformada Código fuente

Pulsando en las opciones de la parte inferior podrás visualizar aquí el código fuente de los scripts que transforman la imagen original de acuerdo con los parámetros elegidos para cada opción

Nombre de la constante Valor equivalente

Parámetros Función Aplicar

IMG_FILTER_NEGATE 0 Invierte los colores de la imagen Aplicar

IMG_FILTER_GRAYSCALE 1 Convertir a escala de grises Aplicar

IMG_FILTER_BRIGHTNESS 2 Brillo=60

Aumenta o disminuye el brillo en el intervalo 255 a -255 Aplicar

IMG_FILTER_CONTRAST 3 Contraste=10

Aumenta o disminuye el contraste en el intervalo 255 a -255 Aplicar

IMG_FILTER_COLORIZE 4

Rojo=10

Verde=10

Azul=10

Alfa=10

Coloriza Aplicar

IMG_FILTER_EDGEDETECT 5 Detectar y resalta bordes Aplicar

IMG_FILTER_EMBOSS 6 Efecto relieve Aplicar

IMG_FILTER_GAUSSIAN_BLUR 7 Desenfoque gaussiano Aplicar

IMG_FILTER_SELECTIVE_BLUR 8 Desenfoque selectivo Aplicar

IMG_FILTER_MEAN_REMOVAL 9 Aplica efecto boceto a la imagen Aplicar

IMG_FILTER_SMOOTH 10 Suavizado=60

Aplica suavizado la imagen Aplicar

IMG_FILTER_PIXELATE 11

Tamaño=5

Pixelación

avanzada Si

Pixela la imagen (Solo funciona en versiones PHP 5.3 y superiores) Aplicar

Los filtros requieren incluir los parámetros que aparecen en las respectivas cuadrículas. Su inclusión se realiza agregándolos (únicamente sus valores numéricos) a continuación del nombre o identificador del filtro y separados de este y entre ellos por una coma.

Aplicaciones sucesivas de efectos

Las diferentes opciones de filtrado pueden irse aplicando de forma sucesiva sobre una misma imagen. Un caso típico puede ser el que puedes verlo en la imagen de la parte superior si pulsas en este enlace. El efecto «sepia» se logra aplicando primero IMG_FILTER_GRAYSCALE para convertir a escala de grises y colorizando por medio de IMG_FILTER_COLORIZE. En este caso los valora aplicados a las componentes R,G,B han sido 210, 90, 30 respectivamente y el valor del cuarto parámetro (canal alfa) ha sido de 64

Page 272: Manual PHP

Obtención de valores de las componentes de color

PHP dispone de función:

$codigo_de_color=imagecolorat($imagen, x, y)

por medio de la cual la variable $codigo_de_color recogería un valor decimal resultante de convertir en decimal el color correspondiente (la cadena hexadecimal con el formato clásico RRGGBB) al pixel cuyas coordenadas horizontal y vertical, respecto a la esquina superior izquierda de la imagen son x e y de la imagen cuyo identificador es $imagen

Para la manipulación de imágenes puede resultar muy interesante conocer las componentes R,G y B de esos colores. En caso de la libreria GD cuando se trata de conocer el color de uno de los pixel de una imagen los valores R,G, B de sus componentes puede obtenerse por medio de la función:

$codigo_de_color=imagecolorat($imagen ,x,y) $array_de_colores=imagecolorsforindex($imagen ,$codigo_de_color)

Ver ejemplo Ver código fuente

Si ejecutas el ejemplo anterior podrás ver que imagecolorsforindex es un array asociativo cuyos indices son: red, green,blue y alfaque corresponden los tres primeros a las componentes de los colores primarios y el cuarto –alfa– que indicaría el grado de transparencia de ese color (en caso de que la imagen la tuviera asignada). Por lo general la información de color de un pixel de una imagen estaría conformado por 24 bits de los cuales corresponden 8 a cada uno de ellos. Cuando se agrega un canal alfa para incluir una determinada transparencia se agregan otros 8 bits para recoger este valor. En ese caso, como resulta obvio, se requieren 32 bits por cada pixel. El valor cero asignado en el ejemplo anterior al índice alfa significa que no está asignada ninguna transparencia al pixel estudiado.

Cuando se trata de extraer, sin disponer de una imagen de referencia, las componentes de color de un código de color uno los procedimientos para hacerlo requiere el uso de los operadores bit a bit de la forma que hemos tratado de desglosar paso a paso en la tabla que tienes aquí debajo.

Valor inicial Función Valor decimal

Valor hexadecimal

Valor binario Resultado

$a="#ABCDEF"

$b=str_replace('#','',$a)); ABCDEF

Elimina el carácter # de la cadena que contiene el color y determina sus valores hexadecimal, decimal y binario. Observa que el valor binario tiene 24 caracteres. Los 8 primeros son los dos dígitos hexadecimales del color rojo, los siguientes los del verde y los últimos los del azul.

$b=ABCDEF $c=hexdec($b); 11259375

$c=11259375 decbin($c);

101010111100110111101111 101010111100110111101111

$c=11259375 ($c >> 16); 171 Elimina 16 caracteres por la derecha en la cadena que contiene el valor binario. Transformamos el resultado a las distintas bases. Es la componente rojo del color

$c=11259375 base_convert(($c>>16),10,

16); AB

$c=11259375 base_convert(($c

>>16),10,2); 10101011

$c=11259375 $h=($c >> 8); 43981 Elimina 8 caracteres por la derecha en la cadena que contiene el valor binario. Transformamos el resultado a las distintas bases. Son las componente rojo yverde del color

$c=11259375 base_convert(($c >> 8),10,16);

abcd

$c=11259375 base_convert(($c >> 8),10,2);

1010101111001101 1010101111001101

Page 273: Manual PHP

$c=11259375 ($c >> 8 & 0xFF); 205 Por medio del operador &extraemos 8 caracteres por la derecha. La razón es que el valor binario de 0xFF (sería lo mismo poner el decimal 255) es 11111111 y operar & con esta cadena daría los 8 carácteres (observa que estos son 8 unos) de la derecha de la otra cadena operada. El resultado será la componente verde del color.

$c=11259375 base_convert(($c>>8 &

0xFF),10,16); CD

0xFF base_convert((0xFF),10,2); 1111111111111111

$c=11259375 base_convert(($c>>8 &

0xFF),10,2); 1111111111001101

$c=11259375 $c; 11259375

Estos son los valores iniciales. Los repetimos únicamente con el fin de facilitar la visualización de las cadenas binarias e ilustrar los procesos bit a bit

$c=11259375 base_convert($c,10,16); abcdef

$c=11259375 base_convert($c,10,2); 101010111100110111101111

$c=11259375 ($c & 0xFF); 239 El operador & compara cadenas de bits (por la derecha). Si ambos bits tienen valor 1 devuelve 1 en caso contrario devuelve cero. Por esa razón al comparar con 0xFF estamos comparando con unos y en consecuencia el resultado coincide con el valor de bit comparado. El resultado de esta comparación es la componente azul del color.

$c=11259375 base_convert(($c &

0xFF),10,16); EF

0xFF base_convert((0xFF),10,2); 11111111

$c=11259375 base_convert(($c &

0xFF),10,2); 11101111

El cálculo utilizando los operadores bit a bit de la tabla anterior no es el único posible. Es el más rápido pero es equivalente a este otro realizado por medio de la aritmética clásica.

En un sistema de numeración hexadecimal (por su condición de ordenado y por ser su base igual a 16) siempre puede convertirse el número en una forma polinómica del tipo: A*165+B*164+C*163+D*162+E*161+F*160, o también (utilizando números de dos cifras)AB*164+CD*162+EF*160. Si dividimos el polinomio anterior entre 164 obtendremos AB como cociente entero y CD*162+EF*160 como resto.

Si el resto anterior, CD*162+EF*160 lo dividimos entre 162 obtendremos como cociente entero CD y como resto EF (160=1). Los resultados de estas operaciones resultan ser las componentes de los tres colores.

Por tanto, si partimos del valor $c=11259375 del ejemplo de la tabla anterior, tendríamos que la componente roja (expresada en valor decimal) del color se obtendría mediante: (int)($c/pow(16,4))=171 sería el valor de la componente roja del color.

El valor de (int)($c%pow(16,4)/pow(16,2))=205 nos daría la componente verde y, por último, por medio de (($c%pow(16,4)) % pow(16,2))=239 sería el valor de la componente azul.

Filtros personalizados

Cualquiera de los efectos que puedan provocarse en una imagen es el fruto de modificar los valores de color de sus pixels. Este tipo de modificaciones individuales para un solo pixel pueden realizarse por medio de las funciones:

$color=imagecolorAllocate($imagen,R,G,B) o $color=imagecolorallocatealpha($imagen,R,G,B,alfa) y imagesetpixel($imagen, $x, $y, $color)

Page 274: Manual PHP

Por medio de la primera de las funciones, ya comentada en páginas anteriores, se crea un índice de color($color) para la imagen identificada por $imagen al que se asignan como valores de R, G y B valores enteros comprendidos entre 0 y 255. En el segundo de los supuestos, el parámetro alfa es un indicador de transparecia que puede tomar valores entre 0 (opaco) y 127 (transparente).

El paso siguiente es aplicar la función imagesetpixel para asignar al pixel de la imagen$imagen cuyas coordenadas son x e y el color establecido en el índice $color.

Aquí tienes un ejemplo de como utilizar esas funciones para efectuar la transformación Umbral que no es otra cosa que transformar a escala de grises y posteriormente modificar el color de los mismos asignando color rojo a aquellos cuyas componentes de color sean superiores a un valor umbral (por ejemplo 110) y color negro a aquellos que lo tengan inferior a ese valor.

<?php Header("Content-type: image/jpeg"); $im = imagecreatefromjpeg('./images/aviones4.jpg'); /* utilizamos imagesx e imagesy para calcular las dimensiones de la imagen actual. También podríamos haber utilizado los valores del array obtenido por medio de la funcion getimagesize que nos daría información de la imagen original. En este caso ambas son iguales */ $ancho=imagesx($im); $alto=imagesy($im); /* asignamos un valor entre 0 y 255 a la variable umbral dependiendo de este valor aumentarán o se reducirán las zonas blancas de la imagen */ $umbral=110; /* convierto a escala de grise grises */ imagefilter($im,IMG_FILTER_GRAYSCALE); /* creamos dos indices de color. Negro y rojo */ $color[0]=imagecolorAllocate($im,0,0,0); $color[1]=imagecolorAllocate($im,255,0,0); /* este doble bucle nos permite recorrer cada uno de los pixels de la imagen */ for ($i=0;$i<$ancho;$i++){ for ($j=0;$j<$alto;$j++){ /* leemos el color actual. Como se trata de grises las componentes R,G y B de cada uno de los pixels van a ser iguales, nos bastará por tanto conocer uno cualquiera. en este caso extraemos el valor correspondiente al azul */ $color_actual =imagecolorat($im,$i,$j) & 0xFF; /* si la componente gris es mayor que el umbral pinto en negro en caso contrario en blanco */ if($color_actual>$umbral){ imagesetPixel($im,$i,$j,$color[1]); }else{ imagesetPixel($im,$i,$j,$color[0]); } } } /* visualizamos la imagen */ imagejpeg($im); imagedestroy($im); ?>

Ver ejemplo Ver código fuente

Creación de una imagen CAPTCHA

CAPTCHA es el acrónimo de Completely Automated Public Turing test to tell Computers and Humans Apart que podría ser traducido como Prueba de Turing pública y automática para diferenciar a máquinas y humanos. Desde el punto de vista gráfico

Page 275: Manual PHP

ha de ser una imagen el la que se visualizarán carácteres alfanuméricos con elementos de distorsión y deformación que impidan (o al menos dificulten) la lectura por dispositivos electrónicos OCR.

Para dificultar ese proceso existen multitud de aplicaciones, clases PHP y recursos de toda índoles. Nosotros vamos a intentar crear una imagen con poca nitidez, con caracteres aleatorios de diferentes tamaños, posiciones, tranparencias y ángulos de rotación y posteriormente le añadiremos una deformación vertical desplazando los pixeles en esta dirección por medio de funciones sinusoidales y, posteriormente haremos lo mismo en sentido horizontal. Veamos paso a paso el proceso.

Fondo con ruido añadido

Ver código fuente

Inserción de caracteres aleatorios

Ver código fuente

Deformación vertical

Ver código fuente

Deformación horizontal

Ver código fuente

Detectar bordes

Page 276: Manual PHP

Ver código fuente

Filtros de convolución

Filtros de convolución

Una imagen digital no es otra cosa que una especie de hoja de papel cuadriculado en el que cada una de las cuadrículas tiene asignado un color concreto especificado por medio de su código de color tal como hemos visto en la página anterior. Cada uno de esos elementos que hemos llamado cuadrículas es un pixel.

Una matriz de convolución no es otra cosa que una matriz cuadrada formada por nueve cuadrículas (matriz de dimensión 3 x 3) que contiene valores numéricos enteros.

Aplicar un filtro de convolución no es otra que superponer simbólicamente la matriz de convolución sobre la cuadrícula que conforma la imagen (El area sombreada de amarillo en la imagen sería delimitaría sobre ella la zona de superposición). El proceso aritmético de aplicación del filtro consistiría en multiplicar el valor de cada cuadrícula de la matriz de convolución por el situado debajo suyo en esa superposición imaginaria. Los resultados de todos esos productos se sumarían obteniendo un valor que sustituiría al valor del pixel centraldel area marcada (en rojo en la imagen).

El valor anterior puede ser corregido por medio de un factor multiplicador del resultado y también por medio un parámetro de desplazamiento (offset) que permitiría incrementar (o reducir, ya que admite valores negativos) en un valor entero el resultado anterior.

En este esquema tienes el proceso de cálculo del nuevo valor para el pixel central que es el marcado con fondo rojo.

La convolución en PHP

Para aplicar este filtro en PHP es necesario empezar creando una matriz de convolución. Dado que esa matriz ha de ser obligatoriamente de 3 x 3 la matriz de convolución puede definirse de alguna de estas formas:

Page 277: Manual PHP

$matriz_de_convolución = array(array(a11,a12,a13), array(a21,a22,a23), array(a31,a32,a33))

o también definiendo independientemente cada fila:

$fila1 = array(a11,a12,a13) $fila2 = array(a21,a22,a23) $fila3 = array(a31,a32,a33)

y formando luego la matriz con las tres filas. Es decir:

$matriz_de_convolución = array($fila1, $fila2,$fila3)

Una vez disponemos de la matriz de convolución podremos utilizar la función:

imageconvolution ( $imagen , $matriz_de_convolución, multiplicador, desplazamiento)

o, si preferimos definir la matriz de convolución en la propia llamada a la función, escribiendo:

imageconvolution ( $imagen , array(array(a11,a12,a13), array(a21,a22,a23), array(a31,a32,a33)), multiplicador, desplazamiento)

Dónde $imagen es el identificador de imagen, matriz_de_convolucion es el array ya comentado, multiplicador será el factor multiplicador del resultado de la convolución y desplazamiento un valor entero que se sumaría a los resultados anteriores. El resultado sería la nueva imagen modificada después de haber aplicado la matriz de convolución a cada uno de los pixels que la conforman.

Dado que los resultados no son facilmente predecibles hemos incluido simulador que te permitirá ir modificando valores y observar los efectos producidos por esos cambios.

Imagen original Imagen transformada Código fuente de la imagen

Si modificas las opciones de la parte inferior podrás visualizar aquí el código fuente de los scripts que transforman la imagen original de acuerdo con los parámetros elegidos para cada opción

Matriz de convolución

0

0

0

0

1

0

0

0

0

Divisor: 1

Desplazamiento: 0

A modo de ejemplo, desde estos enlaces podrás visualizar los efectos indicados y a la vez las matrices de convolución utilizadas.

Sin convolución Enfocar Desenfocar Repujado

Page 278: Manual PHP

Gestión de directorios

Funciones con directorios

PHP dispone de funciones que permiten obtener información sobre los contenidos de los directorios del servidor. Algunas de estas funciones son las siguientes:

Pseudo-objeto dir

Mediante la expresión:

$objeto = dir (path)

dónde path es la ruta absoluta (../dir/subdir/subsubdir) o relativa (./subdir ) hasta el directorio del que vamos a obtener información, recogemos en el pseudo objeto $objeto la información sobre el directorio en cuestión de una forma un tanto peculiar ya que $objeto se comporta como objeto y, como tal objeto, posee las propiedades y métodos que indicamos:

$objeto->handle

Devuelve una cadena con la descripción del identificador del recurso.

$objeto->path

Devuelve la ruta del directorio especificado.

$objeto->read()

Realiza una lectura secuencial de los nombres y extensiones de los ficheros contenidos en el directorio especificado.

$objeto->rewind()

Posiciona el puntero en la posición inicial.

$objeto->close()

Cierra el identificador de directorio.

Este es un ejemplo de utilización de los métodos de éste pseudo-objeto. Observa que las propiedades handle y path se invocan sin utilizar () mientras que tanto el método read() como rewind() o close() sí requieren esos paréntesis.

<?php $directorio = dir("./images"); # en el caso de los objetos la manera de invocar # uno de sus métodos y/o funciones requiere una sintaxis # especifica con la que vera a lo largo de este ejemplo: # es $objeto->metodo que equivale a la tradicional llamada # a una variable en la forma $variable. echo "Handle: ".$directorio->handle."<br>\n"; echo "Path: ".$directorio->path."<br>\n"; while($fichero=$directorio->read()) { echo $fichero."<br>\n"; } $directorio->rewind(); echo "nuevo listado del directorio despues de rebobinar<br>" ; while($fichero=$directorio->read()) { echo $fichero."<br>"; } $directorio->close(); ?>

Page 279: Manual PHP

ejemplo264.php

Otras funciones sobre directorios

Este nuevo grupo de funciones informativas requiere un sintaxis que recuerda la lectura de ficheros. Hay que comenzar abriendo el directorio y una vez se hayan realizado las consultas hay que cerrarlo según se indica en las funciones siguientes.

$directorio= opendir (path)

Recoge en la variable $directorio un identificador que permitirá utilizar las restantes funciones.

$z= readdir ($directorio)

Hace una lectura secuencial del directorio indicado por el identificador $directorio. A medida que efectúa la lectura secuencial el puntero de lectura va desplazándose al fichero que sigue al último leído.

rewinddir($directorio1)

Rebobina haciendo que el puntero apunte al primer fichero del directorio. closedir($directorio1)

Cierra el identificador del directorio.

<?php #abrimos el identificador de directorio $directorio = opendir("./images"); #leemos el primer fichero que será "." (recuerda la estructura de los directorios de MS-DOS) $fichero=readdir($directorio); echo $fichero,"<br>"; #leemos el fichero siguiente que será ".." (recuerda la estructura de los directorios de MS-DOS) $fichero=readdir($directorio); echo $fichero,"<br>"; #leemos el fichero siguiente (el primer fichero "real") $fichero=readdir($directorio); echo $fichero,"<br>"; #rebobinamos, enviando el puntero al primer fichero rewinddir($directorio); echo "Lista de TODOS los ficheros usando un bucle while<br>"; #leemos todos los ficheros while($fichero=readdir($directorio)) { echo $fichero."<br>"; } closedir($directorio); ?>

ejemplo265.php

Page 280: Manual PHP

Protección de directorios

Errores y redireccionamiento

Configuración del servidor Apache

Para poder proteger de directorios –mediante un fichero llamado .htaccess– es necesario realizar algunas modificaciones en la configuración de Apache. Abriremos nuestro fichero httpd.conf y buscaremos las líneas en las que aparece: AllowOverride None y reemplazaremos None por All. Guardaremos los cambios en httpd.conf y reiniciaremos el servidor.

En caso de que estemos usando como sistema operativo Ubuntu haremos esa modificación ejecutando desde consola:

sudo gedit /etc/apache2/sites-enabled/000-default

El fichero .htaccess

Las misiones más importantes que puede realizar este fichero son: redireccionar y restringir accesos. Veamos el primero de los casos. Los mensajes de error más frecuentes al intentar acceder a páginas web –y sus causas– son los siguientes:

� Error 401 – El subdirectorio está protegido por número IP o por password y el intento de acceder a él no ha tenido éxito.

� Error 403 – El acceso al documento solicitado está prohibido.

� Error 404 – El documento solicitado no ha sido hallado.

� Error 500 – Error del servidor. Usualmente este error se da cuando se ha intentado ejecutar de forma incorrecta un CGI, o bien debido a problemas en el servidor.

Los errores de los tipos 403 y 404 suelen producirse en la mayoría de las ocasiones por direcciones incorrectas y –aparte de causar un pésimo efecto– suelen provocar el abandono de la visita.

Un fichero .htaccess con este contenido:

ErrorDocument 401 pagX ErrorDocument 403 pagY ErrorDocument 404 pagZ

donde pagX, pagY y pagZ sean direcciones (completas) de las páginas a las que deseamos redireccionar el navegador, conseguiría que esos errores llevaran al visitante a la página que nosotros deseáramos.

Intenta acceder a una dirección inexistente del tipo: http://localhost/noexisto.php y verás que aparece un mensaje de error advirtiendo que no existe ninguna página con ese nombre. Algo lógico, porque realmente no existe.

Bastará con que abras tu editor –no utilices el block de notas porque te dará muchísimos problemas en este caso– escribas una línea como esta

ErrorDocument 404 http://www.rinconastur.net

y guardes el documento en el raiz de tu servidor con el nombre .htaccess (aunque te parezca extraño, no lleva nada delante del punto).

Reinicia el servidor, prueba nuevamente a acceder de nuevo a http://localhost/noexisto.php y observarás que ahora no dice página no encontrada sino que se abre la página principal del este dominio.

Edita de nuevo el fichero .htaccess y añádele las siguientes líneas:

ErrorDocument 401 http://www.rinconastur.net ErrorDocument 403 http://www.rinconastur.net

Page 281: Manual PHP

guarda los cambios, reinicia tu servidor y comprueba que cada vez que intentes acceder a una dirección inexistente te ocurre lo mismo que al pulsar aquí: http://rinconastur.net/noexisto.php. Como es lógico puedes redireccionar cada uno de los errores a una página distinta y, desde luego, no necesariamente a la mía. ;-)

Herencias

El archivo .htaccess provoca herencia. Eso significa que las especificaciones incluidas en un directorio –sean restricciones o redirecciones– son efectivas en todos los subdirectorios que contiene, incluso en el caso de que esos subdirectorios tengan su propio.htaccess y que en él se establezcan condiciones distintas a las anteriores.

Al crear un fichero .htaccess las condiciones establecidas afectarán a todos sus subdirectorios. Por esa razón cuando pretendemos que desde subdirectorios distintos se redireccione a páginas distintas tendremos que incluir un .htaccess en cada uno de ellos y no poner ningún .htaccess en el directorio raíz que los contenga.

Protección de directorios

Son muchas las posibilidades que ofrece .htaccess a la hora de restringir el acceso a un directorio determinado. Entre otras opciones, se puede denegar el acceso a todos los usuarios; denegar el acceso con excepciones, autorizar a todos (equivale a no restringir), autorizar con excepciones o requerir clave y contraseña.

Lo que hemos denominado excepciones también permite una serie de alternativas tales como: una IP determinada, un rango de IP's, nombres de dominio, etcétera. Sólo comentaremos la forma de protección de directorios mediante claves de usuario y contraseña.

Restricción de acceso a usuarios no autorizados

Este tipo de protección requiere crear un fichero de claves y contraseñas y configurar de forma adecuada .htaccess

Contraseñas sin encriptar

Para crear un fichero de claves y contraseñas basta con abrir el block de notas y escribir la clave seguida de dos puntos (:) y a continuación escribir la contraseña. Podemos poner tantas como deseemos sin más limitaciones que escribir cada bloque clave:contraseñaen una línea distinta. Este puede ser un ejemplo:

pepe:Pepito pepa:Pepita

Podemos guardar este fichero en el sitio que deseemos sin que sea necesario que pertenezca al root del servidor que es la opción más aconsejable. Podemos ponerle cualquier nombre sin que importe que tenga extensión o no la tenga.

Contraseñas encriptadas

Apache dispone de una utilidad que permite la creación de ficheros de claves con contraseñas encriptadas. Se trata de un programa llamado htpasswd.exe que está en el subdirectorio bin del servidor.

Para crear un nuevo fichero el procedimiento sería el siguiente

– En la línea de comandos: Inicio->Ejecutar

Page 282: Manual PHP

debemos escribir:

path htpasswd -c nombre y path del fichero de claves usuario si se trata de un sistema operativo Linux Ubuntu habría que escribir en la consola htpasswd -c nombre y path del fichero de claves usuario

En nuestra configuración y para crear un fichero con la palabra clave pepe escribiríamos:

htpasswd -c /home/rinconas/seguridad/misclaves.txt pepe

y aparecería una ventana de MS-DOS en la que deberemos escribir la password para ese usuario.

Añadir usuarios a un fichero de contraseñas encriptadas

Porcederíamos de la misma forma. Volveríamos a ejecutar htpasswd con la nueva clave pero sin utilizar -c.

¡Cuidado! El modificador -c destruiría el fichero anterior, si existiera y crearía uno nuevo. El proceso ahora sería:

htpasswd /home/rinconas/seguridad/misclaves.txt luis

Page 283: Manual PHP

Habríamos creado así nuestro fichero con claves encriptadas. Si pretendiéramos visualizarlo nos aparecería lo siguiente:

pepe:$apr1$EC4.....$7Z3.p2tv2QpzrZbo4bI2j0 luis:$apr1$SU4.....$iU8a.YTo.ZvYyRggDAvTC.

¡Cuidado! Bajo Linux, antes de añadir el usuario luis tendríamos que asegurarnos de que el fichero misclaves.txt tenga permisos para poder efectuar modificaciones en su contenido.

Configuración de .htaccess

Para el caso específico de nuestro servidor Apache, el fichero .htaccess ha de contener:

� AuthType Basic No permite modificación e indica el tipo de autentificación requerida.

� AuthName "nuestro texto " El texto que escribamos aquí aparecerá como mensaje en la ventana en la que nos pedirá la clave

� AuthUserFile "path" Entre esas comillas debes escribir el nombre del fichero de contraseñas especificando su path completo.

� require valid-user Este texto indica que para acceder se requiere un usuario válido.

Con nuestra configuración de Apache no es necesario especificar en .htaccess la forma de encriptación de contraseñas. El propio servidor interpreta el contenido del fichero y aplica u omite los criterios de encriptación.

A riesgo de parecerte pesados tenemos que volver a insistir que no todos los hosting tienen habilitada esta opción, pero además hemos de hacer mención a otro detalle muy importante. La configuración que hemos comentado no es válida para todos los servidores. Según como esté configurado el servidor, la versión del software que utilice, etcétera no sería extraño que se necesitara esta otra sintaxis: AuthType Basic, AuthName "Texto", AuthTextUserFile fichero, required valid-user, AuthTextCrypt On/Off u otras similares que pueden inducirnos al error. Lo mejor, en caso de servidores ajenos, es consultar al administrador del sistema sobre estos aspectos y recabarle detalles sobre la sintaxis específica de su configuración.

Un ejemplo de .htaccess

Supongamos que tenemos un directorio llamado protegido en cualquier parte del servidor (por ejemplo dentro de htdocs). La forma de protegerlo sería crear un fichero con nombre .htaccess con un contenido como este:

AuthType Basic AuthName "Pruebas de autentificación" AuthUserFile "c:/Apache/seguridad/misclaves.txt" require valid-user

y guardarlo en el directorio que hayas especificado en lugar de lo escrito en rojo en el recuadro anterior. Al acceder al directorioprotegido aparecerá una ventana como esta:

Page 284: Manual PHP

y si al cabo de tres intentos no escribimos la clave y contraseña adecuadas se producirá un Error 401.

Una vez que tengamos modificada la configuración del servidor Apache estaremos en condiciones de poder proteger directorios –restringir o limitar el acceso a ellos– y también podremos redirigir peticiones a páginas predeterminadas en casos concretos. Este control ha de realizarse mediante ficheros que tienen por nombre .htaccess (el primer caracter es el punto), que no pueden llevar ningún tipo de extensión y que pueden ser incluidos en cualquier directorio o subdirectorio del espacio del servidor.

¡Cuidado! Existen otras opciones de protección de acceso a directorios. Algunas guardan la lista de usuarios autorizados en una base de datos y otras requieren que el usuario disponga de un certificado digital. En el epígrafe Servidores seguros tienes información al respecto.

Las cookies

Las cookies

De igual modo que ocurría con la función mail, no todos los hosting tienen habilitada la opción de envío de cookies. Como sabes, lascookies son pequeños ficheros que se escriben en el ordenador del cliente.

Si utilizas Internet Explorer podrás ver que se almacenan como ficheros de texto en un directorio llamado Archivos temporales de Internet y que su nombre es de este tipo: cookie:xxx@nombre[z].txt donde xxx suele ser el nombre que figura en el registro de Windows como nombre del equipo (el que se pone al instalar Windows); nombre suele ser el nombre del directorio de servidor desde el que se envió la cookie y el número z suele ser el ordinal del números de accesos a la página que envía la cookie.

Firefox trata las cookies de forma distinta. Las almacena en un único fichero llamado cookies.sqlite que suele encontrarse en:C:\Documents and Settings\Usuario\Datos de programa\Mozilla\Firefox\Profiles\xxxx.default. En cuanto a Google Chrome también utiliza un único fichero llamado cookies que suele estar en un directorio como este: C:\Documents and Settings\Usuario\Configuración local\Datos de programa\Google\Chrome\User Data\Default

En el caso de usar Ubuntu como sistema operativo el fichero cookies.sqlite de Firefox puedes encontrarlo en un directorio cuya ruta será similar a esta: /home/(nombre_de_usuario)/.mozilla/firefox/xxxx.default

Enviar y leer cookies

La instrucción para el envío de cookies debe insertarse al principio de la página y antes de cualquier etiqueta HTML o línea en blanco. Esto es muy importante, no lo olvides. La sintaxis es la siguiente:

setcookie(nombre, valor, expira)

Page 285: Manual PHP

donde nombre es una cadena que contiene el nombre de la variable que recoge el valor de la cookie, valor es el valor, numérico o cadena, que se asignará a la variable anterior y expira indica la fecha de caducidad de la cookie que suele expresarse usando el valor devuelto por la función time() (hora actual) más un número que representa los segundos que han de transcurrir desde «ahora» hasta que lacookie expire.

Los valores contenidos en las cookies pueden ser leídos por el servidor a partir de variables predefinidas $_COOKIE que, como todas las de este tipo, es un array asociativo. El índice de este array será el nombre asignado a la cookie. Veamos este ejemplo:

<?php /* setcookie escribe el contenido de la cookie en el ordenador del cliente */ setcookie("cookie1","Soy la cookie1",time()+3600); # escribe el valor leido en la cookie @print "Esta es la <i>galletita</i>: ".$_COOKIE['cookie1']; ?>

ejemplo266.php

Al ejecutar por primera vez este script (o al hacerlo después de limpiar o eliminar historial de navegación en el cliente) solo leeremos el texto «Esta es la galletita :» sin nada detrás. La explicación es la siguiente: las instrucciones PHP se ejecutan en el servidor antes de enviar la página al cliente. Eso significa que, al ejecutar por primera vez, se inserta la orden de escritura y se comprueba el valor de la variable, que aun no ha sido creada y por ello aparece en blanco. Será en la actualización –ya se habría producido un envío al navegador y ya se habría escrito la cookie– cuando si se leerá el valor anterior

Dado el carácter superglobal de $_COOKIE también se le puede aplicar la función extract tal como hemos comentado en al tratar el tema de los formularios. En este ejemplo podemos ver esa opción

<?php extract($_COOKIE); @print "Esta es la galletita leida desde extract: ".$cookie1; ?>

ejemplo267.php

Como es lógico los valores de las cookies pueden asignarse también por medio de variables tal como puedes comprobar en estos ejemplos:

<?php $variable="Ahora me han incluido desde una variable"; setcookie("cookie2",$variable,time()+3600); @print "Esta es la nueva cookie: ".$_COOKIE['cookie2']; ?>

ejemplo268.php

<?php print "Estoy leyendo la cookie2: ".$_COOKIE['cookie2']; extract($_COOKIE); print "<br>Sigo leyendo lo mismo a través del resultado de extract: ".$cookie2; ?>

ejemplo269.php

Una cookie definida como array

Page 286: Manual PHP

Una cookie puede contener varios valores agrupados en un array. Para ello basta con incluir en setcookie el nombre y el índice de un array utilizando un setcookie por cada uno de los elementos del array tal como puedes ver en el ejemplo. Para leer las cookies enviadas de esta forma bastará con leer el array devuelto.

¡Cuidado! Fíjate en la sintaxis de setcookie del ejemplo. Los nombres de los índices del array no se escriben entre comillas. Observa también que al leer los datos de la cookie hemos de tratarla como un array bidimensional. El primer índice el nombre de la cookie (en nombre del array que contiene la información) y el segundo será el nombre concreto de cada uno de los índices tal como han sido creados. <?php $valores=Array("Verde","Verano","Rolls-Royce","Millonario"); # a diferencia de lo que ocurre al definir elementos de array asociativos # en este caso los indices asociativos (color, estación, etc.) no van # entre comillas setcookie("cookie3[color]",$valores[0],time()+3600); setcookie("cookie3[estacion]",$valores[1],time()+3600); setcookie("cookie3[coche]",$valores[2],time()+3600); setcookie("cookie3[finanzas]",$valores[3],time()+3600); # la variable superglobal $_COOKIE['cookie3'] contiene un array, por ello # la lectura de sus valores debe hacers considerando que se trata de un # array bidimensional @print "<br>El color viene en $_COOKIE['cookie3']['color'] y es: ".$_COOKIE['cookie3']['color']; @print "<br>La estación $_COOKIE['cookie3']['estacion'] y es: ".$_COOKIE['cookie3']['estacion']; @print "<br>La finanzas $_COOKIE['cookie3']['finanzas'] están: ".$_COOKIE['cookie3']['finanzas']; @print "<br>El coche es $_COOKIE['cookie3']['coche'] y es: ".$_COOKIE['cookie3']['coche']; print "<br> también podemos leer la cookie mediante un bucle como este:<br>"; if (isset($_COOKIE['cookie3']) ) { while( list( $indice, $valor) = each($_COOKIE['cookie3']) ) { echo "$indice == $valor\n"; } } ?>

ejemplo270.php

Un contador de visitas como aplicación práctica

<?php @$numero=$_COOKIE['visitante']; $numero+=1; setcookie("visitante",$numero,time()+86400); if($numero==1){print "Es la $numero ª vez que visitas esta página";} if($numero>1){print "Es la $numero ª vez que visitas esta página";} ?>

ejemplo271.php

Cambio del color de fondo

Page 287: Manual PHP

<?php @$numero=$_COOKIE['color']; $numero+=1; setcookie("color",$numero,time()+86400); extract($_COOKIE); $colores=array('red','blue','yellow','green','black'); /* el fondo de la página tendrá uno de estos cinco colores de fondo dependiendo del resto de la division entre 5 del número de visitas efectuadas hasta el momento */ @print "<body bgcolor='".$colores[$color%5]."'></body>"; ?>

ejemplo272.php

Sesiones

¿Qué son las sesiones?

Suponemos que habrás estado alguna vez en un hotel y que recuerdas que al inscribirnos como huéspedes nos facilitan una tarjetita identificativa que teóricamente habríamos de presentar a la hora de solicitar cualquier servicio del hotel (cafetería, restaurante, etc.).

Al registrarnos en ese hotel estaremos iniciando una sesión (estancia) y al recibir la tarjeta identificativa se nos estará facilitando un identificador de sesión, que tiene validez temporal, ya que expirará en la fecha de salida indicada en ella y que recoge una serie de datos (nuestros datos personales, nuestro período de estancia, el precio y los servicios contratados, etcétera).

Imaginemos ahora que vamos al restaurante. Pueden ocurrir dos cosas: que decidamos efectuar el pago directamente, o que pidamos que el importe de la factura se incluya en nuestra cuenta. En el segundo de los casos, se reiniciará la sesión y se registrará una nueva variable de sesión –el importe del servicio– al firmar la nota de cargo del mismo.

El responsable del restaurante deberá guardar en algún sitio (probablemente en la oficina de recepción) esa nota de cargo –una variable de sesión– de forma temporal ya que una vez abonado su importe o en el momento que abandonemos el hotel expirará la sesión y dejará de tener utilidad. Se requiere de forma imprescindible un directorio temporal en el que almacenar las variables de sesión.

En PHP la sesiones funcionan de forma muy similar y de esa similitud surge la necesidad de habilitar un directorio temporal antes de utilizar sesiones. Si visualizamos el fichero info.php y buscamos la directiva session.save_path veremos que, en el caso de Windows tiene el valor C:/ServidoresLocales/tmp como consecuencia de la creación del directorio tmp y de la modificación que hicimos en la línea 1471 dephp.ini cuando detallábamos el proceso de configuración de php (puedes verlo aquí).

En el caso de la instalación sobre Ubuntu esa configuración se hace de forma automática. Al observar info.php podrías ver que:session.save_path="/var/lib/php5"

La posibilidad de propagación de las sesiones y con ellas la información guardada en las variables de sesión ($_SESSION) son instrumentos de enorme utilidad en la gestión de de páginas web.

Funciones de sesión

Para la gestión de sesiones se utilizan estas funciones:

session_start()

Crea una sesión o continúa con la actual. En el segundo caso el identificador de sesión debe ser tranferido por medio de una variable GET o a través de una cookie.

session_name()

Page 288: Manual PHP

Recoge el nombre de la sesión. Si no se asigna uno de forma explícita utiliza como nombre de sesión el contenido de la directivasession.name del fichero php.ini. Por defecto ese nombre suele ser PHPSESSID, pero no está de más comprobarlo mirando el valor actual de la directiva session.name en info.php.

session_name('nombre')

Esta variante de la función anterior permite asignar un nuevo nombre a la sesión actual. Debemos tener en cuenta que si cambiamos de página y queremos mantener el mismo identificador (conservar la sesión anterior) esta función debe ser incluida con el mismo nombreen la nueva página y, además, ha de ser insertada antes de que la función session_start() se invocada para iniciar la sesión.

session_cache_limiter()

El limitador de caché controla las cabeceras HTTP enviadas al cliente. Estas cabeceras determinan las reglas mediante las que se habilita la opción de que los contenidos de las páginas puedan ser guardados en la caché local del cliente o se impida tal almacenamiento.

En este último modo –no caché– cada petición de página requeriría una nueva llamada al servidor, lo cual tiene –como todo en la vida– ventajas e inconvenientes. Entre las ventajas está la garantía de que en cada acceso estamos viendo la versión actualizada de la página, cosa que podría no ocurrir de otro modo. El inconveniente es que requiere una nueva petición que puede significar un tiempo de espera, mientras el servidor produce la respuesta. Esta opción viene configurada por defecto en las directivas de configuración del php.inicomo nocache.

Para evitar –sea cual fuera la configuración de php.ini– el almacenamiento de las páginas en la caché del cliente hemos de utilizar:

session_cache_limiter ('nocache,private')

Igual que ocurría con session_name, si utilizamos está función debemos escribirla antes que session_name y –también igual que en aquel caso– deberemos repetirla en cada uno de los documentos. Por tanto, el orden de escritura de estas instrucciones sería el siguiente:

<?php session_cache_limiter(); session_name('nombre'); session_start(); ..... ?>

Es muy importante mantener ese orden y que este bloque de instrucciones sea el primer elemento de la página –antes de cualquier otra etiqueta– y que no haya líneas en blanco ni antes de la etiqueta <?php ni entre ella y las llamadas a estas funciones.

Propagación de las sesiones

La verdadera utilidad de las sesiones estriba en su propagación, es decir, la posibilidad que tanto el identificador de sesión como losvalores de las variables de sesión –luego hablaremos de estas variables– puedan ir pasando de una página a otra sin necesidad de recurrir al uso de formularios. Para entendernos, se trata de dar validez y utilizar la misma tarjeta para movernos por las diferentes secciones delhotel que hemos utilizado como ejemplo.

La forma habitual de propagar las sesiones es a través de cookies, pero como quiera que el usuario tiene la posibilidad de desactivar la opción (no aceptar cookies) PHP dispone una opción alternativa que permite la propagación a través de la URL aún en el caso de que lascookies estén desactivadas.

Caso de que el cliente tenga activada la opción aceptar cookies

Si queremos que las sesiones se propaguen únicamente en el caso de que esté activada la opción aceptar cookies en el navegador bastará con hacer la llamada a la nueva página siguiendo el método tradicional, es decir: <A href="pagxx.php"> y que esa nueva página contenga sin que lo preceda ninguna línea en blanco el script siguiente:

Page 289: Manual PHP

<? session_cache_limiter(); session_name('nombre'); session_start(); ..... ?>

donde no es imprescindible incluir session_cache_limiter pero cuando se incluye ha de ir como primera línea del script. Consession_name ocurre algo similar. Si la sesión fue iniciada con un nombre distinto al que le asigna por defecto PHP, debemos escribirsession_name('nombre'), y además, nombre debe tener el mismo valor que en la página precedente.

Si no se asigna ningún nombre tomará el valor PHPSESSID –nombre por defecto– en todas las páginas y no será necesaria la instrucción session_name.

Caso de cookies deshabilitadas

Para garantizar la propagación de las sesiones –aún cuando el navegador esté configurado para no aceptar cookies– tendremos quepasar el identificador de sesión junto con las llamadas a las páginas siguientes. Para ello se requiere la siguiente sintaxis:

<A href="pagxx.php?<?php print echo session_name()."=".session_id():?>"

Con esta sintaxis, después de escribir el ? (recordemos que es la forma de indicar que añadimos variables y valores para que sean transferidos junto con la petición de la página) estaremos pidiendo a PHP que escriba como nombre de variable el nombre de la sesión y como valor de esa variable, después de insertar el signo igual, el identificador de sesión.

Es evidente que la utilización de esta opción nos permite asegurar que las sesiones y sus variables podrán ser usadas sin riesgos de que una configuración inadecuada por parte del cliente produzca resultados imprevisibles.

Algunos ejemplos

Aquí tenemos un ejemplo de como iniciar una sesión y también podremos comprobar como se propaga al llamar a la misma página si está activada la opción aceptar cookies. Si ejecutamos reiteradamente el script –pulsando en volver a llamar esta página– podremos ver que no se modifica el valor del identificador de sesión.

Si bloqueamos las cookies (en este enlace está descrito el procedimiento para hacerlo) podremos comprobar que ahora la sesión no se propaga y que cada vez que volvemos a llamar a la página nos aparece un nuevo valor en el identificador de sesión, es decir, se crea una nueva sesión.

<?php session_start(); #pedimos que escriba el identificador único y el nombre de la sesión echo session_id(),"<br>"; echo session_name(),"<br>"; ?> <A Href="ejemplo273.php">Volver a llamar estallamar esta página</A>

ejemplo273.php

Ahora haremos una modificación importante en el script. Al incluir en la llamada a la página el nombre y el identificador de sesiónestamos transfiriendo –mediante el método GET– el valor de esa variable. De esta forma, la propagación de la sesión estará asegurada sea cual fuere la configuración del navegador del cliente.

Podemos comprobar –activando/desactivando cookies– que en las sucesivas llamadas a la página se mantiene el identificador de sesión.

<?php session_start(); #pedimos que escriba el identificador único y el nombre de la sesión echo session_id(),"<br>"; echo session_name(),"<br>";

Page 290: Manual PHP

?> <A Href="ejemplo275.php?<?php echo session_name()."=".session_id();?>">Volver a esta página</a>

ejemplo275.php

Sesiones con nombre propio

En los ejemplos siguientes se crean y propagan sesiones con nombre propio sin que ello altere las condiciones de respuesta con las opciones aceptar /no aceptar cookies. También hemos incluido session_cache_limiter con las restricciones de nocache con lo cual el navegador no guardará las páginas de ejemplo en la caché. Si cambiáramos los parámetros (nocache, private por public) podríamos comprobar si se almacena en la caché del navegador la página web devuelta por el servidor.

<?php session_cache_limiter('nocache,private'); session_name('leocadia'); session_start(); #pedimos que escriba el identificador único echo session_id(),"<br>"; echo session_name(),"<br>"; ?> <A Href="ejemplo276.php">Volver a llamar esta página</A>

ejemplo276.php

<?php session_cache_limiter('nocache,private'); session_name('leocadia'); session_start(); #pedimos que escriba el identificador único echo session_id(),"<br>"; echo session_name(),"<br>"; ?> <A Href="ejemplo278.php?<?php echo session_name()."=".session_id()?>">Volver a esta página</A>

ejemplo278.php

Las cookies de sesión y sus parámetros

Para el caso de que el navegador del cliente tenga activada la opción de aceptar cookies PHP dispone de una función que permiteleer los parámetros de esa cookie. Es la siguiente:

session_get_cookie_params() que devuelve un array asociativo con los siguientes índices: � lifetime: Indica el tiempo de duración de la duración de la cookie

� path: Indica la ruta -en el ordenador del cliente- en la que se almacenan los datos de la sesión

� domain :Indica el dominio de procedencia de la cookie.

� secure :Indica si la cookie solo puede ser enviada a través de conexiones seguras (1) o si no considera esa posibilidad (0).

Los valores por defecto para estos parámetros se pueden establecer en la configuración del fichero php.ini. Si visualizamos info.phppodremos ver –en las directivas session.cookie_xxx nuestros valor por defecto– y observaremos que session.cookie_lifetime tiene valor 0, razón por la cual si –con la opción aceptar cookies activada– ejecutamos cualquiera de los script anteriores y miramos el directorio Temporal Internet Files, no encontraremos ninguna de estas cookies, dado que su plazo de expiración es cero.

<?php session_name('mi_sesion'); session_start();

Page 291: Manual PHP

echo session_id(),"<br>"; echo session_name(),"<br>"; # recogemos en la variable $a el array con los datos de la sesión $a=session_get_cookie_params(); foreach($a as $c=>$v){ echo $c,"--->",$v,"<br>"; } ?> <A Href="ejemplo279.php">Volver a llamar esta página</a>

ejemplo279.php

<?php session_name('mi_sesion'); session_start(); echo session_id(),"<br>"; echo session_name(),"<br>"; # recogemos en la variable $a el array con los datos de la sesión $a=session_get_cookie_params(); foreach($a as $c=>$v){ echo $c,"--->",$v,"<br>"; } ?> <A Href="ejemplo281.php?<?php echo session_name()."=".session_id()?>">Volver a esta página</A>

ejemplo281.php

Las configuraciones de session.cookie pueden cambiarse sin necesidad de modificar el fichero php.ini. Para ello disponemos de la función:

session_set_cookie_params(duración ,'path','dominio',segura)

que permite configurar esos parámetros de forma temporal –unicamente para la página en la que está insertado– y que como en los casos anteriores puede configurarse sólo para el caso de que el navegador acepte cookies como para las situaciones en la que esa opción esté deshabilitada.

En estos ejemplos escribirá –siempre que esté configurada la opción aceptar cookies en el navegador del cliente– una cookie que tendrá una caducidad de DIEZ minutos (el valor 10 de session_set_cookie_params). Esa cookie se guardará en el mismo directorio (/) donde se guardan las páginas web visitadas (el famoso C:\WINDOWS\Temporary Internet Files, en la configuración por defecto de IE), pero..., eso solo ocurrirá si –tal como ves en el ejemplo– pones en el parámetro dominio elnombre real del dominio donde está alojada la web. Si el nombre de dominio no coincide con que alberga la página –razonable criterio de seguridad– no se guardará la cookie.

<?php session_set_cookie_params (10,"/","localhost", 0); session_name('mi_sesion'); session_start(); echo session_id(),"<br>"; echo session_name(),"<br>"; $a=session_get_cookie_params(); foreach($a as $c=>$v){ echo $c,"--->",$v,"<br>"; } ?> <A Href="ejemplo282.php">Volver a llamar esta página</A>

ejemplo282.php

<?php session_set_cookie_params (10,"/","localhost", 0);

Page 292: Manual PHP

session_name('mi_sesion'); session_start(); echo session_id(),"<br>"; echo session_name(),"<br>"; $a=session_get_cookie_params(); foreach($a as $c=>$v){ echo $c,"--->",$v,"<br>"; } ?> <A Href="ejemplo284.php?<?php echo session_name()."=".session_id()?>">Volver a esta página</A>

ejemplo284.php

Manejo de variables de sesión

Las funciones más importantes para el manejo de variables de sesión son las siguientes:

$_SESSION['variable']=valor

es una de las formas de definir una variable de sesión. El índice variable debe contener –entre comillas– el nombre que pretendamos asignarle a esa variable de sesión y valor sería el valor asignado a esa variable. Si ya existiera le reasignaría el nuevo valor.

unset($_SESSION);

La función unset destruye las variables contenidas en el paréntesis. En este caso, al contener el array $_SESSION destruiría todas las variables contenidas en él.

unset($_SESSION['variable']);

Es similar a la anterior. En este caso solo sería destruida el elemento del array cuyo índice sea var.

isset($_SESSION['variable']);

La función isset –no es específica del tratamiento de sesiones– devuelve un valor booleano (UNO ó NUL) según que exista o no exista la variable contenida en el paréntesis. De hecho, se comporta con las variables de sesión de forma idéntica a como lo haría con cualquier otro tipo de variable.

<?php # iniciamos la sesión session_start(); # visualizamos el identificador de sesión print "Este es el identificador de sesion: ".session_id()."<br>"; # registramos una variable de sesión asignandole un nombre $_SESSION['variable1']=""; #asignamos un valor a esa variable de sesión $_SESSION['variable1']="Filiberto Gómez"; /* registramos una nueva variable de sesión asignandole directamente un valor */ $_SESSION['variable2']="Otro filiberto, este Pérez"; #comprobamos la existencia de la variables de sesión @print "Mi_variable1 esta registrada: ".isset($_SESSION['variable1'])."<br>"; #leemos el contenido de esa variable @print "Su valor es: ".$_SESSION['variable1']."<br>"; #comprobamos la existencia de la otra variable y la visualizamos @print "Mi variable2 esta registrada :".isset($_SESSION['variable2'])."<br>"; print $_SESSION['variable2']."<br>"; #destruimos la variable1 unset($_SESSION['variable1']); @print "La variable1 ha sido destruida:".isset($_SESSION['variable1'])."<br>";

Page 293: Manual PHP

@print $_SESSION['variable1']."<br>"; #destruimos todas las variables restantes unset($_SESSION); #comprobamos que han sido destruidas @print "La variable1 ya estaba vacia: ".isset($_SESSION['variable1'])."<br>"; @print $_SESSION['variable1']."<br>"; @print "También ha sido destruida la variable2: ".$_SESSION['variable2']."<br>"; @print $_SESSION['variable2']."<br>"; ?>

ejemplo285.php

Propagación de sesiones

Los tres scripts siguientes son un ejemplo del uso de sesiones para la propagación de sesiones. Funcionan bajo cualquier forma de register_globals y también en el caso en que las cookies estuvieran desactivadas en el navegador del cliente

<?php /* recuerda que entre <?php y la primera línea no puede haber líneas en blanco ni tampoco puede haberla encima de <? aunque como en este caso, si admite líneas de comentario pero no líneas en blanco */ # deactivamos la opcion de que las páginas puedan guardarse # en la cache del navegador del cliente session_cache_limiter('nocache,private'); # le asignamos un nombre a la sesión # aunque lo habitual sería dejar el nombre por defecto # que le asigna la configuración de php.ini session_name('pruebas'); # iniciamos la sesion session_start(); # creamos variables de sesion y les asignamos valores $_SESSION['valor1']=25; $_SESSION['valor2']="Ambrosio de Morales"; $_SESSION['variable3']="Una prueba más"; /* cerramos el script e insertamos un enlace a otra página y propagamos la sesión incluyendo en la llamada el nombre de la session y su identificador En esta página no se visualizaría nada. Solo el enlace */ ?> <A Href="ejemplo287.php?<?php echo session_name()."=".session_id()?>">Propagar la sesion</A>

ejemplo286.php

<?php /* pese a que la sesion viene de la página anterior tenemos que poner nuevamente session_cache_limiter ya que esta instruccion no se conserva solo es válida para la página en la que esta definida También tenemos que poner en session_name el mismo nombre de la página anterior, de no hacerlo PHP entendería que se trata de iniciar una sesion distinta Por ultimo también debemos iniciar la sesión es obligatorio iniciarla */ session_cache_limiter('nocache,private'); session_name('pruebas');

Page 294: Manual PHP

session_start(); /* comprobaremos que la sesion se ha propagados visualizando el array asociativo $_SESSION que contiene todas la variables de Sesion */ foreach($_SESSION as $indice=>$valor){ print("Variable: ".$indice." Valor: ".$valor."<br>"); } # modificamos los valores de las variables de sesion # de igual forma que si fueran variables de cualquier otro tipo $_SESSION['valor1']+=87; $_SESSION['valor2'] .=" bonito nombre"; # destruimos la tercera variable unset($_SESSION['variable3']); # propagamos la sesion a la página siguiente # con identico proceso al del script anterior ?> <A Href="ejemplo288.php?<?php echo session_name()."=".session_id()?>">Propagar la sesion</A>

<?php # identicos comentarios a los anteriores session_cache_limiter('nocache,private'); session_name('pruebas'); session_start(); # este bucle nos confirmará que se han propagado # los nuevos valores y que la tercera variable ha sido destruida foreach($_SESSION as $indice=>$valor){ print("Variable: ".$indice." Valor: ".$valor."<br>"); } ?>

Clases y objetos (I)

Clases y objetos

Aunque PHP 5 no es un lenguaje orientado a objetos, sí tiene recursos que permiten definir clases y construir objetos. El modelo de objetos de PHP 5 ha sido reescrito en su práctica totalidad y presenta sustanciales diferencias respecto a las versiones anteriores. El uso declases y objetos (POO – Programación Orientada a Objetos) no añade ninguna funcionalidad nueva a las posibilidades de PHP. Su verdadera utilidad es la de hacer la programación de otra manera, con un código más legible y reutilizable.

Las clases

Una clase no es otra cosa que una especie de plantilla dentro de la cual se pueden definir una serie de variables llamadaspropiedades –que pueden contener valores predefinidos– y un conjunto de funciones denominadas métodos que pueden ser invocadasdesde cualquier parte del documento por un objeto o instancia de esa clase.

La sintaxis que permite definir una clase es siguiente:

class nombre { .... ... definición de variables y/o propiedades.... .... .. constructor (opcional)...

Page 295: Manual PHP

....

.. destructor (opcional)...

....

.. definición de métodos o funciones ...

.... }

Vayamos por partes. La palabra reservada class es obligatoria y ha de ir seguida de un nombre mediante el que será identificada la clase. La definición de la clase comienza con un llave de apertura ({) y acaba con una llave de cierre (}). Entre ambas llaves podemos incluir sus variables o propiedades y también sus métodos o funciones.

A las variables o propiedades pueden serles asignados valores, aunque no es imprescindible hacerlo. Los métodos o funcionestienen una sintaxis muy similar a la utilizada en la programación estructurada.

Entre los métodos o funciones susceptibles de ser incluidos en una clase existen dos bastante especiales. Son los llamadosconstructor y destructor. Ninguno de ellos tiene carácter obligatorio. Su peculiaridad estriba en que se ejecutan de forma automática en determinadas circunstancias. Hablaremos de ellas un poco más adelante.

Definición de las propiedades

Para definir una variable o propiedad en una clase es obligatorio anteponer a su nombre una de estas tres palabras reservadas:public, private o protected. Por compabilidad con versiones anteriores de PHP si se usa la palabra var será interpretada como un alias depublic. El nombre de la variable utiliza la sintaxis habitual de PHP y en el caso de que tratemos de asignarle un valor, bastará con poner detrás del nombre el signo = seguido de ese valor que en el caso de ser una cadena tienen que ir entre comillas.

A modo de ejemplo podemos decir que public $pepe="Jose" es una sintaxis válida, pero que $pepe="Jose" no lo es dado que le falta la palabra reservada (opción de visibilidad) que obligatoriamente debe llevar delante de su nombre y que de no ser incluida provocaría un error.

Lo que se conoce como encapsulamiento es uno de los pilares básicos de la Programación Orientada a Objetos. Permite establecer las condiciones y/o restricciones de visibilidad de los diferentes elementos (propiedades y métodos) de una clase. Cuando al definir una variable o propiedad anteponemos a su nombre la palabra public estamos estableciendo ese carácter público de visibilidad que significa que sus valores pueden ser vistos e incluso modificados desde cualquier parte del script en que sean invocadas.

Por el contrario, si establecemos la visibilidad como private (privada) estamos encapsulando la información de forma que sus valores sólo son accesibles a través de métodos o propiedades incluidos en la propia clase. La tercera de las posibilidades, protected (protegida), flexibiliza un poco la condición private al permitir que el método o propiedad sea ser visible desde la propia clase y desde sus clases extendidas (las trataremos un poco más adelante cuando nos refiramos a la herencia).

Funciones o métodos de clase

Los métodos o funciones definidas en las clases que tienen una sintaxis casi idéntica al resto de las funciones PHP

function nombre( ){ ... instrucciones.... .... }

Las diferencias más sustanciales respecto a las funciones de la programación estructurada son:

� – Los métodos siguen los mismos criterios de encapsulamiento que hemos comentado al hablar de las propiedades. Se establecen anteponiendo a la palabra function una de las tres palabras public, private o protected aunque en este caso si se omite no se genera error sino que el método es considerado como public.

� – Siempre que desde un método se invoque una variable o propiedad definida en la misma clase ha de hacerse mediante la sintaxis: $this ->propiedad.

� – Los métodos de la propia clase se invocan de esta forma: $this->nombre_de_la_funcion(). Como es lógico dentro del paréntesis pueden incluirse los parámetros o argumentos que sean necesarios.

Page 296: Manual PHP

Prestemos mucha atención. El $ va siempre delante de la palabra this y solo se escribe una vez y en esa posición. El nombre de la variable (que va siempre después de -> no lleva el $.

Este es un ejemplo de una clase muy sencilla. Como ves el nombre de la clase es MiClase. Como es lógico, si tratas de ejecutar el ejemplo no obtendrás ningún resultado. Será necesario que un objeto utilice esta clase.

<?php class MiClase{ public $factor1=7; // esta es pública private $factor2=8; // variable privada protected $factor3=3; // variable protegida function calcula($a=3,$b=5){ // al no indicar visibilidad será public return $this->factor1-$b^2+$this->factor2*$a-$this->factor3; } } ?>

ejemplo299.php

Los objetos

Las clases son solo plantillas y sus métodos no serán ejecutados en tanto no exista un objeto que requiera su aplicación. Dicho en forma coloquial, una clase es como una sartén. En tanto no tengamos algo que freir carece de utilidad alguna. Su razón de ser son los objetos susceptibles de se cocinados por ella. Lo mismo ocurre con las clases cuya auténtica razón de ser es servir para la creación y manejo de sus objetos.

Creación y destrucción de objetos

Para crear un nuevo objeto (instancia de la clase) que utilice una clase determinada debemos usar la siguiente sintaxis:

$objeto = new clase

donde objeto es una palabra cualquiera con la que identificar el objeto (como si se tratara de una variable), new es una palabra reservada obligatoria y clase es el nombre de una clase que puede estar escrita en el mismo documento o en un fichero externo. Si la clase está escrita en un documento distinto del que contiene el script que crea el objeto habremos de utilizar include o require para incluirla.

¡Cuidado! Fíjate que por el momento no hemos puesto los () requeridos para invocar las funcionesdetrás del nombre de la clase.

Cuando se trata de eliminar un objeto (liberar el área de memoria que contiene toda la información relativa a este) podemos utilizar la siguiente sintaxis:

$objeto = null o unset(objeto )

si bien es cierto que se destruyen de forma automática una vez termina la ejecución del script que los crea y utiliza.

Utilización de los objetos

Una vez creado un objeto ya se podrán utilizar (si su visibilidad lo permite) y modificar sus propiedades y también las funciones o métodos de su clase. Los métodos se invocan mediante la siguiente sintaxis:

$objeto->funcion() o $objeto->funcion(p,q) o $objeto->funcion(p)

Page 297: Manual PHP

dónde $objeto es el objeto creado en la instrucción anterior, el -> es obligatorio, funcion es el nombre de uno de los métodos o funciones definidos en la clase invocada y donde los () son obligatorios y además –como ocurría en las demás funciones PHP– puede contener valores, variables, etcétera separadas por comas.

Las propiedades (siempre que su visibilidad lo permita) son accesibles mediante:

$objeto->propiedad o $objeto->propiedad=valor

En el primer caso estaríamos leyendo el valor de esa propiedad y en el segundo le estaríamos asignando el incluido después del signo igual.

<?php /* incluimos el fichero que contiene la clase que pretendemos utilizar */ include('ejemplo299.php'); /* creamos un objeto utilizando el nombre exacto de la clase*/ $MiObjeto1=new MiClase; //no lleva paréntesis /* leeremos los valores de las propiedades */ print "<br />Valor por defecto de la propiedad factor1: "; /* solo podremos leer desde aquí la propiedad publica factor1 Las propiedades privadas y protegidas solo son accesibles desde la propia clase y aquí estamos haciendo desde uno objeto que la instancia */ print $MiObjeto1->factor1; print "<br />Aplicación del método calcula "; print "<br />No pasamos argumentos. Por tanto calcula con 'a'=3 y 'b'=5: "; print $MiObjeto1->calcula(); print "<br />Pasamos 15 como argumento por tanto calcula con 'a'=15 y 'b'=5: "; print $MiObjeto1->calcula(15); print "<br />Pasamos 125 y -98 como argumentos por tanto calcula con 'a'=125 y 'b'=98: "; print $MiObjeto1->calcula(125,-98); /* solo podemos modificar desde aquí la propiedad factor1. Las otras dos por su carácter privado y restringido solo podrían ser modificadas desde dentro de la propia clase o de una clase extendida */ print "<br />Modificamos el valor de la propiedad factor1: "; $MiObjeto1->factor1=196; print$MiObjeto1->factor1; print "<br />Cálculo con los nuevos valores por defecto: "; print $MiObjeto1->calcula(); print "<br />Cálculo con nuevos valores por defecto pero asignando a 'a' el valor 25: "; print $MiObjeto1->calcula(25); print "<br />Cálculo con nuevos valores por defecto con 'a'=15 y 'b'=-8: "; print$MiObjeto1->calcula(15,-8); /* creamos un nuevo objeto y comprobamos que la modificación de la propiedad factor1 no afectará al nuevo objeto (la modificación se hizo en el objeto anterior no en la clase) */ $MiObjeto2=new MiClase; print "<br />Cálculo con valores por defecto del nuevo objeto: "; print $MiObjeto2->calcula(); print "<br />Cálculo con valores por defecto del objeto anterior: "; print $MiObjeto1->calcula(); /* destruimos el primero de los objetos aunque no sea necesario hacerlo. Al finalizar el script se destruiría de forma automática */ $MiObjeto1=null; ?>

ejemplo300.php

En este otro ejemplo trataremos de ver el comportamiento de los métodos y propiedades de carácter no público.

Page 298: Manual PHP

<?php /* crearemos una clase con los diferentes tipos de variables y métodos */ class Coche{ /* las propiedades han de tener especificada su visibilidad */ public $potencia="135 C.V."; public $tipo_iva="33"; private $precio="24535 euros"; protected $modelo="HF345"; /* un método publico ya que no especificamos visibilidad */ function precio_final(){ return (int)($this->precio)*(1+$this->tipo_iva/100); } /* un método privado al que solo podremos acceder desde la propia clase*/ private function precio_amigo($descuento=15){ return $this->precio_final()*(1-$descuento/100); } /* un metodo publico que nos lleva a otro privado. La funcion siguiente es accesible por su condición de pública. Ella puede acceder a las funciones privadas de la propia clase. Asi que le pedimos que "se cuele" por "la puerta trasera" en el método precio_amigo y nos devuelva el resultado */ function puerta_trasera($porcentaje){ return $this->precio_amigo($porcentaje); } /* un metodo publico que accede a una propiedad privada. No podemos cambiar precios directamente porque están en una variable privada. Pero esta función pública si tiene acceso a cualquier propiedad o método de su clase. La utilizamos para cambiar el precio */ function cambia_precios($precio){ $this->precio=$precio; } } /*Hasta aquí la clase. Ahora toca instanciar objetos de la clase Coche */ $MiNuevoCoche= new Coche; /* visualizamos sus propiedades publicas */ print "<br>La potencia es: ".$MiNuevoCoche->potencia; print "<br>El tipo de IVA es: ".$MiNuevoCoche->tipo_iva; print "<br>No puedo conocer el modelo. Es una propiedad absurdamente protegida"; print "<br>Tampoco puedo acceder al precio. Es una propiedad privada"; print "<br>Cambio el precio: a 100 euros"; $MiNuevoCoche->cambia_precios('100 euros'); print "<br>Compruebo el precio final: ".$MiNuevoCoche->precio_final(); print "<br>El precio de amigo (25% descuento)es: ".$MiNuevoCoche->puerta_trasera(25); /* instanciemos un nuevo objeto. Al nuevo cliente no le afectarán las modificaciones anteriores */ $TuNuevoCoche= new Coche; print "<br>Para el nuevo objeto el precio final: ".$TuNuevoCoche->precio_final(); ?>

ejemplo298.php

Constructores y destructores

Un constructor es una función que se ejecuta de forma automática cada vez que se instancia un nuevo objeto. Para que PHP considere a una función como tal ha de estar definida en la propia clase y además debe tener por nombre la palabra reservada __constructque tal como puedes ver comienza por «dos guiones bajos».

Cuando está definido, el constructor la instancia del nuevo objeto puede hacerse de una de estas formas:

$objeto=new nombre_clase;

Page 299: Manual PHP

No difiere en nada de la comentada anteriormente. El constructor usuará como valores de las variables que necesite utilizar los definidos en la propia función __construct y/o los valores de la propiedades establecidas en la propia clase.

$objeto=new nombre_clase (valor1, valor2, ...)

En este caso se incluyen, dentro del paréntesis, los valores que deben asignarse a una o varias de las variables que requieran ser utilizadas por la función __construct. Cuando se le pasan valores la función se ejecuta sin tomar en consideración los asignados por defecto y cuando se le pasan sólo parte de esos valores utiliza los valores recibidos y para los no asignados en la llamada utiliza los valores delconstructor.

Un destructor es también una función que se ejecuta de forma automática cada vez que se destruye objeto. Esa función ha de estar definida en la propia clase con el nombre __destruct (también comienza por «dos guiones bajos»). Hemos de tener en cuenta que esta función no destruye el objeto. Simplemente se ejecuta cuando se produce esa destrucción. Podríamos decir que es algo así como un timbre de alarma. El timbre no provoca el incendio. Lo único que hace es sonar cuando se produce uno.

Los objetos van a dejar de ser necesarios cuando se finalice la ejecución de un script. Al llegar a ese punto será cuando, de forma autómatica, se libere la memoria usada para contenerlos y como consecuencia de ello se destruyan. Al producirse ese evento (liberación de memoria) será cuando se ejecute la secuencia de instrucciones contenidas en la función __destruct.

También podríamos destruir un objeto en cualquier punto del proceso por medio de $objeto= null o unset($objeto) tal como ya hemos comentado. El objeto $objeto sería eliminado de la memoria y como consecuencia de esa destrucción se ejecutaría de forma automática la función __destruct. Aquí tienes un ejemplo de una clase que incluye un constructor y un destructor.

<?php class Operador1{ public $factor1=7; // esta es pública private $factor2=8; // variable privada protected $factor3=3; // variable protegida function __construct($a=0,$b=0){ print "El constructor de MiObjeto".$a." ha fabricado esta fecha: "; print date("j-n-Y",(time()+($this->factor1+$b)*24*3600)); print "<br />"; } function opera($a=3,$b=46){ return $this->factor1-$b^2+$this->factor2*$a-$this->factor3; } function __destruct(){ print "O se acaba de destruir un objeto o me han llamado (soy __destruct)<br />"; } } ?>

ejemplo301.php

Si intentas ejecutar el ejemplo anterior no obtendrás ningún resultado. Sólo es una clase. En este otro ejemplo instanciaremos esa clase y crearemos algunos objetos para tratar de comprobar el funcionamiento del constructor y el destructor.

<?php /* incluimos el fichero que contiene la clase que pretendemos utilizar */ include('ejemplo301.php'); /* creamos vaios objeto utilizando la clase que acabamos de incluir*/ $MiObjeto0=new Operador1; //no lleva paréntesis y no incluye ningun valor $MiObjeto1=new Operador1(1); //solo lleva el primero de los parámetros del constructor $MiObjeto2=new Operador1(2,8); //incluye dos parámetros $MiObjeto3=new Operador1(3,98); /* llamo a la función destruct que pese a su nombre no destruye nada. Se llama así porque se ejecuta automáticamente cuando un objeto deja de estar referenciado por una variable y se autodestruye. */ $MiObjeto0->__destruct();

Page 300: Manual PHP

print "El destructor no destruyó. El objeto mantiene sus propiedades Este es factor 1: "; print $MiObjeto0->factor1."<br />"; print " Cálculos en MiObjeto1. Empezamos por valores por defecto: "; print $MiObjeto1->opera(); print "<br />Cálculo con valores por defecto pero asignando a 'a' el valor 25: "; print$MiObjeto1->opera(25); print "<br />Cálculo con 'a'=15 y 'b'=-8: "; print$MiObjeto1->opera(15,-8); print "<br />Modificamos el valor de la propiedad factor1 en el objeto 1: "; $MiObjeto1->factor1=196; /* vamos a destruir MiObjeto2 y comprobaremos como al hacerlo se dispara automaticamente la funcion __destruct */ print "<br>Me van a borrar ahora mismo: "; $MiObjeto2 = null; print $MiObjeto1->factor1; print "<br />Cálculo con los nuevos valores por defecto: "; print $MiObjeto1->opera(); print "<br />Cálculo en MiObjeto3 pero asignando a 'a' el valor 25 al objeto 3: "; print $MiObjeto3->opera(25); print "<br />Cálculo con nuevos valores por defecto con 'a'=15 y 'b'=-8 al objeto 4: "; print $MiObjeto3->opera(15,-8); print "<br />Vamos a acabar el script. Lo objetos se autodestruirán y aparecerán los mensajes alusivos a eso.<br />"; print "Quedan tres objetos y tres autodestrucciones. Cada una de ellas disparará la función __destruct<br />"; ?>

ejemplo302.php

Clases extendidas y clases finales

La herencia es otra de las razones de ser de la Programación Orientada a Objetos. En PHP también tenemos la posibilidad de crearclases extendidas o clases derivadas de una clase padre. Su característica más importante es que pueden heredar todas las propiedades y métodos de esta y, además, tienen la posibilidad de agregar otros nuevos y/o modificar o redefinir los heredados (polimorfismo).

Sintaxis de las clases extendidas

Para definir una clase clases extendida se requiere la siguiente sintaxis:

class hija extends padre { .... ... definición o redefinición de variables (opcional).... .... .. constructores (opcional)... .... .. definición o redefinición de funciones (opcional)... .... }

dónde hija es el nombre de la nueva clase (la extendida), padre es el nombre de la clase padre y extends es la palabra reservada que indica a PHP que se trata de una clase extendida.

Se puede impedir que una clase pueda extenderse. Para ello basta con anteponer a la palabra class la palabra final. Si escribimos:

final class nieta extends hija {.....}

Page 301: Manual PHP

estaremos imposibilitando que puedan definirse clases extendidas de la clase nieta. Las restricciones de PHP 5 en lo relacionado con las herencias son las que puedes ver en este gráfico:

Este es un ejemplo de clase extendida de una de las anteriores. Si intentas ejecutar el script te dará un mensaje de error advirtiendo de que no encuentra la clase padre. En este caso es irrelevante dado que el documento solo tiene la finalidad de escribir la clase extendida.

<?php /* esta clase no podra ser extendida y es hija de la llamada Operador1. Heredará de ella sus métodos y sus propiedades. Podrá modificarlos y también incluir los propios */ final class Operador2 extends Operador1{ /* modificamos esta propiedad cambiando su valor */ public $factor1=297; // esta es pública /* añadimos una nueva propiedad */ public $factor4=-67; /* Este es el constructor de la clase extendida. Sustituiría al de la clase padre */ function __construct(){ print "Soy el constructor extendido y punto.<br>"; } /* redefinimos la función opera. Dará otros resultados */ function opera($a=3,$b=46){ /* utilizaremos solo factor1 y factor3 porque son los únicos visibles desde esta clase. factor1 por su condición de público y factor3 porque su condición de protegido le hace también visible desde la clase extendida */ return $this->factor1-$a^2+$this->factor3*$b+$this->factor3; } /* omitimos la función __destruct pero podremos usar la definida en la clase padre */ function nueva(){ print "Soy el resultado de una nueva función. <br />"; } } ?>

ejemplo303.php

Page 302: Manual PHP

Aquí tienes un ejemplo de utilización de algunas de las posibilidades de la clase extendida anterior. Ahora ya incluimos ambas clases (padre y extendida) y no tendremos mensajes de error al ejecutar el script.

<?php /* hemos de incluir los ficheros que contienen ambas clases */ include('ejemplo301.php'); include('ejemplo303.php'); /* crearemos un objeto instanciando la clase padre y otro instanciando la extendida*/ $Objeto_de_clase_padre=new Operador1; /* este primer objeto no va a tomar en cuenta nada de lo contenido en la clase extendida*/ $Objeto_de_clase_extendida=new Operador2; /*este segundo objeto ya va a utilizar el constructor de la clase extendida. Al ejecutar el script podrán verse las diferencias */ print "<br>Este el valor del factor1 en el objeto creado a partir de la clase padre: "; print $Objeto_de_clase_padre->factor1; print "<br>Este el valor del factor1 en el objeto creado a partir de la clase extendida: "; print $Objeto_de_clase_extendida->factor1; /* la función (método) nueva solamente puede ser usada en objetos creados mediante la clase extendida ya que para la clase padre no está definida. */ print "<br>Soy el resultado de la funcion nueva() aplicada al objeto de la clase extendida: "; $Objeto_de_clase_extendida->nueva(); /* la función (método) opera es distinta según el objeto pertenezca a una u otra clase. */ print "<br>Soy el resultado de la funcion opera() aplicada al objeto de la clase padre: "; print $Objeto_de_clase_padre->opera(); print "<br>Soy el resultado de la funcion opera() aplicada al objeto de la clase extendida: "; print $Objeto_de_clase_extendida->opera(); print "<br>Esta es la última instrucción del script. Lo que leas de aquí en adelante "; print "será obra de los destructores que se activarán al destruirse los dos objetos<br>"; ?>

ejemplo304.php

Bibliotecas de clases

Una de las ventajas más importantes de las clases es la posibilidad de reutilización de sus scripts tanto si se trata de rutinas propias o de desarrollos de terceros. Bastaría con disponer del fichero que contiene la clase correspondiente conocer sus métodos públicos (los únicos accesibles de la forma que lo hemos hecho en los ejemplos anteriores) y utilizarlos con la sintaxis que hemos venido usando en los ejemplos anteriores.

Existen algunos sitios en la red en los que pueden obtener una gran cantidad de materiales de este tipo. Uno de los más populares puedes encontrarlo en la dirección: phpclasses.org. Además existen otros sitios dónde se pueden obtener algunas clases muy interesantes.

Cuando tratemos lo relativo a ficheros en formato PDF veremos el enorme abanico de posibilidades que nos ofrece la clase TCPDF. Por el momento, para intentar ilustrar un poco la utilización de clases de terceros, incluimos un ejemplo que permite el envío de mensajes de correo con ficheros adjuntos (similares a los que hemos podido ver al tratar el tema de mensajes de correo) utilizando la clase phpmailer.

Pese a que el enlace de descarga incluye abundante material y documentación solo hemos extraido de él los dos ficheros que necesitamos para el ejemplo: class.phpmailer.php y class.smtp.php que podemos utilizar de la siguiente forma:

<?php /* evito con esta opción init_set evitamos queaparezca un mensaje de advertencia relativo a una función utilizada en la clase y que está deprecated */

Page 303: Manual PHP

ini_set('display_errors','Off'); /* incluimos el fichero que contiene la clase llamada PHPMailer */ require("class.phpmailer.php"); /* creamos un nuevo objeto (mi_mensaje) instanciando la clase PHPMailer*/ $mi_mensaje = new PHPMailer(); $mi_mensaje->IsSMTP(); // este metodo publico especifica el tipo de servidor a utilizar (SMTP) /* asignamos valores a una serie de propiedades (variables) de la clase */ $mi_mensaje->Host = "127.0.0.1"; //modificamos la propiedad Host asignandole la IP del servidor $mi_mensaje->From = "[email protected]"; // Correo del remitente asignado a al propiedad From $mi_mensaje->FromName = "Juan Mis Pruebas"; // nombre del remitente a la propiedad FromName $mi_mensaje->AddAddress("[email protected]", "Perico Mis Pruebas"); // un destinatario $mi_mensaje->AddAddress("[email protected]"); // otro destinatario el nombre es opcional $mi_mensaje->AddReplyTo("[email protected]", "Juan Mis Pruebas"); //dirección de respuesta $mi_mensaje->WordWrap = 50; // longitud de la línea del mensaje $mi_mensaje->Subject = "Prueba con adjuntos usando la clase phpmailer"; //Asunto $mi_mensaje->Body = "Este es un mensaje de prueba con ficheros adjuntos que usa la clase phpmailer"; //cuerpo $mi_mensaje->AltBody = "Este es un mensaje de prueba con ficheros adjuntos que usa la clase phpmailer"; //cuerpo cuando no utiliza HTML /* aplicamos al objeto los métodos (funciones) de la clase necesarios para nuestro propósito */ $mi_mensaje->IsHTML(true); // enviar mensaje con formato html $mi_mensaje->AddAttachment("apachito.zip"); // añadir adjunto $mi_mensaje->AddAttachment("casa08.jpg", "leocadio.jpg"); // adjunto con nombre /* aplicamos al objeto el método Send y comprobamos si tod se ha realizado correctamente */ if(!$mi_mensaje->Send()){ echo "El mensaje no ha podido enviarse. <br />"; echo "Error al enviar el mensaje: " . $mi_mensaje->ErrorInfo; exit; }else{ echo "El mensaje ha sido enviado"; } ?>

¡Cuidado! No olvides que para ejecutar este script es imprescindible que tengas activo tu servidor de correo.

Como habrás podido ver, la única dificultad que nos plantea el uso de esta clase es conocer los métodos y propiedades que incluye y la utilidad de los mismos. Ese tipo de información suele venir incluida como comentarios dentro de la propia clase y/o en la documentación relativa a la misma.

Uso de la clase phpmailer mediante el SMTP de gmail

Para enviar mensajes de correo externos utilizando el SMTP de Gmail sólo es necesario disponer de una cuenta de usuario en Gmail y agregar o sustituir en el script del ejemplo anterior lo siguiente:

$mi_mensaje->Host = 'ssl://smtp.gmail.com'; $mi_mensaje->Port = 465;

Page 304: Manual PHP

$mi_mensaje->SMTPAuth = true; $mi_mensaje->Username = 'direccion_completa_de_la_cuenta_de_gmail'; $mi_mensaje->Password = 'contraseña_de_la_cuenta_de_gmail'; $mi_mensaje->From = 'direccion_completa_de_la_cuenta_de_gmail';

Como es obvio, además de tener en marcha el servidor Apache en modo local es preciso que nuestro equipo disponga de acceso a Internet.

Clases y objetos (II)

Constantes. Variables y métodos estáticos

En la página anterior sólo hemos visto una parte de las opciones de definición y uso de clases y objetos. Intentaremos ampliar un poco más aquellos conceptos.

Constantes

Uno de los elementos no imprescindibles que puede contener una clase son las constantes. Para definirlas debemos escribir:

const nombre = valor

dónde const es una palabra reservada que establece la condición de constante, nombre es el nombre de la propia constante (no va precedido de $) y valor el número o cadena alfanumérica asignada a esa constante.

Variables estáticas y métodos estáticos

Las propiedades que hemos estudiado en la página anterior son conocidas como propiedades y también como variables de objeto. Sin embargo pueden definirse otro tipo de variables conocidas como variables de clase, variables estáticas o simplemente estáticas.

Se caracterizan por ser inherentes a la propia clase pudiendo existir y ser utilizadas sin necesidad de instanciar ningún objeto. Es decir, no forman parte de los objetos. A modo de ejemplo podríamos decir que entre las propiedades de una perforada puede estar la de hacer funciones de cascanueces y que para hacer uso de esta propiedad (partir una nuez) no se requiere de ninguno de los objetos específicos (hojas de papel o cualquier otro objeto susceptible de ser perforado) para los que fué diseñada la perforada (la clase).

Son ese tipo de propiedades inherentes a la propia clase y que no requieren de un objeto para ser utilizadas las conocidas comoestáticas. Para definirlas basta anteponer al nombre de la propiedad la palabra reservada static. La sintaxis podría ser así:

static nombre = valor

La condición de estática de una propiedad no es incompatible con la gestión de su visibilidad. Es perfectamente factible utilizar variables definidas como: public static, private static ó reserved static. Por una cuestión de compatibilidad con PHP4 en los casos en los que solo se indique static tendrá la consideración de public static. Aquí tienes algunos ejemplos de definiciones válidas:

public static $pepe="Jose" static $lola="Dolores" private static $rosa="Rosa"

Por lo que respecta a métodos estáticos (tampoco requieren definir un objeto para poder utilizarlos) la sintaxis es similar a la anterior.

Page 305: Manual PHP

Acceso a constantes y a variables y métodos estáticos

Las variables estáticas, las constantes y los métodos definidos como estáticos tienen la peculiaridad de que pueden ser utilizadossin necesidad de haber creado o instanciado ningún objeto. Para acceder a ellos se usa, en vez del tradicional ->, el operador :: conocido como doble dos puntos, operador de resolución de ámbito o Paamayim Nekudotayim

Para acceder a este tipo de variables y/o constantes sin instanciar un objeto debemos utilizar la siguiente sintaxis:

$variable1= nombreClase::constante

recoge en $variable1 el valor de la constante establecida en la clase nombreClase. Los caracteres (::) son obligatorios y reservados.

$variable2= nombreClase::$variable_estatica

recoge en $variable2 el valor de la $variable_estatica correspondiente a la clase nombreClase. Los caracteres (::) son obligatorios y reservados. El nombre de la variable, a diferencia de lo que ocurre con variables no estáticas, debe ir precedido del carácter $.

$variable3= nombreClase::metodo_estatico()

recoge en $variable3 el resultado de la ejecución del netodo_estatico() correspondiente a la clase nombreClase. Los caracteres (::) son obligatorios y reservados. El paréntesis puede incluir eventuales valores de los argumentos de la función.

Si lo que pretendemos es acceder a la variables o métodos estáticos a partir de una instancia de objeto perteneciente a su clase utilizaremos la sintaxis:

$objeto :: constante , $objeto :: variable_estatica o $objeto :: metodo_estatico()

y en el caso de ejercer esta opción desde un método de la propia clase lo haríamos con la misma sintaxis pero sustituyendo $objetopor el pseudobjeto $this.

Cuando el acceso a constantes, variables o métodos estáticos se produce desde un método de la propia clase puede sustituirsenombreClase por la palabra reservada self (yo mismo). Obsérvalo en los ejemplos. Este primero gestiona únicamente constantes y elementos estáticos.

<?php class MiClase { /* definimos constantes */ const cadena1="Soy una cadena"; const cadena2=1.234; /* aqui usamos sintaxis de documento incrustado */ const cadena3=<<<'cde' Soy una cadena incluida dentro de una constante mediante la sintaxis de documento incrustado. Esta sintaxis funciona siempre que la versión de PHP sea superior a 5.3 cde; /* definimos una variable estática */ public static $estatica=<<<'fdg' Soy una variable estática y soy pública. Tanto yo como las constantes podemos ser visualizadas sin necesidad de crear un objeto. Utilizando :: puede vérseme sin problemas. fdg; /* incluyamos dos funciones definidas como estáticas */

Page 306: Manual PHP

public static function mi_estatica($a,$b){ return pow($a, $b)*self::cadena2- MiClase::otra_estatica($a,$b); } public static function otra_estatica($a=3,$b=2){ return pow($b, $a) + MiClase::cadena2; } } /* he escrito la clase pero no voy a crear ningún objeto. Visualizo los elementos y ejecutaré los métodos estáticos */ print MiClase::cadena1."<br />"; print MiClase::cadena2."<br />"; print MiClase::cadena3."<br />"; print MiClase::$estatica."<br />"; print MiClase::mi_estatica(6,5)."<br />"; print MiClase::otra_estatica()."<br />"; ?>

ejemplo306.php

¡Cuidado! La sintaxis de documento inscrutado no funciona en versiones de PHP inferiores a la 5.3. Ocurre lo mismo con $objeto :: constante , $objeto :: variable_estatica o $objeto :: metodo_estatico() que en versiones inferiores han de ser sutituidos por: Nombre_de_la_Clase::constante , Nombre_de_la_Clase::variable_estatica oNombre_de_la_Clase::metodo_estatico()

En este otro ejemplo estamos mezclando llamadas a variables y métodos estáticos junto con otros que lo no son. Todos tienen en común la condición de visibilidad pública.

<?php /* definimos una clase */ class MiPagina { /* establecemos los valores de las constantes y las variables cuidando que todas estas sean publica */ const cabeza1="<html><head><title>"; static $titulo="Probando clases y objetos"; const cabeza2="</title></head><body bgcolor='"; public $color='yellow'; const cabeza3="'>"; public $cuerpo=<<<'CUERPO' Las clases en PHP permiten la sintaxis de <b>documento incrustado</b> solo a partir de la versión 5.3.<br /> En esta página utilizamos constantes y propiedades de tipo <b>public</b> (las estáticas siempre lo son). Por esa razón son accesibles desde cualquier lugar.<br /> Modificaremos el <i>color de fondo</i> y el <i>título</i>. CUERPO; public $pie="</body></html>"; /* en este caso la clase no incluye ningún método */ } /* creamos un nuevo objeto instanciando esta clase */ $MiObjeto= new MiPagina; /* modificamos el color y el título. Observa la diferente forma en que tratamos las variables estáticas */ $MiObjeto->color='Coral'; /* cambiamos el valor de la variable estática $titulo;

Page 307: Manual PHP

MiPagina::$titulo='Ha cambiado el titulo de esta pagina'; /* visualizamos los diferentes elementos (constantes, variables y variables estáticas aplicando la sintaxis adecaduada a cada caso) */ print $MiObjeto::cabeza1; print $MiObjeto::$titulo; print $MiObjeto::cabeza2; print $MiObjeto->color; print $MiObjeto::cabeza3; print $MiObjeto->cuerpo; print $MiObjeto->pie; ?>

ejemplo307.php

Aquí tienes un tercer ejemplo en el se utilizan distintos tipos de visibilidades

ejemplo308.php Ver código fuente

Métodos y/o propiedades con el mismo nombre en clases extendidas

Cuando una clase incluye un método o propiedad cuyo nombre coincide con el de otro método o propiedad de una de sus clases extendidas los objetos utilizarán los métodos o propiedades de la clase que ha sido instanciada para crearlos. Esta situación, conocida bajo el nombre de polimorfismo (varias formas de un mismo método) es algo muy habitual en la programación orientada a objetos.

Hay que tener en cuenta que cuando se trata de constantes, variables estáticas o métodos estáticos que no requieren un objeto puede accederse indistintamente a los de una u otra clase por medio de la siguiente sintaxis:

nombre_de_clase:: constante, variable o método

pudiendo también reemplazarse el nombre_de_clase por un objeto de esa misma clase.

La cuestión es: ¿puede accederse desde un objeto de una clase extendida a un método de la clase padre cuano se da una coincidencia de nombres?. La respuesta es sí. Una posibilidad es utilizar esta sintaxis:

nombre_de_la_clase_padre :: nombre_del_metodo()

dónde se ejecutaría el método nombre_del_metodo() de la forma en que está definido en la clase nombre_de_la_clase_padre. Otra forma de hacerlo sería dotar a la clase extendida de un método similar a este:

function intermediaria () { return parent::metodo(); }

de esta forma la función intermediaria devolverá (return) el resultado de ejecutar el método de la clase padre (parent). Observa en el ejemplo los resultados las diferentes opciones.

<?php /* creamos las clases A, B extendida de A */ class A { /* habrá en la clase extendida otra constante con el mismo nombre CONSTANTE*/ const CONSTANTE=3.141592; /* Hay otra variable estática con el mismo nombre (estatica) en la clase B */ public static $estatica="Soy la variable publica estática de la clase A"; /* Hay otra variable publica con el mismo nombre (variable) en la clase B */ public $variable=" Soy publica y de la clase A";

Page 308: Manual PHP

/* incluimos un contructor en la clase padre */ function __construct(){ print "<br><b>Se instanció un objeto de la clase A.</b> <br>"; } /* Hay otro metodo estático con el mismo nombre (estatico )en la clase B */ public static function estatico(){ return "Me envía el método estático de la clase A (padre)"; } /* Hay otro metodo con el mismo nombre (metodo)en la clase B */ public static function metodo(){ return "Me llamo método y soy de la clase A (padre)"; } } class B extends A{ /* Hay otra constante con el mismo nombre CONSTANTE en la clase A (padre)*/ const CONSTANTE=6.28; /* Hay otra variable estática con el mismo nombre (estatica) en la clase A (padre)*/ public static $estatica="Soy una variable publica estática de la clase B"; /* Hay otra variable publica con el mismo nombre (variable) en la clase A (padre) */ public $variable=" Soy publica y de la clase B"; /* incluimos un contructor en la clase extendida */ function __construct(){ print "<br><b>Se instanció un objeto de la clase B</b><br>"; } /* Hay otro metodo estático con el mismo nombre (estatico )en la clase A (padre) */ public static function estatico(){ return "He surgido del método estático de la clase B"; } /* Hay otro metodo con el mismo nombre (metodo)en la clase A padre */ public static function metodo(){ return "Me llamo método y soy de la clase B"; } /* Creo una funcion puente para poder acceder a métodos de la clase padre que tienen un nombre coincidente con uno de de la extendida */ public static function puente(){ return parent::metodo(); } } /* creamos un objeto instanciando la clase A */ $objeto_clase_A= new A(); /* creamos un objeto instanciando la clase B */ $objeto_clase_B= new B(); print "<br><br>LECTURA DE CONSTANTES<br>"; print "<br>\$objeto_clase_A::CONSTANTE= ".$objeto_clase_A::CONSTANTE; print "<br>\$objeto_clase_B::CONSTANTE= ".$objeto_clase_B::CONSTANTE; print "<br>A::CONSTANTE= ".A::CONSTANTE; print "<br>B::CONSTANTE= ".B::CONSTANTE; print "<br><br>LECTURA DE VARIABLES ESTÁTICAS<br>"; print "<br>\$objeto_clase_A::\$estatica= ".$objeto_clase_A::$estatica; print "<br>\$objeto_clase_B::\$estatica= ".$objeto_clase_B::$estatica; print "<br>A::\$estatica= ".A::$estatica; print "<br>B::\$estatica= ".B::$estatica; print "<br><br>LECTURA DE VARIABLES PUBLICAS<br>"; print "<br>\$objeto_clase_A->variable= ".$objeto_clase_A->variable; print "<br>\$objeto_clase_B->variable= ".$objeto_clase_B->variable; print "<br><br>EJECUCIÓN DE METODOS ESTATICOS<br>"; print "<br>\$objeto_clase_A::estatico()= ".$objeto_clase_A::estatico();

Page 309: Manual PHP

print "<br>\$objeto_clase_B::estatico()= ".$objeto_clase_B::estatico(); print "<br>A::estatico()= ".A::estatico(); print "<br>B::estatico()= ".B::estatico(); print "<br><br>EJECUCIÓN DE METODOS <br>"; print "<br>\$objeto_clase_A->metodo()= ".$objeto_clase_A->metodo(); print "<br>\$objeto_clase_B->metodo()= ".$objeto_clase_B->metodo(); print "<br>A::metodo()= ".A::metodo(); print "<br>También podemos recurrir a la función puente para ejecutar el método de la clase padre"; print "<br>\$objeto_clase_B->puente()= ".$objeto_clase_B->puente(); ?>

ejemplo310.php

Funciones con clases y objetos

Existen también algunas funciones que pueden resultarte útiles a la hora de depurar los scripts en los que manejes clases y objetos. Son las siguientes:

method_exists(objeto, nombre_funcion)

Comprueba si está definida la función nombre_funcion (entenderemos función y método como sinónimos) en el objeto $objeto. Devuelve un valor booleano. Cierto (true) en el caso de que exista esa función o falso (false) en el caso de que no exista.

get_class_vars(nombre_de_la_clase)

Crea un array asociativo cuyos índices son los nombres de las propiedades de la clase nombre_de_la_clase y cuyos valores coinciden con los valores preasignados a cada una de esas propiedades. En este array solo se recogen las variables que han sidoinicializadas asignándoles un valor.

get_class_methods(clase)

Devuelve un array conteniendo los valores de todos los métodos (funciones) definidas en la clase.

get_object_vars(objeto)

Devuelve las variables visibles (y sus valores) contenidas en el objeto.

Incluyendo clases desde ficheros externos

A lo largo de nuestros ejemplos hemos guardado muchas de las clases en ficheros independientes y posteriormente las hemos incluido en los script mediante las funciones include o require_once. No habría restricción en cuanto al nombre a utilizar, sin embargo, resulta muy aconsejable fijar una metodologia simple y cómoda para hacerlo. Si establecemos la pauta de guardar siempre los ficheros que contienen una clase con el nombre de la propia clase (exactamente el nombre de la misma) seguido de una extensión que también vamos a mantener fija y que podría ser, por ejemplo, .class.php podemos hacer uso de una comodísima función (__autoload) que intentaremos concretar con el ejemplo y que comentamos en él.

Hemos duplicado el fichero ejemplo299.php de la página anterior (aquí está su código fuente) y lo hemos guardado comoMiClase.class.php porque MiClase es precisamente el nombre de la clase que contiene.

<?php

Page 310: Manual PHP

function __autoload($nombre_de_clase){ include $nombre_de_clase .'.class.php'; } /* esta funcion se encarga de que cada vez que sea instanciada una clase se cargue mediante include el fichero cuyo nombre coincide con el de la clase y cuya extensión es .class.php. De este modo ya podemos despreocuparnos de incluir clases. */ $objeto= new MiClase; /* se cargaría la clase MiClase.class.php y se crearía el objeto A partir de aquí comprobaremos los resultados de las diferentes funciones informativas visualizando sus resultados mediante print_r con lo cual estaremos en condiciones de visualizar de forma cómoda nombres de variables y/o métodos y sus valores */ print "<br />Resultados de var_dump<br /><br /><pre>"; var_dump($objeto); print "</pre><br /><br />Resultados de print_r<br /><br /><pre>"; print_r($objeto); print "</pre><br /><br />Resultados de var_export<br /><br /><pre>"; var_export($objeto); print "</pre><br /><br />Resultados de get_object_vars<br /><br /><pre>"; print_r(get_object_vars($objeto)); print "</pre><br /><br />Resultados de get_class_methods<br /><br /><pre>"; print_r(get_class_methods($objeto)); print "</pre><br /><br />Resultados de get_class_vars<br /><br /><pre>"; print_r(get_class_vars('MiClase')); method_exists('MiClase','lee') ? print "<br>Existe el método" : print "<br>No existe el método"; method_exists('MiClase', 'calcula')? print "<br>Existe el método" : print "<br>No existe el método"; print "</pre>"; ?>

ejemplo309.php

Objetos únicos

La utilización de clases está íntimamente ligada a la creación de objetos (en plural). Si pensamos en un centro de enseñanza veremos que varias son las aulas, los profesores o los alumnos. Pero puede que nos planteemos que el centro como tal sea un objeto único. Para aquellos casos en los que queramos impedir la creación de más de un objeto podría ser aplicable el procedimiento descrito en este ejemplo.

<?php /* escribiremos una clase cuyos único objeto sea un centro educativo. Para poder utilizar la función __autoload el fichero tendrá como nombre Centro.class.php que coincide con el nombre de la clase */ class Centro{ /* establecemos las propiedades con condición de protegidas*/ protected $nombre="I.E.S. «Las Pruebas y los Objetos»"; //nombre del centro protected $identificador="Q337777777x"; //podría ser el N.I.F. protected $localidad="Fuentes de Narcea (Degaña)" protected $aulas=array(); // será un array destinado a contener aulas (objetos de otra clase) /* insertamos un costructor con visibilidad protegida. Así evitamos que pueda ser invocado desde fuera de la clase o de sus extendidas */ protected function __construct(){ print "Objeto creado satisfactoriamente<br>"; }

Page 311: Manual PHP

/* Agregamos una variable privada y estatica con valor nulo que solo será visible desde la propia clase y no requerirá un objeto para ser instanciada */ protected static $instancia; /* Incluimos un método público y estático con nombre Unico() que: – Comprueba si la propiedad $instancia tiene valor. – En caso de tenerlo nos da un mensaje de advertencia. – Si no lo tiene crea un nuevo objeto de la clase actual. */ public static function Unico(){ if (!isset(self::$instancia)) { $objeto =__CLASS__; // __CLASS__ contiene el nombre de la clase actual self::$instancia = new $objeto; }else{ print "Ya existe un objeto Centro y ha de ser único"; } return self::$instancia; //devuelve el objeto recien creado } } ?>

<?php /* la funcion autoload nos permitirá cargar la clase Centro que es la que se instanciará para crear el objeto */ function __autoload($clase){ include $clase.'.class.php'; } /* la sintaxis habitual \$objeto=mew Centro daría un mensaje de error ya que el método __construct tiene el carácter de protected con lo cual se impide que puede ejecutarse desde esta opción */ /* Lo único visible de la clase Centro es el método Unico. Dado que es un método estático habrá que invocarlo con la sintaxis adecuada a su condición */ Centro::Unico(); /* Al ser invocado por primera el método Unico crea el objeto. */ Centro::Unico(); /* Cuando se invoca por segunda vez el método Unico ya no puede crea el objeto y da el mensaje de advertencia. */ ?>

ejemplo311.php

Objetos múltiples

Aquí puedes ver el código fuente de algunas clases que hemos creado y que utilizaremos para la creación del múltiples objetos tal como puede verse en el ejemplo que hay a continuación

Ver clase Centro Ver clase Aula Ver clase Alumnos Ver clase Profesores

<?php /* la funcion autoload nos evita problemas a la hora de incluir los ficheros que contienen las clases requeridas */ function __autoload($clase){ include $clase.'.class.php'; } /* creamos el objeto miCentro tal como se describe en los ejemplos anteriores */ $miCentro=Centro::Unico(); /* comprobemos el contenido del objeto */

Page 312: Manual PHP

print "<br>Estas son las propiedades del objeto miCentro<br>"; print "<pre>"; print var_dump($miCentro); print "</pre>"; /* vamos a crear el objetos de la clase Aula si miras el código fuente de la clase Aula.class.php verás que el contructor agrega el valor correspondiente al identificador de cada objeto por esa razón cuando instanciemos objetos Aula vamos a incluir como argumento el valor de ese identificador */ /* un array nos puede ser util para crear masivamente las aulas */ $nombre_aulas=array('1º A','2º C','4º F','3º B','1B A'); /* Leeremos este array e iremos creando nuevos objetos de la clase Aula incluyendo el identificador que requiere el constructor */ for ($i=0;$i<sizeof($nombre_aulas);$i++){ /* Haremos que los nuevos objetos sean elementos de un array escalar Para ello solo tenemos que agregar [] a misAulas y cada objeto creado pasará a ser un elemento de ese array */ $misAulas[]=new Aula($nombre_aulas[$i]); } /* comprobemos el contenido de los objetos */ print "<br>Comprobación de los objetos misAulas<br>"; print "<pre>"; print var_dump($misAulas); print "</pre>"; /* con una estrategia similar podremos crear los objetos de la clase Alumnos Ahora al instanciar cada objeto incliremos su nombre y apellido */ /* estos son los datos para los objetos alumnos */ $nombre_alumnos=array('Ana','Benito','Carla','Dionisio','Esther', 'Fernando','Guiomar','Herminio','Isabel','Jenaro'); $apellidos_alumnos=array('Jiménez','Iglesias','Husillos','Gómez','Fernández', 'Escapa','Díaz','Casado','Blázquez','Alonso'); for ($i=0;$i<sizeof($nombre_alumnos);$i++){ $misAlumnos[]=new Alumnos($nombre_alumnos[$i],$apellidos_alumnos[$i]); } /* comprobemos el contenido de los nuevos objetos */ print "<br>Comprobación de los objetos misAlumnos<br>"; print "<pre>"; print var_dump($misAlumnos); print "</pre>"; ?>

ejemplo313.php

La ejecución de este script nos permite comprobar que efectivamente se han creado los objetos pretendidos pero tiene un par de deficiencias manifiestas. Los diferentes tipos de objetos carecen de relación. Hemos creado un centro, aulas y alumnos pero no tienen relación alguna. Sus clases son independientes entre sí. Los alumnos podrían ser de ese u otro centro y lo mismo podría ocurrir con las aulas.

Un segundo problema es que una vez que finaliza la ejecución del script los objetos se destruyen y se pierde toda la información relativa a ellos. Vamos a tratar de ver a continuación como evitar esa pérdida.

Serialización de objetos

Toda la información relativa a un objeto puede se transformada en una cadena mediante la función:

Page 313: Manual PHP

serialize(nombre_del_objeto)

donde nombre_del_objeto indica el objeto que pretendemos serializar. Los resultados de la serialización son susceptibles de ser guardados en un fichero. De esta forma (puedes verlo en el ejemplo) los objetos pueden estar disponibles para su uso posterior.

<?php /* para evitar reescribir incluimos el ejemplo anterior. Se ejecutará integramente */ include('ejemplo313.php'); /* visualizamos la serializacion de cada objeto */ print "<brEsta es la serialización de misAlumnos<br>"; print serialize($misAlumnos); print "<brEsta es la serialización de misAulas<br>"; print serialize($misAulas); print "<brEsta es la serialización de misAulas<br>"; print serialize($miCentro); /* también podemos guardar cada objeto en una variable o en un array tal como hacemos en las instrucciones siguientes */ $serializado[] = serialize($misAlumnos); $serializado[] = serialize($misAulas); $serializado[] = serialize($miCentro); /* vamos a hacer una segunda serialización. Serializaremos y guardaremos en una variable los contenidos del array resultante de la serialización anterior */ $todo=serialize($serializado); /*guardaremos en un fichero (le ponemos como nombre Almacenado)la serialización resultante */ file_put_contents('Almacenado', $todo); ?>

ejemplo314.php

Recuperación de objetos serializados

Los objetos contenidos de las cadenas (o ficheros) que contienen el resultado de una o varias serializaciones pueden ser recuperados por medio de la función:

$objeto_recuperado= unserialize($cadena_serializada)

donde $objeto_recuperado es el nombre que pretendemos asignar al objeto recuperado (no tiene por qué coincidir que el que tenía en el momento de ser serializado) y $cadena_serializada una variable que contiene el resultado de la serialización previa.

Es necesario que estén disponibles las clases a las que pertenecen los objetos que pretendemos recuperar.

<?php /* la funcion autoload no va a incluir los ficheros con las clases necesarias */ function __autoload($clase){ include $clase.'.class.php'; } /* leemeos el fichero y recogemos su contenido en una variable */ $serializado = file_get_contents('Almacenado'); /* recuperamos la serialización y visualizmos el contenido */ $todo_junto = unserialize($serializado); print "<pre>";var_dump($todo_junto);print "</pre>"; print "<br>Podemos leer el array mediante un bucle<br>"; for($i=0;$i<sizeof($todo_junto);$i++){

Page 314: Manual PHP

print "<pre>"; var_dump(unserialize($todo_junto[$i])); print "</pre>"; } ?>

ejemplo315.php

Objetos que contienen objetos

Las propiedades (variables) de los objetos pueden contener valores alfanuméricos o numéricos, arrays, objetos y también arrays cuyos elementos sean objetos. La forma de asignar valores a las propiedades puede tener alguna de las formas siguientes:

$objeto -> propiedad=valor

es la forma más sencilla. Se asigna un nuevo valor (numérico o alfanumérico) a la propiedad del $objeto. Como es lógico requiere la preexistencia del objeto.

$objeto -> propiedad [ ] = valor

Se asigna un nuevo valor (numérico o alfanumérico) a la propiedad [] del $objeto . Como es lógico la propiedad habrá sido definida como array escalar en su clase y el resultado sería un nuevo elemento cuyo índice se conformaría de forma automática sumando una unidad al del último existente.

$objeto -> propiedad = $otro_objeto

se asigna $otro_objeto (objeto preexistente de otra clase) como valor de la propiedad del $objeto .

$objeto -> propiedad [ ] = $otro_objeto

se asigna $otro_objeto (objeto preexistente de otra clase) como valor de la propiedad del $objeto . El hecho de la propiedad tenga forma de array significa que $otro_objeto será el valor asignado a un nuevo elementos del array.

$objeto -> propiedad[] =new clase_del_objeto_a_incluir()

también en este caso se asigna un objeto como valor de la propiedad del $objeto . La diferencia estriba en que el objeto añadido no existe previamente. Por esa razón se crea en el momento de la inclusión mediante new seguido de clase_del_objeto_a_incluir() con los eventuales argumentos que pudiera requerir el constructor de la nueva clase.

$objeto -> propiedad [ ] =new clase_del_objeto_a_incluir()

su única diferencia con el caso anterior es que ahora el nuevo objeto será almacenado en un array.

En estos enlaces puedes ver el código fuente de las clases utilizadas en el ejemplo que se incluye a continuación de ellos.

Ver clase Centro Ver clase Aula Ver clase Alumnos Ver clase Profesores

<?php /* la funcion autoload nos evita problemas a la hora de incluir los ficheros que contienen las clases requeridas */ function __autoload($clase){ include $clase.'.class.php';

Page 315: Manual PHP

} /*creamos un nuevo objeto asignándole valor al identificador al instanciar de la clase Aula Puedes visualizar el constructor desde enlace que hay aqui encima */ $miAula=new Aula('Primero B'); /* la propiedad profesor va a contener un objeto de la clase Profesores que crearemos incluyendo los argumentos que utiliza su constructor */ $miAula->profesor=new Profesores('Juan','López','Matemáticas'); /* agregamos un nuevo elemento al array materiales */ $miAula->materiales[]="Pizarra digital"; /* modificamos el número de puestos ecolares del aula */ $miAula->puestos=45; /* haremos una inclusión masiva de objetos de la clase Alumnos (también puedes ver en el enlace el código fuente de esta clase) utilizamos como valores los nombres y apellidos contenidos en los array */ $nombre_alumnos=array('Ana','Benito','Carla','Dionisio','Esther','Fernando','Guiomar'); $apellidos_alumnos=array('Jiménez','Iglesias','Husillos','Gómez','Fernández','Escapa','Díaz'); for ($i=0;$i<sizeof($nombre_alumnos);$i++){ /* la propiedad alumnos de la clase Aula es un array de ahi que incluyamos los nuevos objetos (Alumnos) en alumnos[] */ $miAula->alumnos[]=new Alumnos($nombre_alumnos[$i],$apellidos_alumnos[$i]); } /* Visualizamos el contenido de el objeto Aula que incluye otros objetos en sus propiedades */ print "<pre>"; print var_dump($miAula); print "</pre>"; ?>

ejemplo316.php

Manipulación de objetos contenidos en objetos

En los ejemplos que incluimos a continuación puedes ver, comentados, algos casos de manipulación de objetos contenidos en otros objetos.

<?php class AulaExtendida extends Aula { function lista_profes(){ print $this->profesor->nombre; print $this->profesor->apellidos; } function lista_profes1(){ print $this->profesor->nombre; print $this->profesor->apellidos; print $this->profesor->materia; } function lista_alumnos(){ foreach ($this->alumnos as $indice=>$contenido){ print $contenido->nombre." ".$contenido->apellidos." ".$contenido->ruta."<br>"; } }

Page 316: Manual PHP

function modifica_alumnos($nom_act='',$ap_act='',$nom_nue='',$ap_nue='', $ruta_nue=''){ foreach ($this->alumnos as $ind=>$cont){ if($this->alumnos[$ind]->nombre==$nom_act && $this->alumnos[$ind]->apellidos==$ap_act){ if ($nom_nue !='')$this->alumnos[$ind]->nombre=$nom_nue; if ($ap_nue !='')$this->alumnos[$ind]->apellidos=$ap_nue; if ($ruta_nue !='')$this->alumnos[$ind]->ruta=$ruta_nue; } } } function borra_alumnos($nom_act='',$ap_act=''){ foreach ($this->alumnos as $ind=>$cont){ if($this->alumnos[$ind]->nombre==$nom_act && $this->alumnos[$ind]->apellidos==$ap_act){ unset($this->alumnos[$ind]); } } $this->alumnos=array_values($this->alumnos); } } /****** comienza una porción de código idéntica a la del ejemplo anterior ******/ function __autoload($clase){ include $clase.'.class.php'; } $miAula=new AulaExtendida('Primero B'); //instanciamos la clase extendida para usar sus metodos $miAula->profesor=new Profesores('Juan','López','Matemáticas'); $miAula->materiales[]="Pizarra digital"; $miAula->puestos=45; $nombre_alumnos=array('Ana','Benito','Carla','Dionisio','Esther','Fernando','Guiomar'); $apellidos_alumnos=array('Jiménez','Iglesias','Husillos','Gómez','Fernández','Escapa','Díaz'); for ($i=0;$i<sizeof($nombre_alumnos);$i++){ $miAula->alumnos[]=new Alumnos($nombre_alumnos[$i],$apellidos_alumnos[$i]); } /**** acaba el código idéntica a la del ejemplo anterior ****/ $miAula->lista_profes(); print "<br /><br />"; /* lista alumnos */ $miAula->lista_alumnos(); print "<br />"; /* modifica alumnos sin busqueda*/ $miAula->alumnos[2]->nombre="Heliodoro"; $miAula->alumnos[3]->apellidos="de los Ríos"; /* modifica alumnos con busqueda*/ $miAula->modifica_alumnos('Ana','Jiménez','María Gertrudis','Cucalón',48); /* borra alumnos con búsqueda*/ $miAula->borra_alumnos('Benito','Iglesias'); $miAula->lista_alumnos(); print "<br />"; print "<pre>"; print var_dump($miAula); print "</pre>"; print "<br><br>"; foreach ($miAula->alumnos[1] as $propiedad=>$valor){

Page 317: Manual PHP

print $propiedad."....".$valor."<br>"; } print "<br><br>"; foreach ($miAula->alumnos as $indice=>$objeto){ foreach ($objeto as $propiedad=>$valor){ print $indice."...".$propiedad."....".$valor."<br>"; } } $miAula->lista_profes1(); ?>

ejemplo317.php

<?php function __autoload($clase){ include $clase.'.class.php'; } /* crearemos un objeto de la clase Aula */ $aula=new Aula('Aula Magna'); print "<br><i>El identificador del aula recien creada es</i>: ".$aula->identificador; /* creamos tres objetos de la clase Alumnos */ $alumno[0]=new Alumnos ('Juan Carlos', 'Rodriguez'); $alumno[1]=new Alumnos ('Feliciano','Perez'); $alumno[2]=new Alumnos ('Luisindo','Orcasitas'); print "<br /><br /><i>Los objetos del array alumnos son estos</i>: "; /* El primer bucle nos permite leer los elementos del array (objetos) y el segundo extrae el nombre de la propiedad y su valor */ foreach ($alumno as $indice=>$objeto){ print "<br>".$indice." "; foreach ($objeto as $propiedad=>$valor){ print "<i>".$propiedad."</i>: ".$valor."; "; } } /* añadimos algunos objetos Alumnos a la propiedad alumnos de la clase Aula */ $aula->alumnos[]=$alumno[0]; $aula->alumnos[]=$alumno[1]; /* incluimos un nuevo objeto en el array alumnos de la clase Aula */ $aula->alumnos[]=new Alumnos ('Sindulfo','Yebra'); /* copiamos el objeto recien creado en un nuevo objeto */ $alumno[3]=$aula->alumnos[2]; print "<br /><br /><i>Los objetos de la propiedad alumnos de la clase Aula son estos</i>: "; foreach ($aula->alumnos as $indice=>$objeto){ print "<br>".$indice." "; foreach ($objeto as $propiedad=>$valor){ print "<i>".$propiedad."</i>: ".$valor."; "; } } /* modificamos nombres de alumnos tanto en el array alumnos como en los objetos aula->alumnos */ $aula->alumnos[0]->nombre="Tácito Petronio"; $alumno[1]->nombre="Francisca Maria"; $alumno[3]->nombre="Iñigo Francisco"; print "<br><br><br>La modificación se produce en ambos lugares<br><br>"; print "<br /><br /><i>Los objetos del array alumnos después de los cambios son</i>: "; foreach ($alumno as $indice=>$objeto){ print "<br>".$indice." "; foreach ($objeto as $propiedad=>$valor){ print "<i>".$propiedad."</i>: ".$valor."; "; }

Page 318: Manual PHP

} print "<br><br><i>En la clase Aula vemos esto después de los cambios:</i>: "; foreach ($aula->alumnos as $indice=>$objeto){ print "<br>".$indice." "; foreach ($objeto as $propiedad=>$valor){ print "<i>".$propiedad."</i>: ".$valor."; "; } } /* eliminaremos algunos objetos. En un caso lo haremos en los de clase Aula y en otros en el array alumno */ unset($aula->alumnos[0]); unset($alumno[1]); unset($aula->alumnos[2]); print "<br><br><br>Después de destruir objetos.<br>"; print "<br>Tácito e Iñigo en el objeto de la Clase Aula"; print "<br>Francisca en el objeto alumnos"; print "<br><br><i>Los objetos del array alumno después de eliminar son</i>: "; foreach ($alumno as $indice=>$objeto){ print "<br>".$indice." "; foreach ($objeto as $propiedad=>$valor){ print "<i>".$propiedad."</i>: ".$valor."; "; } } print "<br /><br /><i>Los objetos de la clase Aula después de eliminar son estos</i>: "; foreach ($aula->alumnos as $indice=>$objeto){ print "<br>".$indice." "; foreach ($objeto as $propiedad=>$valor){ print "<i>".$propiedad."</i>: ".$valor."; "; } } ?>

ejemplo318.php

Clases y objetos (III)

Sobrecargas de propiedades

Al hablar de la privacidad decíamos que las propiedades privadas y protegidas únicamente son accesibles desde funciones incluidas en la propia clase (o en una clase extendida si es protected). Eso significaría que opciones del tipo print $objeto->propiedad o $objeto->propiedad=valor solo funcionarían en el caso de que la propiedad tuviera condición de pública.

En esas situaciones la solución para acceder a las propiedades no visibles (private y protected) sería agregar dentro de la propia clase algo como esto:

...... public function lee_privado($propiedad){ return $this->propiedad } ......

que haría de intermediario y que mediante algo similar a: print $objeto->lee_privado(propiedad) nos permitiría conocer el valor de tal propiedad. Mediante procedimientos similares podríamos modificar valores, eliminar propiedades o comprobar su existencia.

Page 319: Manual PHP

Para simplificar este proceso disponemos de palabras reservadas que permiten que determinadas funciones se ejecuten, si están incluidas en la propia clase, de forma automática. Enumeraremos el comportamiento de algunas de ellas.

public function __get($propiedad) { return $this->$propiedad; }

cuando incluimos una función idéntica a esta en una clase (es imprescindible que tenga la condición de public y que el nombre de la función sea __get) podemos despreocuparnos de si una propiedad es public o tiene la condición de private, protected. En el momento que intentamos conocer el valor de una propiedad del objeto escribiendo: $objeto->propiedad se ejecutará (si es necesario) de forma automática esa función y nos devolverá el valor requerido.

public function __set($propiedad,$valor) { $this->$propiedad=$valor; }

Una función idéntica a esta (también es imprescindible la condición de public y que el nombre de la función sea __set) nos permite asignar un valor a cualquier propiedad del objeto independientemente de que su condición sea public, private o protected. En el momento que intentamos asignar un valor escribiendo: $objeto->propiedad=valor se ejecutará (si es necesario) de forma automática esa función y asignará a la propiedad el valor indicado.

public function __unset($propiedad) { unset ($this->$propiedad); }

Eliminará la propiedad cuando escribamos: unset ($objeto->propiedad) ejecutándose de forma automática, cuando sea necesario, la función __unset.

public function __isset($propiedad) { return isset($this->$propiedad); }

Devolverá TRUE o FALSE (dependiendo de que exista o no exista la propiedad) cuando escribamos: isset($objeto->propiedad).

En el ejemplo puedes ver el funcionamiento de estos métodos incluso en los casos en los que los valores de las propiedades sean arrays u objetos.

<?php /* esta clase tiene como una finalidad la creación de un objeto para pruebas lo único que hace cuando es instanciada es asignar valores a las propiedades publicas a y b.*/ class ObjetoAuxiliar{ public $a; public $b; function __construct($a=7,$b=14){ $this->a=$a; $this->b=$b; } } /* esta nueva clase es la que usaremos para nuestras pruebas */ class Pruebas { protected $propiedad1="13"; protected $matriz=array('Uno','Dos','Tres'); protected $objeto; function __construct(){ $this->objeto=new ObjetoAuxiliar; }

Page 320: Manual PHP

/* esta funcion mágica __get nos permitirá ver desde fuera de la propia clase los contenidos de sus propiedades protegidas y privadas */ public function __get($variable){ return $this->$variable; } /* esta funcion mágica __get nos permitirá, desde fuera de la propia clase, asignar valores a sus propiedades protegidas y privadas */ public function __set($propiedad,$valor){ $this->$propiedad=$valor; } /* esta funcion mágica __unset nos permitirá, desde fuera de la propia clase, eliminar propiedades protegidas y privadas */ public function __unset($propiedad){ print "Eliminando: ".$propiedad."<br />"; unset($this->$propiedad); } /* esta funcion mágica __isset nos permitirá, desde fuera de la propia clase, comprobar si está definidan propiedades protegidas y privadas */ public function __isset($propiedad){ return isset($this->$propiedad); } } /* creamos un nuevo objeto de la clase pruebas */ $miObjeto=new Pruebas(); /* la propiedad1 es protected. Eso significa que solo se visualizaría desde una función de la propia clase o de una de sus extendidas. Cuando al invocarla resulta no ser accesible PHP comprueba si está definido el método __get en la clase. Si lo está lo ejecuta de forma automática considerando la propiedad1 como parámetro de la función __get. Dado que esa función pertenece a la propia clase si puede leer la propiedad y devolver el resultado */ print $miObjeto->propiedad1."<br>"; /* comprobamos que __get funciona tanto cuando la propiedad contiene un array como cuando se trata de un objeto. */ print_r($miObjeto->matriz)."<br>"; print_r($miObjeto->objeto)."<br>"; /* comprobemos ahora la función mágica __set. Se comporta identica a la anterior con la diferencia de que ahora recoge como parámetros el nombre de la propiedad y el valor que se pretende asignar */ $miObjeto->propiedad1=7456; print $miObjeto->propiedad1."<br>"; $miObjeto->matriz=array(7,4,5,6); print_r($miObjeto->matriz)."<br>"; $miObjeto->objeto= new ObjetoAuxiliar(25,987); print_r($miObjeto->objeto)."<br>"; /* comprobamos que se pueden borrar propiedades con los tres tipos de valores */ unset($miObjeto->propiedad1); unset($miObjeto->matriz); unset($miObjeto->objeto); /* comprobamos la existencia de la propiedad utilizando de forma idéntica a los casos anteriores la función mágica __isset */ if (isset($miObjeto->propiedad1)){ print "La propiedad1 si existe"; }else{ print "La propiedad1 no existe"; } ?>

Page 321: Manual PHP

ejemplo319.php

¡Cuidado! Presta atención a la sintaxis de las funciones que hemos descrito. Observa que hemos utilizado $this->$propiedadcuando lo habitual en las funciones sería $this->propiedad. Si omitimos el $ nos dará error.

Clases y métodos abstractos

Quizá nos sea de utilidad detenernos en un caso de la vida cotidiana. Pensemos en una ITV. Los inspectores han de seguir un protocolo que consiste en verificar el funcionamiento de: carroceria, gases, bocina, luces, suspensión, frenos, dirección, etcétera, de cadavehículo y, posteriormente, emitir y firmar un documento acreditativo de los resultados de tal inspección. Este protocolo contiene (o puede contener) instrucciones precisas para emitir y firmar los documentos (se sigue el mismo procedimiento sea cual sea el tipo de vehículo). Nos encontramos ante dos situaciones distintas: especificaciones concretas (métodos) para la burocracia y especificaciones abstractas para la parte técnica. Nadie nos ha dicho aún como inspeccionar una dirección o unas luces. Este protocolo inicial podríamos entenderlo como una clase abstracta con métodos abstractos.

El inspector necesita con carácter imprescindible métodos concretos para los diferentes tipos de vehículos. Si se trata de una motocicleta el método de inspección será radicalmente distinto al de un turismo y el de este será también distinto al de un autobús. Así pues, habrá de contar como métodos de comprobación de cada uno de los elementos para cada una de las clases de vehículo. En nuestro argotdiríamos que son necesarias clases extendidas: motocicleta, turismo y autobús, y además, cada una de esas clases ha de disponer de métodos para todas y cada una de las verificaciones: carroceria, gases, etcétera. Dicho de otra forma: es imprescindible redefinir cada uno de los métodos abstractos para cada una de las clases extendidas. Algo muy similar ocurre con la Programación Orientada a Objetos.

Clases y métodos abstractos

Para definir un método como la condición de abstracto hemos de utilizar la siguiente sintaxis:

abstract protected function nombre_del_metodo ()

dónde la palabra reservada abstract especifica su condición de abstracto, protected establece el carácter protegido del mismo y además, con caracter imprescidible, la function nombre_del_metodo () ha de estar declarada pero no definida (observa que faltan las {} y el habitual contenido de ellas que realmente es lo que define el método o función).

Otra cuestión sumamente importante. Para que una clase pueda incluir un método abstracto también ella ha de tener la condición de abstracta. Es decir, no podemos definir un método como abstracto sin que la clase que lo contiene lo sea. Para definir como abstracta una clase hemos de hacer utilizar la siguiente sintaxis:

abstract class nombre_de_la_clase

una clase definida como abstracta puede no contener métodos abstractos y propiedades con cualquier tipo de visibilidad. Por el contrario, un método abstracto solo puede ser incluido en una clase abstracta.

Restricciones de las clases y métodos abstractos

El uso de clases abstractas requiere tener muy presentes las siguientes cuestiones:

� – Las clases abstractas no pueden ser instanciadas para crear objetos � – Los métodos no abstractos y/o las propiedades de una clase abstracta sólo son accesibles desde sus clases extendidas.

Estonos obliga a definir clases extendidas. � – Los métodos abstractos de una clase (abstracta) han de ser obligatoriamente redefinidos en todas sus clases extendidas. � – Cuando se redefine un método abstracto en una clase extendida ha de hacerse con una condición de visibilidad igual o más

permisiva a la que se estableció en la clase abstracta. Si un método abstracto tiene condición de protected sólo puede ser redefinido como public (más permisivo) o protected (igual de permisivo) <?php

Page 322: Manual PHP

/*creamos una clase abstracta en la que incluimos métodos tanto abstractos como no abstractos, variables y constantes. Los método abstractos solo se declaran pero no se define lo que deben hacer cuando sean invocados */ abstract class Figuras{ /*incluimos los métodos abstractos */ abstract protected function calculaArea(); abstract protected function calculaPerimetro(); /* los restantes metodos, propiedades y constantes de la clase */ const PI=3.141592; protected $tipo; public function Imprime($a,$b){ print "La ".$this->tipo." cuyos radios son: ". $a." y ".$b."<br />"; print "El area es:".$this->calculaArea($a,$b)."<br />"; print "El perímetro es:".$this->calculaPerimetro($a,$b)."<br />"; } } /* las clases abstractas no pueden ser instanciadas para crear objetos. Por tanto es imprescindible una clase extendida */ class Redondas extends Figuras{ public function calculaArea($radio1=0,$radio2=''){ if ($radio2=='') $radio2=$radio1; return self::PI*$radio1*$radio2; } public function calculaPerimetro($radio1=0,$radio2=''){ if ($radio2==''){ $radio2=$radio1; return self::PI*2*$radio1; }else{ return self::PI*(3*($radio1+$radio2)-sqrt((3*$radio1+$radio2)*($radio1+3*$radio2))); } } } /* instanciamos un objeto de la clase extendida (la abstracta no lo permitiria)*/ $circulo= new Redondas(); /* desde este objeto ya se puede llamar a métodos de cualquiera de las clases */ $circulo->Imprime (5,5); ?>

ejemplo320.php

Interfaces

Las interfaces tienen un cierto paralelismo con las clases y métodos abstractos pero ni su sintaxis ni su comportamiento son iguales. Podemos decir que una interface es un declaración o lista de métodos publicos no definidos que han de incluirse (y definirse) con carácter obligatorio en todas las clases que implementen esa interface.

Desde una perspectiva utilitarista podríamos entenderlas como una lista de obligaciones y tareas obligatorias (métodos que han de incluirse en una clase) que imposibilitará el olvido ya que de no realizarse todas (omitir algún método establecido en la interface) las instancias de la clase producirán un error.

En una interface solo pueden incluirse métodos (funciones) con visibilidad public y constantes.

Sintaxis de las interfaces

Page 323: Manual PHP

Para declarar una interface es necesario una sintaxis como esta:

interface nombre_de_la_interface { public function nombre_de_una_funcion (argumento1, argumento2,...) public function nombre_de_otra_funcion (argumento1, argumento2,...) ....... const nombre = valor (opcional) }

Para utilizar interface es necesario una sintaxis como esta:

class nombre_de_la_clase implements nombre_de_una_interface, nombre_de_otra_interface { /*TODOS los métodos de nombre_de_una_interface */ public function nombre_de_una_funcion (argumento1, argumento2,...){ .......(código del método) ......... ................ } public function nombre_de_otra_funcion (argumento1, argumento2,...){ .......(código del método) ......... ................ } /* TODOS los métodos de nombre_de_otra_interface */ ................ ................ }

Restricciones de las interfaces y sus clases

� – Cuando una clase usa más de una interface hay que cuidar que estas no contengan métodos con el mismo nombre. De darse esa circunstancia se produciría un error.

� – Hay que definir en la clase todos los métodos de todas las interfaces que impelenta.

� – Las constantes definidas en una interface se utilizan exactamente igual que las constantes de clase. Su única particularidad es que que no pueden ser sobrescritas por una clase/interfaz extendidas.

<?php /* creamos una interface que solo puede contener métodos publicos no definidos y constantes */ interface Figuras{ public function calculaArea(); public function calculaPerimetro(); const PI=3.141592; } /* una nueva interface que en este caso no incluye ninguna constante */ interface Cuerpos{ public function calculaVolumen(); } /* definimos una clase que implementa las dos interfaces (Figuras y Cuerpos) ello le obliga a definir todas las funciones declaradas en ambas clases Si hubiera dos funciones con el mismo nombre se produciría una ambiguedad que generaría un error */ class Redondas implements Figuras, Cuerpos{ public $tipo; public function calculaArea($radio1=0,$radio2=''){

Page 324: Manual PHP

if ($radio2=='') $radio2=$radio1; return $this::PI*$radio1*$radio2; } public function calculaPerimetro($radio1=0,$radio2=''){ if ($radio2==''){ $radio2=$radio1; return self::PI*2*$radio1; }else{ return self::PI*(3*($radio1+$radio2)-sqrt((3*$radio1+$radio2)*($radio1+3*$radio2))); } } public function calculaVolumen($radio1=0,$radio2=''){ if ($radio2=='') { $radio2=$radio1; return 4/3*self::PI*pow($radio1,3); }else{ return 4/3*self::PI*$radio1*pow($radio2,2); } } } /* instanciamos objetos pertenecientes a la clase de la forma habitual. La constante PI definida en el interface es asumida como propia por la clase */ $circulo= new Redondas(); print "El area de la figura es: ". $circulo->calculaArea(5,5) ."<br>"; print "El perimetro de la figura es: ". $circulo->calculaPerimetro(5,5) ."<br>"; print "El volumen del cuerpo es: ". $circulo->calculaVolumen(5,5) ."<br>"; ?>

ejemplo321.php

Métodos que manejan objetos o arrays

Los métodos definidos en una clase pueden manejar objetos de la propia clase o de otra. Esta última posibilidad requiere disponer de un objeto de cada una de las dos clases y, además, que en una de ellas se incluyan métodos con esta sintaxis:

function nombre_del_metodo ( Otra_Clase, $objeto_cualquier_nombre , $argumento1, $argumento2,...) { $objeto_cualquier_nombre->propiedad $objeto_cualquier_nombre->metodo ( $argumento1, $argumento2,...) }

La utilización de estos métodos requiere haber creado previamente un objeto de cada una de las clases que vamos a llamar$Objeto_Base y $Objeto_Externo. Con estas premisas la ejecución de un método requeriría esta sintaxis:

$Objeto_Base->nombre_del_metodo ($Objeto_Externo, argumento1, argumento2)

los nombres corresponden a los anteriormente descritos y los argumentos incluidos en la llamada se corresponden con los eventualmente requeridos por el método referenciado en la que hemos llamado Otra_Clase tal como puedes ver en este ejemplo.

También es posible pasar arrays a los métodos de una clase y manejar sus contenidos mediante funciones incluidas en el método. Para ello se requiere la siguiente sintaxis:

function nombre_del_metodo ( array, $variable) { ......... funciones php que manejan el array $variable

Page 325: Manual PHP

......... }

dónde array es una palabra reservada que especifica la condición de tal de lo recogido por $variable. El método debe ser invocado de la forma siguiente:

$objeto -> nombre_del_metodo( array('elemento1','elemento2',...) ) o, como es lógico $matriz = array('elemento1','elemento2',...); $objeto -> nombre_del_metodo ( $matriz ) <?php /* definimos una clase que llamaremos externa (será la auxiliar) que incluye métodos aritméticos. La funcion suma puede recibir valores como argumentos y, en caso de recibirnos modificará las propiedades de sumando1 y sumando 2 */ class Externa { public $sumando1=1; public $sumando2=2; function Suma($a='',$b=''){ if ($a !='') $this->sumando1=$a; if ($b !='') $this->sumando2=$b; return $this->sumando1+$this->sumando2; } function Resta(){ return $this->sumando1-$this->sumando2; } function Multiplica(){ return $this->sumando1*$this->sumando2; } function Divide(){ return $this->sumando1/$this->sumando2; } } /* definiremos ahora una nueva clase (que será la Base de nuestro ejemplo) con la peculiaridad de que sus métodos hacen referencia a otra clase y a objetos de esa otra clase. En todos los métodos de esta clase (con una excepción que comentaremos) se incluyen como parámetros el nombre de la clase auxiliar (Externa), una variable que recibirá los objetos de la clase Externa y los eventuales argumentos requeridos por la función que en el caso de la suma son dos */ class Base { function adicion (Externa $exterior, $a='',$b='') { /* este método pasará el objeto exterior (recibido en la llamada) junto con los valores de las variables a y b. Devolverá el resultado de ejecutar el método Suma (de la clase externa) sobre el objeto exterior también de la clase externa */ return $exterior->Suma($a,$b); } function sustracion (Externa $exterior) { return $exterior->Resta(); }

Page 326: Manual PHP

function multiplicacion (Externa $exterior) { return $exterior->Multiplica(); } function division (Externa $exterior) { return $exterior->Divide(); } function cambia_a (Externa $exterior, $a) { return $exterior->sumando1=$a; } /* en este caso el objeto recibido en la petición es una instancia de la propia clase Base y se le aplicará un método (neperiano) definido en esta misma clase. */ function lee_logaritmo (Base $exterior,$a) { return $exterior->neperiano($a); } /* el método neperiano que será instanciados por anterior */ function neperiano($a){ return log($a); } /* esta función recibe como argumento un array que será recogido por la función en la variable la_matriz y tratado como tal. Requiere obligatoriamente la palabra reservada array en la misma posición que en las funciones anteriores incluyen el nombre de la clase a la que pertenece el objeto. Ahora podría decirse que el array la_matriz es un objeto de la clase array */ function lee_matriz (array $la_matriz){ print "El array tiene: ".sizeof ($la_matriz)." elementos<br>"; foreach ($la_matriz as $indice=>$valor){ print $indice." * ".$valor."<br>"; } } } /*manejo de las clases anteriores */ /* Creamos un objeto de cada de las dos clases */ $Objeto_Externo = new Externa; $Objeto_Base = new Base; /* el objeto base ejecuta sus métodos incluyendo como argumentos el identificado del objeto externo junto con los eventuales argumentos*/ print "La suma es: ". $Objeto_Base->adicion($Objeto_Externo)."<br />"; print "La suma con parámetros es: ". $Objeto_Base->adicion($Objeto_Externo,25,89)."<br />"; print "Los valores pasados a la función producirán una modificación "; print "en los valores de las propiedades sumando1 y sumando 2 del Objeto_externo. "; print " Esas nuevos valores se mantendrán en tanto no vuelvan a ser modificados.<br>"; print "La suma con un parámetro es: ". $Objeto_Base->adicion($Objeto_Externo,749)."<br />"; print "La suma con un parámetro es: ". $Objeto_Base->adicion($Objeto_Externo,'',125)."<br />"; print "La diferencia es: ".$Objeto_Base->sustracion($Objeto_Externo)."<br />";

Page 327: Manual PHP

print "El producto es: ".$Objeto_Base->multiplicacion($Objeto_Externo)."<br />"; print "El cociente es: ".$Objeto_Base->division($Objeto_Externo)."<br />"; print "El nuevo valor de a: ".$Objeto_Base->cambia_a($Objeto_Externo,1013)."<br />"; print "La suma es: ". $Objeto_Base->adicion($Objeto_Externo)."<br />"; print "El logaritmo neperiano de 10 es: ". $Objeto_Base->lee_logaritmo($Objeto_Base,10)."<br />"; print "Probando un array<br> "; print $Objeto_Base->lee_matriz(array(13,27,128,945,'pepe'))."<br>"; ?>

ejemplo322.php

Crear ficheros PDF

Creación de ficheros PDF

Exiten varias opciones para la creación mediante funciones PHP de ficheros en formato PDF. El abanico de posibilidades se extiende desde las aplicaciones de pago tales como PDFlib hasta las clases open source tales como FPDF o TCPDF, que amplia las prestaciones de la anterior y que será la que utilizaremos para desarrollar los contenidos de este tema.

La clase TCPDF

La clase TCPDF está en constante evolución. Por ello se van produciendo, con mucha frecuencia, continuas actualizaciones, correcciones de errores y mejoras. Las versiones más recientes pueden encontrarse en este enlace. Hemos descargado desdehttp://www.tcpdf.org el fichero tcpdf_5_9_088.zip (la última versión disponible en el momento de elaborar estos materiales) y lo hemos descomprimido en nuestro directorio php. Durante la descompresión se creó de forma automática un directorio llamado tcpdf en el que se incluyeron una serie de subdirectorios tales como: config, examples y fonts y una serie de ficheros entre los que podremos encontrar el denominado tcpdf.php que es el que realmente contiene la clase.

La creación de ficheros PDF mediante la clase TCPDF requiere que en los scripts se incluyan los ficheros: tcpdf.php, tcpdf_config.phpy spa.php que corresponden a la clase propiamente dicha, a su configuración y al idioma en uso. Tales ficheros se encuentran en los directorios: tcpdf y en sus subdirectorios tcpdf/config y tcpdf/config/lang. Para mayor comodidad en el manejo hemos creado un fichero llamado tcpdf.inc.php que sirve para realizar la inclusión de los tres anteriores. Puedes ver aquí su contenido.

Si los ficheros estuvieran en directorios distintos de los indicados habrían de adecuarse las rutas absolutas incluidas en el ficherotcpdf.inc.php.

El constructor de la clase TCPDF

La clase incluida en el fichero tcpdf.php tiene por nombre TCPDF. Por tanto su uso requerirá –además del necesarioinclude('tcpdf.inc.php')– la creación de un objeto mediante la sintaxis:

$objeto= new TCPDF();

Al instanciar un nuevo objeto se ejecuta siempre el constructor (recuerda que un constructor es una método con nombre __constructque se ejecuta de forma automática el momento en que es creado un nuevo objeto) que en esta clase permite seis argumentos: orientacion,unidad de medida, formato, unicode, codificación y diskcaché.

Por tanto, al instanciar la clase para crear el nuevo objeto podríamos escribir:

Page 328: Manual PHP

$objeto= new TCPDF( orientacion, unidad_de_medida, formato, unicode, codificacion, disckcache );

dónde:

� – orientacion admite dos valores: 'P' (normal ó Portrait) y 'L' (apaisado ó Landscape en denominación inglesa). El valor por defecto es 'P' (normal).

� – unidad_de_medida permite especificar las dimensiones del documento en: 'in' (pulgadas), 'pt' (puntos), 'mm' (milímetros) y también 'cm' (centímetros). El valor por defecto es 'mm'' (milímetros). Recuerda que una pulgada equivale a 25,4 milímetros y queun punto equivale a 1/72 pulgadas (0,35277 mm.).

� – formato permite especificar las dimensiones de cada página del documento mediante las convenciones de los formatos ISO 216 serie A desde 'A0' (841x1189 mm) hasta 'A12'; la serie B de ISO 216 desde 'B0' (1000x1414 mm) hasta 'B12' y la serie C de las misma norma desde 'C0' (917x1297 mm) hasta 'C12'. El valor por defecto es 'A4' (297x210 mm).

� – unicode es un parámetro booleano (true o false) que permite indicar si los textos que se incluirán en el documento tendrán formato UNICODE o no. Por defecto su valor es TRUE

� – codificación permite indicar el formato de codificación de los textos incluidos en el documento. Por defecto su valor es 'UTF-8'.

� – diskcaché es otro parámetro booleano que permite configurar el uso de la memoria RAM. Si está configurado como TRUE se utilizará menos memoria RAM almacenando los datos temporales en caché. El ahorro de memoria puede ralentizar fuertemente el proceso. El valor por defecto de este parámetro es false.

Una vez creado el objeto es necesario añadirle una o más páginas. Para ello se utiliza el método AddPage. Su sintaxis más simple es la siguiente:

$objeto->AddPage()

Formatos a medida

La clase TCPDF también permite especificar formatos a medida. Para ello hemos definir las dimensiones deseadas mediante la sintaxis:

$medidas=array(ancho,alto)

dónde ancho y alto son las dimensiones deseadas (especificadas en la unidad establecida para el documento) e incluir este array ($medidas) como valor del parámetro formato en la llamada al constructor de la clase.

El método Output()

La clase TCPDF incluye la función Output que permite visualizar o guardar el objeto creado. Mediante la sintaxis:

$obj->Output(nombre,destino)

puede asignarse cualquier nombre, con o sin extensión, al objeto generado. Cuando no se especifica el nombre, se le asigna por defecto el valor doc.pdf. El parámetro destino puede tener uno o varios de los siguientes valores:

� 'I' – Permite visualizar el fichero directamente en el navegador si el plugin si está disponible. La opción «Guardar como...» asignaría, por defecto, el nombre especificado en el parámetro nombre.

� 'D' – Envía el fichero al navegador mostrando la ventana de opción que permite elegir entre Abrir o Descargar. Si se elige esta última, guardará el fichero con el nombre especificado en el parámetro nombre.

� 'F' – Guarda el fichero en el directorio actual del servidor con el nombre asignado en la opción nombre.

El método SetDisplayMode()

Page 329: Manual PHP

Este método –sólo afecta a la forma en la se visualiza el documento en la pantalla del cliente– permite configurar dos parámetros:tamaño y forma de visualización.

$obj->SetDisplayMode( tamaño,visualizacion)

El tamaño (no se refiere al documento impreso sino a la visualización en pantalla) permite una de estas opciones: fullpage (el zoom del visor se adapta de modo que encaje la página completa en la pantalla); fullwidth (ajusta el zoom de forma que se visualize el documento ajustando el ancho al de la pantalla); real (ajusta el zoom del visor al 100%) ó default que usa el modo por defecto del visor.

La forma de visualización permite, entre otras, las opciones: single muestras las páginas separadas de una en una; continuous va mostrándolas una detrás de otra de forma continua; two muestras dos columnas (con dos páginas) simultaneamente, y default que, como en el caso anterior, usa el modo por defecto del visor.

Algunsos ejemplos

Como primer ejemplo crearemos un fichero con dos páginas en blanco.

<?php # incluimos todos los ficheros requeridos # tal como lo hemos recogido en el fichero tcpdf.inc.php # creado tal como se indica al margen include("tcpdf.inc.php"); # creamos un nuevo objeto (MiPDF) utilizando la clase FPDF $MiPDF=new TCPDF(); # creamos una página en blanco $MiPDF->Addpage(); # creamos una segunda página en blanco $MiPDF->Addpage(); # visualizamos el documento $MiPDF->Output(); ?>

ejemplo323.php

Si observas con detalle el pdf del ejemplo anterior observarás que en la parte superior de las páginas aparece una extraña línea horizontal. Se debe a que, por defecto, esta clase incluye en cada documento un encabezado y un pie de página. La forma de evitarlo será desactivar esa opción incluyendo en el script llamadas a los métodos setPrintHeader(false) y setPrintFooter(false) con el valor booleanofalse tal como puedes ver en este otro ejemplo.

ejemplo324.php Ver código fuente

En este ejemplo establecemos dimensiones, unidades de medida y orientación del documento. Comprobaremos que podemos cambiar la orientación (de normal a apaisada o viceversa) de cada una las páginas del documento.

<?php # incluimos todos los ficheros requeridos # tal como lo hemos recogido en el fichero tcpdf.inc.php # creado tal como se indica al margen include("tcpdf.inc.php"); /* vamos a configurar el documento como apaisado (P), utilizando las pulgadas como unidad de medida y unas dimensiones "no estandar" de 10 x 20 pulgadas */ # creamos un array con las dimensiones (ancho y alto); $dimensiones=array (10,20); # creamos un nuevo objeto (MiPDF) utilizando la clase FPDF

Page 330: Manual PHP

# incluyendo en este caso los valores a utilizar por el constructor $MiPDF=new TCPDF('P','in',$dimensiones); # creamos una página en blanco con las indicaciones del constructor $MiPDF->Addpage(); #Incluimos una segunda página cambiando la orientación # un cambio de orientación respecto a la inicial $MiPDF->Addpage('L'); # creamos una segunda página en blanco # en la que, al no incluir el parámetro de orientación # utilizará el valor utilizado en la anterior $MiPDF->Addpage(); # visualizamos el documento $MiPDF->Output(); ?>

ejemplo325.php

Otros métodos de la clase TCPDF

Aunque no tienen excesiva utilidad práctica, vamos a comentar someramente aquí -también los incluimos en el ejemplo- algunos de los métodos, de carácter básicamente informativo, que incluye la clase FPDF.

� SetAuthor("nombre del autor"). Permite incluir una cadena con el nombre del autor.

� SetCreator("nombre de la aplicación"). Permite incluir una cadena con el nombre del programa utilizado para crear el documento.

� SetTitle("título del documento"). Permite incluir una cadena con el título del documento.

� SetKeywords("palabras clave"). Permite incluir una cadena con una lista de palabras clave separadas por espacios.

� SetSubject(tema) Permite incluir una cadena de texto explicativa de los temas tratados en el contenido del documento

Los datos incluidos en el documento mediante los métodos anteriores no son presentados diractamente en el documento. Sólo son visualizables cuando se exploran los metadatos del documento.

<?php # incluimos la clase fpdf que está en este mismo directorio include("tcpdf.inc.php"); /* vamos a configurar el documento como apaisado (P), utilizando los milímetros como unidad de medida y unas dimensiones "no estandar" de 140 x 200 milimetros */ # creamos un array con las dimensiones (ancho y alto); $dimensiones=array (140,200); # creamos un nuevo objeto (MiPDF) utilizando la clase TCPDF # incluyendo en este caso los valores a utilizar por el constructor $MiPDF=new TCPDF('P','mm',$dimensiones); #desactivamos encabezado y pie de página $MiPDF->setPrintHeader(false); $MiPDF->setPrintFooter(false); # el método SetAuthor nos permite incluir el nombre del autor $MiPDF->SetAuthor('Pepe Pérez'); # el método SetCreator nos permite incluir el nombre de la # aplicacion que genera el pdf $MiPDF->SetCreator('clase TCPDF'); # el método SetTitle nos permite incluir un título $MiPDF->SetTitle('Pruebas del pdf'); # el método SetKeywords nos permite incluir palabras claves # separadas por espacios y dentro de una misma cadena $MiPDF->SetKeywords('palabra1 palabra2'); # el método SetDisplayMode nos permite incluir palabras claves # separadas por espacios y dentro de una misma cadena

Page 331: Manual PHP

$MiPDF->SetDisplayMode('fullpage','two'); # creamos una página en blanco. Incluimos, para esta primera página # un cambio de orientación respecto a la inicial $MiPDF->Addpage('L'); # creamos una segunda página en blanco # en la que, al no incluir el parámetro de orientación # utilizará el valor usado en el último Addpage que ha sido L. $MiPDF->Addpage(); /* insertamos unas nuevas dimensiones */ $dimension2=array(97,234); /* las dimensiones anteriores pero invirtiendo el orden */ $dimension3=array(234,97); /* insertamos páginas con la misma orientación pero utilizando cada uno de los dos arrays anteriores. Veremos que las páginas que se visualizan tienen igual tamaño y orientación. Es decir, no importa el orden en que incluyamos las dimensiones ya que será la orientación establecida la situe esas medidas */ $MiPDF->Addpage('L',$dimension2); $MiPDF->Addpage('L',$dimension3); # visualizamos el documento $MiPDF->Output('donpepito.pdf','I'); ?>

Los ejemplos siguientes son similares. La única modificación que contienen respecto al código fuente anterior es la correspondiente al segundo parámetro (destino) del método Output.

Destino="I" Destino="D" Destino="F"

Textos en PDF

Fijación de márgenes

Algunas funciones de inserción de textos permiten colocar los mismos dentro de lo que podría llamarse caja de texto que no es otra cosa que la superficie de la página delimitada por los márgenes de la misma. Por defecto, estos márgenes miden 15 milímetros, por laizquierda y por la derecha del documento y 27 y 25 milímetros por la parte superior e inferior del mismo. Así mismo, también por defecto, se establecen márgenes para encabezado y pie de página de 5 y 10 milímetros respectivamente.

Los valores de esta configuración por defecto están contenidos en constantes del fichero tcpdf_config.php. Si editamos este fichero y modificamos los valores asignado mediante la función define a las constantes cuyo nombre comienza por PDF_MARGIN_ (seguido de left,top, etcétera) podremos adecuar a nuestras necesidades los valores de esos parámetros.

También podemos hacer esas modificaciones (únicamente para el documento actual) por medio del método siguiente:

$objeto->SetMargins(izquierdo,superior,derecho )

dónde los parámetros izquierdo, superior y derecho son los valores numéricos que permiten establecer, por este orden y en la unidad de medida que se indique en el constructor, los márgenes del documento. Si se omite el tercer parámetro (derecho) se le asignará el valor que se haya indicado para el izquierdo.

Cuando se trata de establecer márgenes entre textos y bordes de celdas contenedoras (lo veremos en los métodos Cell y Multicell) puede utilizarse el método:

$objeto->SetCellPaddings( izquierda, superior, derecha, inferior)

donde izquierda, superior, derecha e inferior son, por este orden, las distancias entre texto y borde de la celda contenedora.

Page 332: Manual PHP

Si lo que pretendemos es establecer o modificar las distancias entre bordes de celdas contiguas utilizaremos el método:

$objeto->SetCellMargins( izquierda, superior, derecha, inferior)

donde izquierda, superior, derecha e inferior son, por este orden, las distancias entre dos celdas contiguas.

Margen inferior y salto de página

Por medio del método:

$objeto->SetAutoPageBreak( automatico, margen_inferior )

donde automatico es un valor booleano (1 ó 0 ) que permite activar o desactivar el salto de página automático. Cuando está activo–true o 1– se van añadiendo nuevas páginas, de forma automática, hasta que se incluya la totalidad del texto especificado. En el caso de estar desactivado –false o 0–, los textos que excedieran los límites de la página no se visualizarían, ya que no se añadiría una nueva página. Por defecto está opción está configurada como true.

El segundo parámetro, el valor margen_inferior, permite especificar el margen inferior mínimo de la página. Su valor –numérico– deberá indicarse en las unidades establecidas por el constructor. Si no se indica asumirá cero con margen por defecto.

Tipo de letra, estilo, tamaño y colores de texto y fondo

La elección del tipo de letra puede haceres mediante la función:

$objeto->SetFont ( nombre,estilo,tamaño)

donde nombre es una cadena que debe indicar el nombre del tipo de letra (helvetica, times, zapfdingbats, courier y symbol son las fuentes incluidas en la distribución). Puedes verlas desde este enlace.

El parámetro estilo puede especificarse mediante la cadena vacía ("") para el tipo normal o mediante 'B', 'I', 'U' (negrita, cursiva,subrayada) o sus combinaciones. Por ejemplo, 'BU' indicará negrita subrayada. Las fuentes de símbolos sólo permiten el estilo normal.

Por medio del parámetro tamaño se especifica el tamaño de la letra expresado en puntos. Si no se especifica otra cosa los valores por defecto son: helvetica normal de 12 puntos.

$objeto->SetFontSize( numero )

Permite modificar unicamente el tamaño de la fuente en uso asignándole en adelante el valor en puntos especificado en numero.

$objeto->setFontSpacing ( numero )

Permite modificar en la cantidad especificada en numero la distancia entre caracteres. Por defecto su valor es 0.

$objeto->setFontStretching ( numero )

Permite estrechar la fuente al porcentaje indicado en numero. Por defecto su valor es 100.

$objeto->SetTextColor ( rojo,verde ,azul )

Establece el color del texto mediante los valores numéricos comprendidos entre 0 y 255 representan las respectivas componentes de los colores primarios rojo, verde y azul.

Page 333: Manual PHP

$objeto->SetTextColor ( negro )

En este caso, el valor numérico negro, también comprendido entre 0 y 255, establece colores de la escala de grises desde el negro (cero) hasta el blanco (255). Si no se especifica un color los textos aparecerán en negro.

Una vez establecido un color su valor se mantiente hasta que no sea invocada nuevamente la función SetTextColor para su modificación.

Una forma alternativa de utilizar los colores es la de definir un color mediante el método:

$objeto->AddSpotColor( nombre, cyan,magenta, amarillo, negro )

permite asignar mediante una cadena cualquiera un nombre a un color (por ejemplo: micolor) y los valores cyan, magenta, amarillo ynegro son los valores porcentuales de cada una de las tintas en la composición del color. Siempre que se les asignen nombres distintos podrán definirse cuantos colores sean necesarios.

El uso de los colores anteriores para la impresión de textos requiere el método:

$objeto->SetTextSpotColor ( nombre, porcentaje)

donde nombre es el nombre de un color previamente creado mediante AddSpotColor y porcentaje representa el tanto por ciento de opacidad de esa tinta (100 la haría absolutamente opaca y 0 la convertiría en totalmente transparente).

Cuando se trata de asignar un color de fondo (fill) para textos, celdas o dibujos se utiliza una de estas opciones:

$objeto->SetFillColor ( rojo, verde, azul) $objeto->SetFillColor ( negro) $objeto->SetFillSpotColor ( nombre, porcentaje )

cuyo comportamiento es idéntico al descrito para el caso de colores de texto.

Posición actual

La clase TCPDF dispone de métodos que permiten determinar el valor actual de las coordenadas del punto de inserción (lugar donde se va a posicionar el texto, imagen o elemento gráfico que se incluya) y también modificar el valor actual de esos puntos. Son las siguientes:

$objeto->GetX() y $objeto->GetY()

permiten conocer los valores de la posiciones actuales, horizontal (X) y vertical (Y), del punto de inserción. El resultado estará expresado en las unidades establecidas por el constructor considerando como origen de coordenadas la esquina superior izquierda del documento y valores positivos –horizontal y vertical– los colocados a la derecha (X) y debajo (Y) del origen.

Para establecer una nuevo punto de inserción se pueden usar cualquiera de estas funciones:

$objeto->SetX(valor X) $objeto->SetY(valor Y) $objeto->SetXY(valor X, valor Y)

mediante las cuales estableceremos sus coordenadas: horizontal, vertical o ambas. Cuando el valor de X (en el caso SetX) es negativo se considera con referencia al margen derecho de la página. De igual modo, cuando se asigna un valor negativo en SetY se considera respecto a la parte inferior de la página.

Page 334: Manual PHP

Números de página

La numeración de las páginas de los documentos es factible ya que TCPDF dispone de métodos específicos para ello.

$objeto->getAliasNumPage()

Este método devuelve una cadena que contiene el número de la página actual.

$objeto->getAliasNbPage()()

Complementario al anterior, este método devuelve el número total de páginas del documento.

Saltos de línea

Mediante $objeto->Ln (medida) se ejecuta un salto de línea. La posición actual regresa al margen izquierdo y la vertical se desplaza en el número de unidades indicado en el parámetro medida.

Estilos de línea

Cuando se trata de establecer estilos de línea podemos recurrir a alguna de las siguientes funciones:

$objeto->SetDrawColor rojo, verde, azul ) o $objeto->SetDrawColor ( negro )

permiten establecer el color de las líneas (bordes de celdas y otros elementos gráficos)siguiendo el mismo criterio ya comentado para rellenos y textos.

$objeto->SetDrawSpotColor ( nombre)

Igual que ocurría para textos y rellenos permite asignar a la líneas un color cuyo nombre debe haber sido definido previamente por medio de AddSpotColor.

$objeto->SetLineWidth ( espesor )

Permite establece el espesor de las líneas mediante un número que puede un número decimal que debe interpretarse expresado en las unidades de medida en uso en la página.

$objeto->SetLineStyle( $array )

Establece el estilo de una línea según los datos contenidos en un $array asociativo cuyos elementos son los siguientes:

� $array['widht']=numero Este elemento del array permite establecer el espesor de la línea. Es una opción alternativa a SetLineWidth

� $array['join']=['miter'|'round'|'bevel'] Admite uno de los tres valores indicados: miter (inglete), round (redondeado) o bevel (bisel) y establece la forma en la que se perfilarán los extremos de los trazos de las líneas discontinuas.

� $array['cap']=['but'|'round'|'square']. Admite uno de los tres valores indicados: but (inglete), round (redondeado) o square (cuadrado) y establece la forma en la que se dibujarán las uniones de las líneas en las esquinas del rectángulo de la celda.

� $array['dash']="cadena" Permite establecer el tipo de trazos que formarán la línea. Si se indica 0 dibujará una línea continua. Si se especificara algo como 20,10 lo que representaría sería una línea discontinua formada por trazos de color de 20 unidades de longitud seguidos de espacio en blanco de 10 unidades. Si este parámetro fuera "20,10,8,6,5,8" lo que representaría sería: un trazo de 20 unidades seguido de un espacio de 10, después iría un trazo de 8 unidades seguido de un espacio de 6 y, a continuación, un nuevo trazo de 5 y un nuevo espacio de 8 repitiéndose la secuencia completa cuantas veces fuera necesario para efectuar el trazado completo.

Page 335: Manual PHP

� $array['phase']= numero Permite especificar mediante un numero entero por cual de los valores indicados en dash comienza el trazado de la línea.

� $array['color']=array(cyan,magenta,amarilo,negro) Permite incluir un array escalar que contenga los valores cyan, magenta,amarillo,negro que componen el color de la línea. Esta opción es alternativa al uso de SetDrawColor y/o SetDrawSpotColor>.

En el ejemplo330 tienes algunas muestras de como definir estilos de línea.

Inclusón de textos en el documento

El método Write

La inclusión de textos multilínea puede hacerse utilizando el método:

$objeto-> Write ($interlinea, $texto, $enlace="", $fondo=false, $alineacion="", $nueva_linea=false)

dónde los parámetros tienen la utilidad descrita en la tabla siguiente.

Parámetro Valores Utilidad

$interlinea número Permite establecer la interlínea del texto. Cuando se establecen valores inferiores a la separación mínima entre renglones desoye el mandato y estable la interlínea mínima.

$texto cadena

Cadena o variable conteniendo el texto que debe incluirse mediante el método Write. Es aconsejable tomar la cautela de comprobar que la codificación del texto utiliza la misma codificación establecida por el constructor del objeto. El texto puede extraerse de un fichero externo mediante una función que permita su lectura. Tal es el caso delfile_get_contents('nombre_del_fichero_externo')

$enlace cadena Permite incluir una cadena con una dirección URI. El texto se comportaría como un hiperenlace

$fondo booleano Cuando este parámetro está como true el texto aparece sobre un color de fondo (puede establecerse medianteSetFillColor (negro por defecto).

$alineacion L, C, R o J Establece la forma de alineación (izquierda, centrada, derecha o justificada) del texto respecto a la caja de texto del documento. Para que los Write consecutivos cambien de alineación hay que producir un salto de línea. Puede hacerse mediante el parámetro $nueva_linea

$nueva_linea booleano Cuando se establece como true el cursor se sitúa en la línea a la del último texto incluido. En caso contrario (false) se mantiene en la misma línea.

ejemplo329.php Ver código fuente

Inserción de celdas con una sola línea de texto

La forma más simple de inserción de textos bien como elementos individuales (por ejemplo títulos de una sola línea) o tablas formadas por celdas que contengan una sola línea de texto es la que utiliza el método:

$objeto->Cell ($ancho, $alto=0, $texto="", $borde=0, $salto_de_linea=0, $alineacion="", $fondo=false, $enlace="", $ajuste_horizontal=0, $ignore_min_height=false, $alin_vertical_texto='T', $alineacion_vertical='M')

que imprime un área rectangular, con fondo y bordes opcionales y que puede contener una cadena de texto que puede alinearse respecto a los bordes de la celda. La esquina superior izquierda de la celda se sitúa en la posición actual del punto de inserción. Después de ser invocado este método el punto de inserción puede desplazarse a la derecha o a la línea siguiente. El texto puede ser utilizado con un hiperenlace y, además, puede expandirse en tamaño o en espaciado para ajustarse a las dimensiones de la celda. Los parámetro que utiliza esta función, cuyos valores por defecto puedes ver detrás del nombre de cada uno de ellos, son los que comentamos en la tabla siguiente.

Parámetro Valores Utilidad

$ancho número Ancho de la celda expresado unidad de medida establecida en la llamada al constructor TCPDF. Si es cero se extiende desde el punto de inserción hasta el margen derecho de la página

Page 336: Manual PHP

$alto número Alto mínimo de la celda expresado en la unidad de medida establecida en la llamada al constructor. La altura de la celda se ajustará al texto contenido en ella. Si se estable altura cero la celda se adaptará a las dimensiones de su contenido

$texto cadena Cadena o variable conteniendo el texto que debe incluirse mediante el método Cell. Es aconsejable tomar la cautela de comprobar que la codificación del texto utiliza la misma codificación establecida por el constructor del objeto.

$borde

0 La celda se visualizará sin bordes

1 Pertime visualizar la celda con un borde del color y ancho especificados por los métodos SetDrawColor ySetLineWidth o por las opciones poder defecto de estos (negro de 1 unidad de espesor)

'LTRB'

Esta cadena que permite, siempre que se mantenga el orden, elegir todas o varias de las letras indicadas sirve para establecer por qué parte de la de la celda se añadirán bordes. L alude a la izquierda, T a la parte superior, R a la derecha y B a la parte inferior. Por ejemplo: 'TB' dibujaría bordes por la parte superior e inferior de la celda, 'LB' los pondría por la izquierda y por debajo Y 'LRB' pondría bordes por todas partes excepto por la parte inferior.

$salto_de_linea

0 Cuando no se especifica este valor o se especifica 0 (valor por defecto) se establece que el punto de inserción, después de la llamada a este método, se sitúa justamente en el borde derecho de la celda.

1 Mediante esta opción, la celda siguiente que se incluya se situará en el margen izquierdo de la página y debajo de la fila actual.

2 Cuando se indica este valor, la próxima celda que se incluya aparecerá debajo de la actual y alineada al borde izquierdo de esta.

$alineacion L, C, R o J Establece la forma de alineación (izquierda, centrada, derecha o justificada) del texto respecto a la celda contenedora.

$fondo booleano Cuando este parámetro está como true la caja contenedora aparece un color de fondo (puede establecerse mediante SetFillColor (negro por defecto).

$enlace cadena Permite incluir una cadena con una dirección URI. El texto se comportaría como un hiperenlace

$ajuste_horizontal

0 No realiza ningún ajuste del texto que puede desbordar el espacio delimitado por el recuadro contenedor

1 Se producirá, sólo si es necesario un estrechamiento del texto hasta lograr que quepa en el recuadro contenedor.

2 Reducirá o expandirá el ancho del texto para ajustarlo a las dimensiones del recuadro contenedor

3 Reducirá la separación entre caracteres (sin alterar el ancho de la tipografía) si resulta necesario para que quepa el texto en el marco contenedor

4 Aumenta o reduce la separación entre caracteres para ajustar el texto al marco contenedor

$ignore _min_ height booleano

Cuando la altura asignada a la celda es demasiado pequeña para contener el texto con la fuente establecida si este parámetro está como false (0) la altura de la celda se ajustará para contener el texto. Si esta opción está configurada como true (1) se respetará la altura de la celda y el texto aparecerá fuera de los bordes de esta.l

$alin_vertical_texto

T El borde superior del contenedor se alineará con el valor de la ordenada especificada en el parámetro y

A El borde superior de las letras se alineará con el valor de la ordenada especificada en el parámetro y

L La línea base de las letras se alineará con el valor de la ordenada especificada en el parámetro y

B El borde inferior del contenedor se alineará con el valor de la ordenada especificada en el parámetro y

$alineacion_vertical

T El texto se alinea verticalmente a la parte superior del contenedor

C El texto se alinea verticalmente al centro del contenedor

B El texto se alinea verticalmente al borde inferior del contenedor

El punto de inserción de cada una de las celdas se establece de la forma siguiente:

� – El resultante de la ejecución anterior de otro método de la clase.

� – Si va precedido de una llamada al método AddPage se situará en el vértice superior izquierdo de la caja de texto (después de aplicar los márgenes) de la página.

� – Si va precedido de una llamada al método Ln se situará en el margen izquierdo de esa nueva línea.

� – Si va precedido de SetXY se situará en los coordenadas establecidas por este método.

� – Si va precedido de una llamada anterior al método Cell se situará en la posición condicionada por el valor del parámetro Lnincluido en aquella llamada.

ejemplo330.php Ver código fuente

¡Cuidado! Según las prestaciones del equipo que estemos utilizando y la complejidad de PDF que tratemos de generar puede aparecernos el mensaje de error: Fatal error: Maximum execution time of 30 seconds exceeded. Esto se debe a se ha sobresado el tiempo máximo por defecto. Hay dos maneras de evitar ese error. Una de ellas sería modificar ese tiempo en fichero php.ini. La segunda sería incluir en el encabezado del script la instrucciónset_time_limit(segundos) dónde segundos el nuevo límite de ejecución que tendrá únicamente validez para el documento actual.

Page 337: Manual PHP

Transformaciones en celdas

La clase TCPDF dispone de métodos que permiten efectuar transformaciones gráficas de algunos elementos entre los que se incluyen las celdas. Toda trasformación comienza con una llamada al método $objeto->StartTransform() y acaba con $objeto->StopTransform(). La estructura del código podría ser similar a esta:

$objeto->StartTransform() ... ... aplicación de las funciones de transformación obj->Cell(....) Incluir la celda que será abjeto de transformación $obj->StartTransform();

Este tipo de acciones requieren los métodos siguientes:

$objeto-> Rotate (angulo,x,y)

dónde el angulo de giro se expresa en grados sexagesimales y se mide en sentido trigonométrico y los valores de x e y son las coordenadas del centro de giro. La transformación producirá un giro de la celda respecto a su centro de giro.

o $objeto-> Rotate (angulo) o o $objeto-> Rotate (angulo, "","")

cuando no se especifica el centro de giro la transformación producirá un giro de la celda respecto a su vértice superior izquierdo. Esta función es equivalente a utilizar en la anterior, las coordenadas actuales del punto de inserción como centro de giro.

¡Cuidado! Rotate(ang,"","") y Rotate(ang,0,0) producen resultados distintos. El primero considera como centro de giro las coordenadas actuales del punto de inserción y el segundo considera que ese centro está situado en la esquina superior izquierda de la página. $objeto-> Translate (x, y)

produce una translación de la celda desde el punto de inserción actual hasta otro punto situado x unidades a su derecha (izquierda si su valor es negativo) e y unidades hacia abajo (arriba si su valor es negativo).

$objeto-> SkewX (angulo, x, y)

transforma la celda (rectangular) en un paralelogramo inclinando los lados verticales del rectángulo original en la medida indicada porang (también en grados sexagesimales y medido en sentido trigonómetrico) siendo el centro de la tranformación el punto de coordenadasx,y. Si se omiten estos valores considerá con centro el punto de inserción actual.

$objeto-> SkewX (angulo, x, y)

transforma la celda (rectangular) de modo similar al anterior aunque ahora los lados que sufren el cambio de orientación son los horizontales.

$objeto-> ScaleX (porcentaje, x, y), $objeto-> ScaleY (porcentaje, x, y) y $objeto-> ScaleXY (porcentaje, x, y)

Page 338: Manual PHP

son tres opciones que permiten transformar la celda modificando sus dimensiones horizontales, verticales ó ambas y sus contenidos en el porcentaje incluido como parámetro en cada una de las funciones. Las coordenas x e y corresponden al centro de la transformación. Si se omiten se tomará como tal el punto de inserción actual.

$objeto-> MirrorV (angulo, y)

transforma la celda mediante una simetría axial (como si se tratara de un espejo) cuyo eje de simetria es la recta horizontal de ordenada y. Si se omite se considerará la ordenada del punto de inserción actual

$objeto-> MirrorH (angulo, x)

transforma la celda mediante una simetría axial (como si se tratara de un espejo) cuyo eje de simetria es la recta vertical de abscisax. Si se omite se considerará la ordenada del punto de inserción actual

$objeto-> MirrorP (angulo,x, y)

transforma la celda mediante dos simetrías axiales (como si se tratara del efecto de un espejo reflejado en otro espejo). La primera transformación ser realiza respecto a la recta vertical de abscisa x y la segunda respecto a la horizontal de ordenada x. Si se omiten se considerará la ordenada del punto de inserción actual

ejemplo331.php Ver código fuente

Celdas multilínea

La creación de celdas sin la restricción del método anterior en cuanto al número de líneas es posible mediante el método:

$obj->Multicell ($ancho, $alto, $texto, $borde=0, $alineacion='J', $fondo=false, $salto_de_linea=1, $x='', $y='', $reseth=true, $ajuste_horizontal=0, $ishtml=false, $autopadding=true, $maxh=0, $alineacion_vertical='T', $fitcell=false)) cuyas propiedades $ancho, $alto, $texto, $borde, $alineacion, $fondo, $salto_de_linea, $ajuste_horizontal y $alineacion_vertical, coinciden con los indicados para cell en la tabla anterior, siendo los restantes los que se indican en la tabla siguiente.

Parámetro Valores Utilidad

$x número Abscisa del punto de inserción expresada en las unidad de medida establecida en la llamada al constructor TCPDF

$y número Ordenada del punto de inserción expresada en las unidad de medida establecida en la llamada al constructor TCPDF

$reseth Booleano

Si se establece como false la celda actual mantendrá como interlínea la altura que tuviera la celda que le precede. Caso de ser true (valor por defecto, la interlínea se establecerá automaticamente ajustándose al tamaño de la letra en uso). La altura de la celda previa será a estos efectos la altura real de la celda después de haberle incluido el texto (si excede lo establecido en el parámetro $alto) o el valor de $alto en caso de no sobrepasar esa altura

$ishtml Booleano Si se establece true permite incluir textos en formato HTML. Esta opción en la versión actual de la clase presenta bastantes restricciones.

$autopadding Booleano Si se configura como true (1) el margen interior de la celda se ajusta de manera automática a lo establecido mediante SetCellPaddings a los cuatro bordes de la celda. Si su valor es false(0) ese ajuste de espacios se realiza únicamente respecto a los bordes izquierdo y derecho.

$maxh número Establece la altura máxima que puede tener la celda. Cuando se especifica, este valor debe ser mayor o igual que parámetro h y su valor máximo puede permitir que la celda sobrepase el margen inferior de la página. Cuando se especifica cero se desactiva la opción. Requiere que el parametro ishtml esté configurado como false

$fitcell Booleano Si se configura como true el tamaño de la fuente puede ser reducido hasta adaptarse a las dimensiones de la celda contenedora. Requiere que esté definido $maxh con un valor distinto de cero

Sigue los mismos criterios indicados para Cell en lo relativo al posicionamiento del punto de inserción cuando se hace una llamada a este método y permite todas las transformaciones comentadas anteriormente.

ejemplo332.php Ver código fuente

Page 339: Manual PHP

Veremos ahora un ejemplo de como crear un documento a partir de un fichero de texto. En el primer ejemplo se utiliza el método Writemientras que en el segundo utilizamos Multicell aplicado un padding para modificar la estética de la presentación

ejemplo333.php Ver código fuente ejemplo334.php Ver código fuente

Convertir HTML en PDF

Cuando se trata de mantener el formato HTML en un documento pdf puede utilizarse el método (tiene algunas restricciones) siguiente:

$objeto->WriteHTML($html, $ln=true, $fondo=false, $reseth=false, $cell=false, $alineacion='')

Para que los resultados sean correctos es muy importante que el código HTML esté limpio y bien formado. De lo contrario los resultados pueden resultar muy diferentes a los deseados.

Los parámetros $fondo, $reseth y $alineacion coinciden con lo descrito en funciones anteriores. Los restantes son estos:

Parámetro Valores Utilidad

$html cadena Es el texto a incluir en el documento junto con sus etiquetas HTML.

$ln booleano Si se establece como true agrega un salto de línea al final del texto.

$cell booleano Si es cierto agrega el contenido en una celda a la derecha de lo anterior

ejemplo335.php Ver código fuente

Textos multicolumna

La clase TCPDF dispone de dos métodos que permiten incluir, utilizando el método Write, textos varias columnas. Para ello es preciso establecer en número de columnas deseado utilizando la función:

$objeto->setEqualColumns(columnas, ancho)

mediante la cual se puede establecer el número de columnas deseadas y el ancho de las mismas. La función determinará de forma automática el espacio entre columnas a partir del ancho de la página (deducidos los márgenes izquierdo y derecho) y del número y ancho de las columnas. Si el ancho se estableciera de tal forma que no hubiera espacio entre columnas la propia función restringiría ese ancho a un valor máximo que permita un margen mínimo entre columnas.

Si no se especifican los parámetros columnas y ancho será considerada una sola columna cuyo ancho sería igual al espacio entre los márgenes izquierdo y derecho de la página.

Una vez definido el número y ancho de las columnas y antes de incluir textos será preciso situar el punto de inserción en la columna adecuada. Para ello la clase dispone del método siguiente:

$objeto->selectColumn(columna)

Cuando no se incluye el parámetro columna se interpreta la primera columna de la izquierda de la página. Una vez ejecutadas estas dos funciones solo faltaría insertar el texto mediante la función Write tal como puede verse en los ejemplos siguientes.

Page 340: Manual PHP

ejemplo336.php Ver código fuente ejemplo337.php Ver código fuente

Reservar espacios en páginas

Cuando se incluyen textos en documento PDF puede ser necesario reservar áreas de las páginas para incluir en ellas imágenes u otros elementos gráficos de forma similar a lo que ocurre cuando se trabaja con código HTML o con programas gráficos de autoedición

La clase TCPDF dispone de un método que permite hacer esa reserva de espacios. Es el siguiente:

$objeto->setPageRegions(array_de_regiones)

Este método debe incluirse antes de empezar a escribir nada en las páginas y requiere que se hayan definido los espaciones en blanco mediante el array_de_regiones que es un array bidimensional de esta forma:

$array_de_definiciones=Array(); $array_de_regiones[ ]=array('page' =>"pagina", 'xt' =>num, 'yt' =>num, 'xb' =>num, 'yb' =>num, 'side' =>"L")

donde page recoge en una cadena el número de la página en la que pretendemos reservar el espacio; side, que puede tomar los valores "L"(izquierda) o "R" (derecha), especifica si la reserva del espacio que pretendemos se hará en el margen izquierdo o derecho de la página. Los parámetros xt, yt, xb e yb corresponden a las coordenadas de los puntos señalados en la imagen. Si establecemos xt y xb con el mismo valor el área reservada se trasnsformará en un rectángulo. Podremos establecer y combinar las áreas que deseemos tal como puedes ver en el ejemplo.

ejemplo338.php Ver código fuente

Gráficos e imágenes en PDF

Dibujar segmentos y líneas

Segmentos

Para dibujar segmentos rectilíneos TCPDF dispone del método:

$objeto->Line ( x0, y0, x1, y1, $estilo_segmento )

donde x0, y0, x1 e y1 son la coordenadas de los extremos del segmento y donde $estilo_segmento es un array que incluye los valores requeridos por el método SetLineStyle. Si no se especifica este valor la línea utilizará el estilo por defecto o el último que haya sido definido en el script.

En este ejemplo, además de aplicar el dibujo de segmentos, procuraremos ver las diferentes opciones de definición y modificación de los estilos tal como hemos señalado en la página anterior al referirnos a los bordes de las celdas.

ejemplo339.php Ver código fuente

Page 341: Manual PHP

Líneas poligonales

El dibujo de líneas poligonales puede hacerse mediante el método:

$objeto->Polyline ( poligonal, tipo_de_trazado, estilo*, color_fondo )

donde poligonal es un array que contiene las coordenadas de los puntos de la misma. El parámetro tipo_de_trazado es uno de los valores que comentamos en la tabla de siguiente y que permiten establecer eventuales rellenos, bordes y cierres en la poligonal.

Valor Utilidad

D Traza la línea sin cerrar la poligonal

d Cierra la poligonal (agregando un segmento que une el primer punto y el último) y dibuja la línea

F Rellena el trazado sin dibujar la línea. Utiliza la regla del número distinto (nonzero winding number) para determinar la región a rellenar.

F* Rellena el trazado sin dibujar la línea. Utiliza la regla del par - impar (even-odd) para determinar la región a rellenar.

DF Traza la línea (sin cerrarla) y rellena el trazado usando lla regla del número distinto (nonzero winding number) para determinar la región a rellenar

DF* Traza la línea (sin cerrarla) y rellena el trazado, usando la regla del par - impar (even-odd) para determinar la región a rellenar.

df Cierra el trazado, dibuja la línea y rellena usando lla regla del número distinto (nonzero winding number)

df* Cierra el trazado, dibuja la línea y rellena usando la regla par-impar (even-odd) para determinar la región a rellenar

CNZ Esta opción permite utilizar elementos gráficos cerrados (rectángulos, circulos, etcétera) para recortar imágenes.

Mediante el array estilo* puede definirse el modo de trazar la polilínea que puede aplicar un estilo_segmento distinto para cada uno de los segmentos que componen la poligonal. Si no se establece aplicará a todos los segmentos el último de los definidos en el documento.

El array estilo* puede tener una de estas formas:

$estilo=array ( 'all'=>estilo_segmento) aplicará el estilo_segmento a todos los segmentos del trazado. Recuerda que lo hemos comentado en la página anterior al referirnos al método SetLineStyle y podría tener una forma similar a esta: $estilo_segmento =array('width' => 0.5,'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(255, 0, 255)) $estilo=array ( estilo_segmento1, estilo_segmento2, ...) iría aplicando los sucesivos estilos incluidos en el array a cada uno de los segmentos que conforman la polínea. Si el número de segmentos fuera mayor que el número de elementos del array de estilos aplicará a los no indicados el último de los indicados en el array. Si el array de estilos contuviera valores igual a cero se entendería que los segmentos afectados no serían trazados.

Para el caso de trazados cerrados y con un color_de_fondo podremos especificar como cuarto parámetro del método un array que contenga los valores RGB del color de fondo aplicable.

ejemplo340.php Ver código fuente

Curvas de Bezier

Mediante el método:

$objeto->Curve(x0, y0, x1, y1, x2, y2, x3, y0, tipo_de_trazado, estilo_segmento, color_fondo)

dónde x0 e y0 son las coordenadas del punto inicial, x3 e y3 las del punto final los puntos x1, y1, x2 e y2 son las respectivas coordenadas de los puntos de control (manejadores de las curvas de Bezier) de los puntos inicial o final se consigue dibujar una porción de curva entre ambos extemos. En la imagen pueden verse con más detalle la posición de los puntos. El resto de los valores, tipo_de_trazado,estilo_segmento y color_de_fondo son exactamente los mismos definidos anteriormente para el caso de las polilíneas.

Page 342: Manual PHP

Las curvas obtenidas mediante este método son susceptibles de ser modificadas utilizando la funciones de transformación que hemos comentado en la página anterior.

ejemplo341.php Ver código fuente

Rectángulos, círculos y elipses

Para representar estas figuras planas se dispone de los siguientes métodos:

$objeto->Rect ( x,y,ancho,alto,tipo_de_trazado, estilo*, color_fondo )

Dibuja un rectángulo cuyo vértice superior izquierdo está situado en el punto señalado por las coordenadas x e y con dimensionesancho y alto. Los parámetros tipo_de_trazado y color_fondo son los mismos señalados para curvas y poligonales y , estilo* define el estilo de cada uno de los lados mediante array asociativo que puede tener la forma array('all'=>estilo_segmento) si se trata de aplicar un estilo único a los cuatro lados. Si pretendemos que cada lado tenga un estilo diferente debemos utilizar una forma del tipo: array('L'=>estilo_izquierda, 'R'=>estilo_derecha,'T'=>estilo_superior,'B'=>estilo_inferior) donde los diferentes estilos incluidos como valores de los elementos del array asociativo son a su vez arrays que definen los estilo_segmento respectivos. Cuando se incluye un cero como valor de una o varias de las posiciones estas no serán dibujadas. $objeto->Circle(x, y, radio, inicio,final, tipo_de_trazado, estilo_segmento, color_fondo )

Dubuja un círculo con las líneas y rellenos indicados en los parámetros tipo_de_trazado, estilo_segmento y color_fondo con su centro situado en el punto de coordenadas x e y y con el radio especificado. Si se especifican valores para inicio y final (sus valores por defecto son 0 y 360) se representaría únicamente el sector circular delimitado por los radios que forman angulos inicio y inicio con el eje horizontal.

$objeto->Ellipse(x, y, semieje_horizontal, semieje_vertical, inclinacion, inicio, final, tipo_de_trazado, estilo_segmento, color_fondo)

Dibuja una elipse (o una porción de ella) siguiendo idénticos criterios a los indicados para el círculo. La única diferencia en cuanto a parámetros es que se incluyen semieje_horizontal y semieje_vertical (medida de los semiejes de la elipse) e inclinacion que indica el angulo que forma con la horizontal el eje de la elipse.

$objeto->RoundedRect(x,y,ancho,alto,radio_esquina,forma_esquinas,tipo_de_trazado, estilo_segmento, color_fondo )

Dibuja un rectángulo del ancho y alto especificados con sus esquinas redondeadas y con un radio_esquina, cuyo vértice superior izquierdo viene especificados por los valores de x e y. El parámetro forma_esquinas es una cadena del tipo "1011" que indica cuales de las esquinas (las señaladas con 1) han de ser redondeas y cuales no (las marcadas con 0). El primer valor corresponde a la esquina superior derecha continuando por las siguientes en el sentido de avance de las agujas del reloj. Los parámetros tipo_de_trazado, estilo_segmento,color_fondo son los mismos comentados anteriormente.

El rectángulo redondeado debe utilizar el mismo estilo de línea para todo su contorno y tanto los rectángulos de todo tipo como los círculos, elipse y también los polígonos soportan las funciones de transformación que hemos comentado en la página anterior.

Page 343: Manual PHP

ejemplo342.php Ver código fuente

Polígonos regulares y estrellados

Polígonos regulares

Para dibujar polígonos regulares podemos utilizar el método:

$objeto->RegularPolygon ( Xc, Yc, radio, lados, angulo_inicial, circunferencia, tipo_de_trazado, estilo*, color_fondo , tipo_circunferencia, estilo_circunferencia, relleno_circulo)

dónde Xc e Yc son las coordenadas del centro de la circunferencia circunscrita, radio es el radio, lados el número de lados,angulo_inicial el ángulo (medido en sentido trigonómetrico) que forma con la parte negativa del eje vertical el radio correspondiente al primer vértice del polígono, circunferencia es un valor booleano que indica si debe representarse o no la circunferencia circunscrita, tipo_de_trazadolas opciones de relleno y trazado del polígono siguiendo el criterio de la tabla ya comentada en métodos anteriores, estilo* un array especificando los estilos de línea del polígono (igual que ocurría con las poligonales puede aplicarse un estilo distinto a cada uno de los lados), color_fondo el color de relleno del polígono, siendo tipo_circunferencia, estilo_circunferencia y relleno_circulo los parámetros ya conocidos aplicados esta vez a la circunferencia circunscrita en el caso de que sea representada.

Polígonos estrellados

Cuando se trata de polígonos estrellados puede utilizarse el método:

$objeto->StarPolygon(Xc, Yc, radio, lados, ng, angulo_inicial, circunferencia, tipo_de_trazado, estilo*, color_fondo , tipo_circunferencia, estilo_circunferencia, relleno_circulo)

cuyos parámetros coinciden exactamente con los descritos para el caso del polígono regular con la única salvedad de que añade uno nuevo, ng, cuya funcionalidad puede verse en esta imagen.

El método StarPolygon lo que hace es determinar las posiciones de los vértices de un polígono regular y trazar una poligonal cerrada(el trazado acaba al llegar al punto de partida) formada por las diagonales que unen dos vértices separados ng posiciones. Obviamente cuando el valor de ng es 1 los vértices a unir serán consecutivos y por lo tanto dibujará un polígono regular.

ejemplo343.php Ver código fuente

Imágenes

Insertar imágenes

Page 344: Manual PHP

La integración de imágenes en un documento PDF puede hacerse mediante el método:

$objeto->Image ( imagen, x, y, ancho, alto, tipo, enlace, punto_insercion, remuestreo, resolucion, alineacion, imk, imgk, borde, proporcionalidad, ocultar, encajar)

dónde: imagen es la ruta y nombre de la imagen, x e y las coordenadas donde se posicionará la esquina superior izquierda de la misma, ancho y alto sus dimensiones después de ser insertada en el documento. Si se omitiera una de las dos dimensiones (o se asignara el valor cero) redimensionaria a la medida especificada manteniendo la otra un valor proporcional a sus dimensiones originales. Si especificamos ambos valores iguales a cero el resultado dependerá del valor que asignemos al parámetro booleano encajar. Si ese valor fuera false la imagen se incluiría según sus dimensiones originales y si su altura fuera mayor que el espacio disponible en la página actual produciría un salto de página de forma automática. Cuando encajar tuviera el valor true la imagen se redimensionaría manteniendo la proporcionalidad y limitando su tamaño de forma que no sobrepase los márgenes del documento.

Mediante el parámetro tipo se puede especificar el tipo de imagen que puede ser, entre otros: GIF, JPG, PNG ó BMP. Si no se especifica lo tomará de la extensión de fichero que contiene la imagen. El parámetro enlace permite incluir una cadena conteniendo el destino de un eventual hiperenlace. Mediante la propiedad punto_insercion se puede especificar la posición en la que quedará el punto de inserción después de haber incluido la imagen. Admite uno de estos valores: T (arriba a la derecha de la imagen), M (en el centro a la derecha de la imagen), B (en la parte inferior de la imagen a la derecha de la misma) o N (en una nueva línea debajo de la imagen).

El parámetro remuestreo es un valor booleano que está íntimamente relacionado con resolucion que es el que le sigue en la llamada al método. Cuando remuestreo toma el valor false la imagen no será remuestreada y mantendrá su resolución original. Cuando se le asigna el valor true o 1 remuestreará la imagen para ajustarla a la resolución especificada en resolucion siempre que el valor de esta (expresada enpuntos por pulgada) sea superior al de la imagen original. En el caso de que remuestreo tuviera valor 2 el proceso se efectuaría tanto si aumente como si disminuye la resolución original. La visualización en pantalla no se afecta por estos parámetros sin embargo si puede tener gran importancia a la hora de producir un documento impreso. En esos casos lo aconsejable será ajustar la resolución de la imagen al valor que vaya a ser utilizado por la impresora.

Mediante el parámetro alineacion es posible alinear una imagen dentro de la línea en la se encuentre el punto de inserción. Admite como valores L, C o R dependiendo si pretendemos alinear a la izquierda, centro o derecha del documento.

El parámetro imk parece estar obsoleto y se refieren a la creación de áreas de transparencia en imágenes. Tienen bastantes restricciones de uso y es absolutamente prescindible. Bastaría con utilizar imágenes en formato PNG ó GIF que permiten directamente esas transparencias. Por el contrario, la opción imgk si tiene gran utilidad cuando se trata de recortar imágenes siguiendo el borde de una figura cerrada tal como veremos cuando comentemos el proceso de este tipo de transformaciones.

¡Cuidado! Cuando al invocar el método Image sea necesario incluir las opciones imk, imgk utiliza siempre false como valores. Hemos comprobado que si ponemos una cadena vacia '' (aparentemente podría parecer los mismo) aparecen mensajes de error en la visualización con algunas imágenes.

El parámetro borde permite asignarle un borde a la imagen. Los valores posibles son: 0 (sin bordes); 1 (borde por defecto) o un array idéntico al descrito para el caso de los rectángulos que permite configurar las características específicas de los bordes de cada uno de los lados de la imagen.

Cuando el valor de proporcionalidad es false la imagen se acomodaría a las dimensiones especificadas en ancho y alto provocando distorsiones en la misma. Por el contrario, cuando su valor no es false la imagen se acomoda a la caja manteniendo la proporcionalidad. Como consecuencia de ello pueden quedar espacios sobrantes que podrían distribuirse a nuestra voluntad dependiendo del valor que asignemos a proporcionalidad. Ese parámetro es una cadena formada por dos caracteres de los cuales el primero ha de ser L (izquierda), C(centro) ó R (derecha), mientras que para el segundo se requiere T (arriba), M (en medio) ó B (abajo). Dependiendo de esta configuración la imagen se acomodará de una u otra forma al marco delimitado por los parámetros ancho y alto.

Por último, valor booleano ocultar establece la no visualización de la imagen en caso de asignarle el valor true. Cuando su valor esfalse (valor por defecto) la imagen se visualiza normalmente.

En el ejemplo siguiente hemos desarrollado y comentado ejemplos con imágenes que utilizan las diferentes opciones aquí comentadas.

Page 345: Manual PHP

ejemplo344.php Ver código fuente

Recortar imágenes

Una de las posibilidades gráficas de la clase TCPDF es la de recortar imágenes siguiendo el perímetro de una figura cerrada. El procedimiento para ello es el siguiente:

� $objeto->StartTransform() Empezamos que invocar el método que inicia una transformación.

� $objeto->Rect(x,y,ancho,alto,'CNZ') Invocamos el método que permita hacer un trazado cerrado (Circle, Elipse, Rect, RoundedRect, Polygon, StarPolygon) estableciendo como tipo_de_trazado el valor CNZ.

� $objeto->Image(imagen, x, y, ancho, alto, tipo) Insertamos la imagen de modo que por su posición y dimensiones se superponga con la figura cerrada incluida en el método anterior.

� $objeto->StopTransform() Finalizamos la transformación.

Como resultado del proceso solo se visualizará la parte de la imagen situada dentro del contorno de la figura cerrada que hace función de máscara. Si la figura tiene definidas bordes estos aparecerán delimitando la imagen.

También es posible efectuar recordes de imágenes delimitándolas por los bordes de las letras que componen un texto. El proceso es bastante similar al anterior. Comienza como aquel con:

$objeto->StartTransform(). A continuación hay que utilizar este nuevo método: $objeto->setTextRenderingMode(borde, relleno, recorte)

dónde borde es un valor numérico que indica el espesor de la línea que dibuje el borde de las letras. Si se especifica cero no se visualizarán esos bordes. La opción relleno es un valor booleano que permite especificar si la letra aparecerá rellena en color negro o no. Por último el paramétro recorte, también booleano, ha de configurarse con valor true para que permita recortar la imagen.

Después de aplicar el método anterior hemos de escribir el texto. Para hacerlo utilizaremos el método Write de una forma similar a esta:

$objeto->Write(interlinea, texto)

El paso siguiente sería insertar la imagen con la posición y dimensiones que permitan que texto e imagen se superpongan.

$objeto->Image(imagenb, x, y, ancho, alto, tipo)

Por último, tendríamos que incluir el final de la transformación mediante el ya conocido: $objeto->StopTransform()

En este ejemplo pueden verse la diferentes posibilidades de utilizar elementos gráficos como rutas para el recorte de imágenes tal como acabamos de describir.

ejemplo345.php Ver código fuente

Transparencias y modos de superposición La clase TCPDF dispone del método $objeto->SetAlpha(opacidad,modo)

en el que opacidad es un valor comprendido entre 0 y 1 que indica el grado de opacidad de la imagen o texto. Por defecto su valor es 1 (opacidad total). El segundo parámetro de la función, modo (permite uno de los valores descritos aquí debajo) se comporta de forma muy similar a la utilizada por los programas de retoque fotográfico en la forma de combinar elementos gráficos superpuestos (imágenes, textos, trazados).

Page 346: Manual PHP

Normal: No hay interaccion entre las imágenes superpuestas. El resultado será la consecuencia del grado de opacidad de cada una de ellas. Multiply: El valor de los píxeles de la imagen de fondo se multiplica por el valor de los píxeles de la capa superpuesta y el resultado se divide entre 255. Generalmente se obtiene un oscurecimiento de la imagen. Screen: Realiza el efecto contrario a Multiplicar (usa el inverso de todos los valores para realizar la operación) por lo que obtenemos una imagen mucho más clara que la original. Overlay: Los colores brillantes de la imagen de fondo se dividen entre los de la capa superpuesta mientras que los colores oscuros se multiplican. El resultado será una superposición de los colores de ambas capas, conservándose las luces y las sombras de la imagen de fondo. Darken: Producirá una imagen más oscura como resultado de la comparación que realiza el programa entre los píxeles de cada imagen, mostrando los más oscuros de cada una. Lighten: Se comporta de forma idéntica a la anterior pero ahora mostrando los colores más claros de cada imagen. Color Dodge: Este efecto es similar al que se produciría si en fotografía tradicional diésemos un tiempo de exposición prologando a la fotografía. Color Burn: Produce un efecto similar a la subexposición de la fotografía tradicional Soft Light: Produce un efecto similar a iluminar la imagen con un foco de luz difusa y suave. Hard Light: Igual que el anterior pero iluminando con una luz más dura. Difference: Rellena parte de la imagen inferior con partes de la superpuesta. Generalmente se sustraerá color de la capa con el color más brillante en el punto que se examine Exclusion: Produce efectos similares que Diferencia pero con un contraste menor. Hue: Mezcla el valor de Tono de la imagen con el de saturación y brillo de la imagen inferior. Saturation: Mezcla el valor de saturación del color de la imagen superpuesta con los valores de brillo y tono de la imagen de fondo. Color: Mezcla el valor de saturación de color de la imagen superpuesta con el valor de brillo de la imagen inferior. Luminosity: Produce el efecto inverso que Color. Mezcla los valores de saturación y tono de la imagen inferior con el valor de brillo de la imagen superpuesta.

ejemplo346.php Ver código fuente

Calidad de las imágenes

En el caso de incluir imágenes en formato JPG es posible establecer el grado de compresión de las mismas. Por defecto la clase establece un valor del 75%. Si pretendemos modificarlo podremos utilizar el método:

$objeto->setJPEGQuality(porcentaje) Encabezados, pies de página y códigos de barras

Encabezados y pies de página

Existen dos opciones posibles de incluir encabezados y pies de página en un documento. La primera de ella sería utilizar el método incluido en la propia clase. Los resultados que produce son bastante pobres y limitados. No obstante resumiremos su funcionamiento.

Con carácter general deberemos tener en cuenta lo siguiente:

$objeto->setPrintHeader ( true ) y $objeto->setPrintFooter ( true )

han de estar configuradas como true (su opción por defecto) para permitir que se visualicen los encabezados y pies de página.

$objeto->setHeaderMargin(numero)

debe tener asignado un valor numérico (la distancia del borde superior de la página al punto donde empezará la inserción de los elementos del encabezado). Caso de no establecerlo tomaría valor cero y el encabezado se situaría con margen cero («a sangre») respecto al borde superior del papel.

$objeto->setFooterMargin(numero)

debería estar establecido ya que con su valor por defecto (cero) empezaría a escribir el pie de página debajo de borde inferior del papel. El número positivo que incluyamos en este método establecerá a que distancia del borde inferior empieza a presentarse el pie de página.

Page 347: Manual PHP

$objeto->setHeaderFont(array(nombre,estilo,tamaño))

permite establecer como elementos del array escalar: el nombre de letra nombre ('times', por ejemplo), el estilo estilo (podría ser algo como 'I' italic) y el tamaño tamaño expresado en las mismas unidades con que se definen las dimensiones de la página.

$objeto->setFooterFont(array(nombre,estilo,tamaño))

Se comporta exactamente igual que el anterior solo que en este caso se establece la tipografía para los pies de página.

$obj->setHeaderData(logo, ancho, titulo, subtitulo)

Mediante este método podremos indicar el nombre de una imagen (logo), el ancho que pretendemos que ocupe esa imagen ancho, una cadena de texto titulo que será el título del encabezado y subtitulo una línea de texto que iría debajo de la anterior.

El pie de página se incluye de forma automática y estará formado número de la página y el número total de páginas del documento separadas por una barra. En ambos casos se dibujaría una raya horizontal delimitadora del encabezado y pie de página que ocupa todo el espacio entre márgenes. Los pies de página se alinearían al margen derecho y los encabezados al margen izquierdo.

ejemplo347.php Ver código fuente

¡Cuidado! Salvo que hagamos una modificación en el fichero de configuración (o hagamos cambios en el fichero tcpdf.php que contiene la clase), la imagen que incluyamos en el encabezado deberá estar en /images/ dentro del directorio tcpdf. Para alterar esa ruta sería necesario modificar el fichero de configuración.

Clase extendida para encabezados y pies de página

La configuración «a medida» de encabezados y pies de página requiere la creación de una clase extendida de la clase TCPDF en la que el carácter polimórfico de las clases nos va a permitir redefinir los métodos Header() y Footer() que son los que incluyen los encabezados y los pies de página. Los objetos de la nueva clase extendida utilizarían los métodos incluidos en ella en lugar de los establecidos en la clase padre.

Aquí tienes un ejemplo de aplicación de una clase extendida.

<?php /*incluimos el fichero con las rutas y los include de las clases necesarias tal como venimos haciendo el los ejemplos anteriores */ include("tcpdf.inc.php"); /* empezaremos creando una clase extendida de TCPDF que permitirá reescribir las funciones Header y Footer incluidas en el constructor de la clase TCPDF. Estas nuevas funciones sustituiran a las preexistentes */ class ConPies extends TCPDF { public function Header() { /*ponemos color al texto y a las lineas */ $this->SetTextColor(0,0,200); $this->SetDrawColor(255,0,0); /* definimos variables con titulo y subtitulo */ $titulo="Adios cordera"; $subtitulo="Leopoldo Alas (Clarin)"; /* posicionamos el puto de insercion 2mm. debajo del borde del papel */

Page 348: Manual PHP

$this->SetY(2); /* escribimos el titulo con la fuente que se establezca por el método opcion SetHeaderFont */ $this->Cell(0, 5,$titulo,0,1,'C'); /* modificamos tipografia para el subtitulo e insertamos este */ $this->SetFont('helvetica', 'I', 7); $this->Cell(0, 2,$subtitulo,0,1,'C'); /* trazamos un rectangulo sombreado que por sus dimensiones ocupará el area de texto de la pagina */ $this->Rect(15,15,180,267,'F','',array(240,240,240)); /*trazamos una linea roja debajo del encabezado */ $this->Line(15,15,195,15); /* insertamos una imagen de fondo con 15% de opacidad */ $this->SetAlpha(0.15,'Normal'); $this->Image('./images/cordera2.jpg',55,84,100,104, '','','N','','','C'); /* recuperamos la opacidad por defecto */ $this->SetAlpha(1,'Normal'); } public function Footer() { /* establecemos el color del texto */ $this->SetTextColor(0,0,200); /* insertamos numero de pagina y total de paginas*/ $this->Cell(0, 10, 'Pagina '.$this->getAliasNumPage(). ' de un total de '. $this-> getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M'); $this->SetDrawColor(255,0,0); /* dibujamos una linea roja delimitadora del pie de página */ $this->Line(15,282,195,282); } } /* creamos un nuevo objeto usando la clase extendida ConPies */ $MiPDF = new ConPies(); /* definimos margenes de encanbezado y pie de pagina, tipos de letra para encabezados y piel */ $MiPDF->setHeaderMargin(2); $MiPDF->setFooterMargin(15); $MiPDF->setHeaderFont(Array('times', '', 14)); $MiPDF->setFooterFont(Array('helvetica', 'I', 10)); /* fijamos el modo de visualizacion, agregamos dos paginas y visualizamos el resultado */ $MiPDF->SetDisplayMode('fullpage'); $MiPDF->AddPage(); $MiPDF->AddPage(); $MiPDF->Output(); ?>

ejemplo348.php Ver código fuente ejemplo349.php Ver código fuente

Códigos de barras

Códigos de barras lineales

TCPDF dispone de una función que permite incluir códigos de barras en los documentos PDF. El método es el siguiente:

Page 349: Manual PHP

$objeto->write1DBarcode ( codigo, tipo, x, y, ancho, alto, xres, estilo, alineacion)

Valor Utilidad

codigo Son los dígitos y/o caracteres que van a representarse en formato de código de barras

tipo Ha de tener como valor una ds estas cadenas:C39, C39+, C39E, C39E+, C93, S25, S25+, I25,I25+, C128A, C128B, EAN2, EAN5, EAN8, EAN13, UPCA, UPCE, MSI, MSI+, POSTNET, PLANET, RMS4CC, KIX, IMB, CODABAR, CODE11, PHARMA, PHARMA2T. Representan algunos de los múltiples formatos de codificación existentes en la actualidad.

x, y Son las coordenadas del puntos de inserción de la esquina superior izquierda del rectángulo contenedor del código de barras.

ancho, alto Son los valores del ancho y el alto del rectángulo contenedor

xres Es el ancho de la barra más estrecha expresada en las mismas unides con las que se define el tamaño del documento. Por defecto su valor es 0,4 mm.

estilo

Es un array asociativo que puede contener los siguientes elementos:

Elemento Valor Utilidad

'border' booleano Indica si ha de ponerse borde al rectángulo contenedor del código de barras

'padding' numero Establece la distancia que ha de separar la barras del los márgenes del contenedor. Además de valores numéricos admita la opción 'auto'.

'hpadding' numero Establece la distancia que ha de separar la barras del los bordes laterales contenedor. Además de valores numéricos admita la opción 'auto'.

'vpadding' numero Establece la distancia que ha de separar la barras del los bordes superior e inferior del contenedor. Además de valores numéricos admita la opción 'auto'.

'fgcolor' array Es una array escalar formado por los valores de los componentes de color R, G, B del color que se utilizará para las barras y los textos

'bgcolor' array Es una array escalar formado por los valores de los componentes de color R, G, B del color que se utilizará rellenar el fondo de rectánulo contenedor

'text' booleano Cuando se le asigna el valor true se escribirá una cadena de texto con los valores del código de barras

'font' nombre Permite definir el nombre de la fuente que se utilizará para presentar los textos. Si se omite se utilizará la fuente por defecto.

'fontsize' booleano Establece el tamaño de la fuente

'label' texto El valor que se incluya en este elemento se incluirá como texto debajo del código de barras

'stretchtext' numero

Si su valor es cero (0) no se producen modificaciones en la escala de los textos. Si se asigna valor uno (1) forzaría la escala horizontal de los textos si fuera necesario para que cupiera dentro del rectángulo contenedor. Cuando el valor asignado sea dos (2) forzaría la escala horizontal, ampliando o reduciendo, para adaptar el texto al espacio disponible. Si los valores son tres(3) o cuatro (4) el comportamiento sería similar a los anteriores (1 o 2) modificando el espacio entre caracteres en vez de ampliar o reducir el tamaño de los mismos.

'position' L,C,R Establece la posición del código de barras respecto a su rectángulo contenedor

'align' L,C,R Posiciona el rectángulo contenedor respecto a los márgenes laterales de la página

'stretch' booleano Cuando está configurado como true adapta el ancho del código al espacio contenedor. Si su valor es false se usa el ancho de barras establecido por el parámetro xres

'fitwidth' booleano Si se asigna el valor de true adapta el ancho del rectángulo contenedor al espacio requerido por el código de barras y los márgenes laterales (padding)

'cellfitalign' booleano Esta opción sólo funciona cuando fitwidth es true y "position no está definido o está vacío. Ajuste la posición horizontal de la celda que contiene código de barras en el interior del rectángulo especificado: L = izquierda, C = centro, R = derecha.

alineacion Establece la posición del punto de inserción después de haber incluido el código de barras. Admite como valores T, M, B, N que se refieren a las partes: superior derecha, centro derecha,inferior derecha o nueva línea siempre respecto al código de barras actual.

En este ejemplo pueden verse las diferentes posibilidades de este tipo de códigos de barras.

ejemplo350.php Ver código fuente

Códigos de barras 2D

La clase TCPDF también permite escribir códigos de barras del tipo 2D utilizando el método:

$objeto-> write2DBarcode ( codigo, tipo, x, y, ancho, alto, estilo, alineacion, distorsion )

de gran similitud al descrito anteriormente aunque con algunos elementos diferenciados en lo que se refiere a tipo, estilo y distorsion.

Page 350: Manual PHP

Este formato de códigos admite como tipos alguno de los siguientes: RAW, RAW2, QRCODE,L, QRCODE,M, QRCODE,Q, QRCODE,H,QRCODE,L o PDF417.

Por lo que respecta al estilo se define, igual que en el caso anterior, mediante un array asociativo cuyos elementos son: 'border','padding', 'hpadding', 'vpadding', 'fgcolor', 'bgcolor', 'position' con comportamientos idénticos a los descritos en el párrafo anterior y también'module_height' y 'module_width' que permiten establecer mediante valores numéricos el alto y el ancho (expresado en puntos) de un módulo simple.

El parámetro distorsion es un valor booleano. Cuando se le asigna true puede distorsionar el código de barras para encajarlo con la anchura y la altura. Si el valor es false (valor por defecto) preservar la relación de aspecto.

En el ejemplo siguiente tienes comentadas algunas de las diferentes opciones de esta función.

ejemplo351.php Ver código fuente

Formularios en PDF

Formularios en PDF

En páginas anteriores hemos tratado del tema relativo a los formularios como medio de intercambio de información entre el cliente y el servidor. La clase TCPDF nos permite desarrollar formularios en formato PDF con algunas ventajas comparativas respecto a los tradicionales. Algunas de ellas son las siguientes:

� - Los archivos PDF que contienen los formularios interactivos podrían estar residente en el cliente o ser descargados con disponibilidad plena sin conexión a internet. En el caso de los archivos HTML, cada vez que sean invocados por el cliente deben ser descargados desde el servidor.

� - La apariencia de los formularios interactivos en PDF es la misma independientemente del browser que se utilice. Esto no ocurre con HTML, donde las preferencias del browser inciden en la manera de visualización de los formularios.

� - Los documentos PDF pueden hacer uso de la opción de firma digital, que garantiza la autenticidad de la autoría de un documento y el manejo de opciones de seguridad.

� - Los documentos PDF incluyen todos los elementos necesarios para la visualización, tales como imágenes, tablas, etc. Los documentos HTML usualmente deben incluir archivos anexos que pueden no funcionar de manera adecuada en el momento de la descarga.

� - La navegabilidad de los documentos PDF se garantiza de manera automática desde su creación. En HTML la navegabilidad se debe construir desde el código con hipervínculos o creando marcos (frames) en los documentos, los cuales presentan dificultades de visualización en algunos browser.

Los métodos que permiten incluir los distintos componentes de los formularios son los siguientes:

Cajas de texto

Este tipo de elemento puede ser utilizado de forma que cubra las necesidades de las entradas de texto, las entradas tipo password, las áreas de texto y también las tipo file. El método que permite insertarlas es el siguiente:

$objeto->TextField(nombre, ancho, alto, propiedades, anotaciones, x, y)

Valor Utilidad

nombre Es el nombre identificador del elemento que servirá para asignar nombre a la variable que recoja su contenido cuando el formulario sea transferido al servidor.

ancho, alto Son los valores del ancho y el alto del rectángulo contenedor. (**)

x, y Son las coordenadas del puntos de inserción de la esquina superior izquierda del rectángulo contenedor del área de texto.

propiedades

Es un array asociativo que puede contener, entre otros, los siguientes elementos:

Elemento Valor Utilidad

'required' booleano Indica si ha de cumplimentarse obligatoriamente (true) o si no es requerido necesariamente (false). Su valor por defecto es false.

'value' cadena Permite asignar un valor por defecto al elemento. Si no se incluye se cargará una cadena vacía.

Page 351: Manual PHP

'password' booleano Cuando este valor se establece como true al teclear contenidos en el campo se visualizarían los típicos asteriscos de los campos tipo password.

'readonly' booleano Permite, cuando se le asigna el valor true, impedir la modificación de los valores asignados por defecto.

'charLimit' numero Permite limitar el número máximo de caracteres susceptibles de ser incluidos en ese campo.

'multiline' booleano Cuando este valor se configura como true permite múltiples líneas y su comportamiento es idéntico al de los textarea del HMTL.

'fileSelect' booleano Cuando se configura como true permite seleccionar ficheros de forma similar a los formularios HTML. Requiere ser complementado con un botón (en HTML se insertan simultáneamente) mediante el cual se ejecutará la acción que permita elegir el fichero a incluir.

'lineWidth' numero Permite especificar el ancho de la línea que contornea el elemento. (*)

'borderStyle' cadena Permite asignar un estilo al borde del elemento. Si se asigna la cadena 'solid' (valor por defecto) contorneará con una línea continua. Si se asigna 'dashed' lo hará con línea discontinua. (*)

'fillColor' array Permite establecer un color de fondo asignado mediante un array definido como array(R,G,B) dónde R, G y B son los valores de las componentes de los colores primarios. (*)

'strokeColor' array Permite establecer un color de fondo asignado mediante un array definido como array(R,G,B) dónde R, G y B son los valores de las componentes de los colores primarios. (*)

anotaciones Permite incluir anotaciones de acuerdo con las especificaciones PDF32000_2008. Para nuestros fines no resulta necesario. Bastará que incluyamos este parámetro en forma de un array vacío: array()

(*) Este valor pueden asignados de forma global para todos los elementos del documento incuyéndolo el método setFormDefaultProp. (**) Si se asigna cero a los valores ancho y alto no se visualizará el campo. Si se le asignara un value podría emular perfectamente los campos ocultos (hidden) de los formularios HTML.

Botón de opción

Este tipo de elemento puede ser utilizado de forma idéntica a la contemplada en los formularios HTML. El método que permite insertarlo es el siguiente:

$objeto->RadioButton( nombre, ancho, propiedades, anotaciones, valor, marcado, x, y)

El parámetro nombre permite identificar el elemento (y el valor de la variable al ser enviado el formulario) y ha de ser el mismo en los diferentes elementos que conforman las opciones de elección. Mediante ancho especificamos el ancho del botón de opción, las propiedadesen este caso se limitan a las características gráficas ya señaladas para las cajas de texto. Las anotaciones tienen una funcionalidad y comportamiento similares (también aquí podemos incluir un array vacío). El parámetro valor permite especificar el valor que se asignaría a la variable nombre en el caso de ser seleccionada la opción mientras que marcado es un valor booleano que en caso de estar configurado como true haría que la opción aparezca seleccionada por defecto. Los parámetros x e y son las coordenadas del punto de inserción del elemento.

Casillas de verificación

También se comportan de forma idéntica a los formularios HTML. El método que permite insertarlos es el siguiente:

$objeto->CheckBox(nombre, ancho, marcado, propiedades, anotaciones, valor, x, y)

Utiliza los mismos parámetros, y con idénticas funcionalidades, que el botón de opción. Las dos únicas diferencias a tener en cuenta es que se incluyen en distinto orden y que las casillas de verificación deben tener siempre nombres diferentes.

Cuadro de lista

Presenta un cuadro de lista desplegando las diferentes opciones que pueden ser seleccionadas. Si se configura adecuadamente pueden hacerse una selección múltiple. El método que permite insertarlo es el siguiente:

$objeto->ListBox(nombre, ancho, alto, valores, propiedades, anotaciones, x, y)

Casi todos los parámetros son los ya conocidos. Las diferencias respecto a lo comentado para el caso de cajas de texto se refieren a los valores y a las propiedades. En caso de los valores deberemos incluir un array escalar que contenga como valores las

Page 352: Manual PHP

diferentes opciones posibles de la lista. En cuanto a propiedades pueden incluirse lo elementos de ese array asociativo relativos a la configuración gráfica y, además, un nuevo elemento: 'multipleSelection' un valor booleano que en caso de tener asignado el valor true permite seleccionar varios items de la lista (bastaría marcarlos manteniendo pulsada la tecla Ctrl).

Cuando se utiliza esta opción es muy importante tener la precaución de agregar unos corchetes al nombre del elemento. Por ejemplo:aficiones[] en vez de aficiones. De esta forma cuando los datos sean enviados al servidor PHP interpretará la variable como un array y podrá extraer de ella los diferentes valores seleccionados. Si se omitieran los corchetes PHP solo recogería el último de los valores seleccionados.

Si en el array propiedades incluyeramos un elemento con nombre 'value' y el asignáramos como valor una de las opciones ocurriría que al ser cargado el formulario aparecería seleccionada ese valor de forma automática.

Cuadro desplegable

Este tipo de elemento puede ser utilizado de forma idéntica a la contemplada en los formularios HTML. El método que permite insertarlo es el siguiente:

$objeto->ComboBox(nombre, ancho, alto, valores, propiedades, anotaciones, x, y)

Se comporta de forma muy similar a la descrita en el elemento anterior. Como es lógico no permite la propiedad multipleSelection. De igual forma que ocurría en aquel caso si se incluye la propiedad value aparecerá seleccionado el elemento correspondiente de la lista.

Respecto al array que contiene los valores caben dos posibilidades de inserción. La primera es la ya descrita para el caso anterior:(array('valor1','valor2','valor3')).

Alternativamente pueden usarse formas del tipo ( array (array ('item1','valor1'),array('item2','valor2'),array('item3','valor3') ) en la que en vez de los valores de los elementos se incluye un array de dos elementos por cada uno de ellos. El primer elemento de cada uno de esos arrays será el texto que aparecerá en el formulario y el segundo el valor que habría de asignarse al ser elegida esa opción. Es decir, se visualizaría item2 y se enviaría al servidor valor2 que es el valor asociado a el.

Botones

Los botones son elementos imprescindibles en el caso del manejo de formularios. El método que los incluye es este:

$objeto->Button(nombre, ancho, alto, caption, accion, propiedades, anotaciones, x, y)

Los parámetros nombre, ancho, alto, propiedades, anotaciones, x e y tienen idéntico significado que en casos anteriores (las propiedades se reducen a sus aspectos gráficos). Incorpora como elementos nuevos el parámetro caption que será una cadena con el texto que aparecerá en el propio botón y el parámetro accion que podrá incluir una llamada a una función de JavaScript o un array asociativo. Estos son algunos ejemplos del primer supuesto:

$objeto->Button('imprime', 30, 10, 'Imprimir', 'print()', $estilo, array() , 30, 265);

insertaría un botón con el estilo definido en $estilo en el que se visualizaría la palabra Imprimir y que al ser pulsado ejecutarla la función print() del sistema del usuario.

$objeto->Button('almacena', 30, 10, 'Imprimir', 'guardar()', $estilo, array() , 30, 265);

insertaría un botón con el estilo definido en $estilo en el que se visualizaría la palabra Guardar y que al ser pulsado ejecutarla la función JavaScript guardar() en el navegador del usuario. Las funciones JavaScript debe estar incluida en el propio documento pdf. Para ello la clase TCPDF dispone del método:

$objeto-> IncludeJS($texto_con_codigo_JavaScript)

Page 353: Manual PHP

dónde la variable $texto_con_codigo_JavaScript contiene el código JavaScript que se pretende incluir en el documento. En el supuesto anterior podría ser una función tal como esta: function guardar(){app.execMenuItem("SaveAs");}

Cuando se trata de enviar el formulario es necesario recurrir a un botón con las siguientes características:

$objeto->Button('submit', ancho, alto, 'Enviar', array('S'=>'SubmitForm','F'=>'xxxxx.php','Flags'=>array('ExportFormat','IncludeNoValueFields')), $estilo, array() , x, y );

en cuyo array asociativo son obligatorios los elementos: 'S' con valor 'SubmitForm' (que hará el envío del formulario); 'F' cuyo valor se la dirección del script que recogerá los resultados del envío 'xxxxx.php' (equivale a action en el formulario convencional de HTML) y elemento'flags' ha de incluir un array conteniendo los valores 'ExportFormat' y 'IncludeNoValueFields'. De no hacerlo no se recibirían en el servidor los contenidos del formulario.

Cuando se trata de borrar el contenido del formulario es necesario recurrir a un botón con las siguientes características:

$objeto->Button('reset', ancho, alto, 'Borrar', array('S'=>'ResetForm'), $estilo, array() , x, y );

en cuyo array asociativo es obligatorio el elemento: 'S' con valor 'ResetForm' (que borrará los contenidos del formulario).

Ficheros adjuntos

El envío de ficheros adjuntos requiere el uso de tres elementos: un campo de texto, un botón y una función JavaScript.

$objeto->TextField( 'nombre_campo',ancho,alto,array('fileSelect'=>true),array(),x,y)

el campo de texto requiere la opción 'fileSelect'=>true

$objeto->Button( 'Sube',ancho,alto,'Seleccione el archivo', "Cargar('nombre_campo')", $estilo_boton)

el botón debe incluir el la llamada una función JavaScript en la que debemos incluir como argumento una cadena que ha de coincidir con el el nombre_campo asignado como nombre al campo de texto.

function Cargar(nombre){ var selecciona = this.getField(nombre); selecciona.browseForFileToSubmit(); }

la funcion JavaScript tal como la estás viendo permite seleccionar un nombre de fichero y que de forma automática se incluya su nombre y su ruta en el campo de texto. El comportamiento es idéntico al caso de enviar ficheros desde los formularios HTML.

En el código fuente de este ejemplo puedes ver como hemos incluido las funciones JavaScript en una variable usando la sintaxis de documento incrustado.

ejemplo352.php Ver código fuente

Inclusión de ficheros y firma digital

Inclusión de ficheros

Otra de las posibilidades que ofrece la clase TCPDF es la inclusión de anotaciones y ficheros incrustados en el propio documento PDF. Para ello se utiliza el método siguiente:

Page 354: Manual PHP

$Objecto->Annotation(x, y, ancho, alto, texto, array('Subtype'=>'FileAttachment', 'Name' =>'icono', 'FS'=>'nombre_fichero '))

dónde x e y son las coordenadas del punto dónde se insertará el icono que advierte de la existencia de una anotación. Los valoresancho y alto especifican las dimensiones de ese icono. El parámetro texto permite incluir un texto alternativo que si visualizaría cuando se colocara el puntero del ratón sobre el icono. Cuando se incluye ese texto aparecerá a la derecha del icono una advertencia en forma de «bocadillo de cómic». Si ese texto se deja como cadena vacía no se visualizará esta última marca.

El array que se incluirá como sexto parámetro es de tipo asociativo. Su primer elemento, Subtype, tendrá como valor FileAttachement(alude a la inclusión de un fichero adjunto), el elemento cuyo índice es Name especificará en su valor el tipo de icono que se visualizará en el documento. Son válidos los tipos: Graph, Paperclip Tag y PushPin. El elemento de índice FS tendrá como valor el nombre y la extensión del fichero que incluirá en el documento. Como es lógico tal fichero ha de estar ubicado en un lugar accesible por el servidor.

¡Cuidado! La descarga de ficheros adjuntos (pulsando con el botón derecho del ratón sobre el icono) está restringida en Adobe Reader por razones de seguridad. Ficheros ejecutables, comprimidos, etcétera no se abren en prevención de que pudieran contener elementos maliciosos.

Cuando se trata de incluir únicamente una nota de comentario puede utilizarse el método anterior con la sintaxis siguiente:

$Objecto->Annotation(x, y, ancho, alto, texto, array('Subtype'=>'Text', 'Name' =>'Comment', 'T'=>titulo, 'C' => array(255, 255, 0)))

las modificaciones respecto al supuesto anterior son: la modificación del valor del elemento Subtype que ahora toma Text como valor; la modificación de valor del elemento Name que ahora pasa a tener como valor Comment; la inclusión del elemento T al que podremos asignar como valor el título de comentario y la posibilidad de añadir un color al icono de comentario mediante el elemento C al que se asigna como valor un array cuyos tres elementos son las componentes R, G, B del color que pretende aplicarse.

Firma digital de los documentos

Otra de posibilidades de la clase TCPDF es la firma digital de los documentos generados. Con ello, si el servidor dispone decertificados digitales (no es necesario que esté configurado en modo seguro) los ficheros pdf pueden ser firmados de forma que al ser leidos por el cliente a través de Adobe Reader pueda verificar la no manipulación y la autenticidad de la firma.

Para efectuar la firma de un documento necesitaremos acceder al certificado digital de servidor y también su clave privada. Si no hemos efectuado la instalación en modo seguro del servidor Apache y no disponemos de los certificados allí utilizados podemos utilizar a efectos de pruebas uno que viene incluido con la distribución de TCPDF, dentro del directorio tcpdf, con nombre tcpdf.crt y cuya contraseña estcpdfdemo. El método que permite incluir la firma digital del servidor es el siguiente:

$objeto->setSignature( certificado, clave_privada, contraseña, extras, tipo, informacion)

dónde certificado es una cadena que comienza por file:// y va seguida de la ruta y nombre del fichero que contiene el certificado que va a ser utilizado para facilitar la comprobación de la firma del documento; clave_privada es la clave privada del firmante (también debe indicarse mediante file:// y va seguida de la ruta y nombre del fichero). La contraseña será la contraseña de uso de la clave privada.

Si utilizamos el certificado de servidor mencionado en este enlace esa contraseña será una cadena vacía ya que, tal como puedes recordar si visitas el enlace anterior, al crear la clave privada del servidor (la función genrsa de OpenSSL) habíamos omitido el modificador -des3 precisamente para que tal contraseña no fuera requerida.

Como parámetro extras vamos a utilizar una cadena vacía ya que nuestro certificados carecen de esa información complementaria. Como parámetro tipo podemos incluir 1 (cualquier cambio en el documento invalidará la firma), 2 (se permite la cumplimentación de formularios sin invalidar la firma) o 3 (no invalida la firma ni la cumplimentación de formularios ni la inclusión, modificación o eliminación de anotaciones).

Page 355: Manual PHP

Por último, informacion es un array asociativo cuyos elementos tienen por índices: 'Name', 'Reason' , 'Location' y 'ContactInfo' y contendrán como valores cadena conteniendo datos informativos sobre el titular del certificado digital utilizado. Esos valores podrán ser visualizados por el cliente de la forma que veremos en el párrafo siguiente.

Aunque no resulta elemento imprescindible es habitual incluir en el documento algún tipo de imagen o símbolo que sugiera que el documento incluye una firma digital. Puede ser cualquier elemento gráfico. Cuando se ejerce esa opción suele complementarse con la definición de un área activa en el documento (normalmente la que ocupa el gráfico que simboliza la firma) de modo que al pulsar con el ratón sobre esa zona se abra una ventana que facilite la información aludida en el párrafo anterior.

En los ejemplos que incluimos a continuación tienes una muestra del uso de anotaciones y firma digital. En el primero de ellos utilizaremos el certificado demostración incluido con TCPDF y en el segundo haremos uso del certificado al que hemos hecho mención encertificado de servidor mencionado en este enlace. Si no dispones de esos certificados o si la ruta no coincide el segundo de los ejemplos te dará error.

ejemplo353.php Ver código fuente ejemplo354.php Ver código fuente

¡Cuidado! En el ejemplo anterior habrás observado que en el caso de que el sistema operativo sea Ubuntu hemos aplicado la funcion realpath() a la ruta hasta el certificado y la clave privada. De no hacerlo así la ejecución del script produce un error. La funcion realpath(ruta) devuelve el nombre de la ruta en forma de ruta absoluta canónica que tiene un valor único y que no siempre coincide con la ruta absoluta que suele depender del sistema operativo en uso.

Tal como habrás podido observar a mirar el código fuente, en este ejemplo he intentado incluir algunos fichero incrustados y a la vez crear un documento firmado con certificado digital, guardando el pdf resultante en el servidor –con un nombre creado de forma aleatoria– e incluyendo en el propio fichero la ruta hasta el mismo. Una vez creado el fichero se abriría de forma automática en el navegador del cliente que podría guardarlo en su equipo y/o imprimirlo.

La firma digital es facilmente comprobable en un documento digital pero es evidente que esa comprobación es imposible en una copia impresa del documento. Al guardarlo en el servidor e incluir la dirección estaremos facilitando la posibilidad de comprobar la fidelidad de una copia a cualquiera que tenga acceso a ella.

Un ejemplo de aplicación

Incluimos un ejemplo que intenta mostrar en alguna medida las posibilidades de esta herramienta emulando una ventanilla de registro clásica. Al ejecutar el script se carga un formulario que, una vez cumplimentado e incluidos los ficheros adjuntos solicitados, es enviado al servidor, donde se bloquean los campos del formulario y se agregan unos textos que emulan el sello de registro de entrada (en caso de identificación mediante certificado electrónico podría incluir los datos de este). También se incrustan en el documento, formando un todo con él, los ficheros adjuntos. El fichero resultante se almacena en el servidor (emulando el original para la administración) y se devuelve al navegador una copia completa del mismo.

ejemplo355.php Ver código fuente

Creación de índices de contenidos y fuentes

Indices de contenidos

Otras de las múltiples posibilidades de la clase TCPDF es la inclusión de marcadores y la confección automática de listas de contenidos. Los marcadores, como podrás ver en el ejemplo, pueden incluirse de modo que el índice resultante pueda tener distintos niveles (capítulos, apartados, sub apartados, etcétera). El método que permite la elaboración de ese tipo de enlaces es el siguiente:

$objeto->Bookmark(texto, nivel, posicion, pagina, estilo, color)

Page 356: Manual PHP

dónde texto es una cadena que contiene el texto que aparecerá en el índice del documento, nivel será un número entero que indicará a qué nivel pertenece la marca. Cero significará primer nivel (por ejemplo TEMA), uno significará segundo nivel (en el índice «colgará» del de primer nivel anterior a él) y así sucesivamente. El parámetro posicion admite como valores 0 y -1. Si se especifica 1 al pulsar sobre el enlace el navegador se punto de inserción en la parte superior de la ventana contenedora del documento. Si ese valor es 0 se situará en el borde superior de la página que contiene el marcador.>

El parámetro pagina permite especificar la página a la que nos redirigirá el enlace. Si se incluye una cadena vacía (lo habitual) nos redirigirá a la página que incluye el marcador actual. El parámetro estilo admite como valores 'B' (negrita), 'I' o una cadena vacía (normal) y permite especificar el estilo del texto (en índice del documento) con que se visualizará el enlace hacia la posición señalada por el marcador. Por último el parámetro color es un array que permite indicar en modo R,G,B el color del texto de los enlaces.

En este ejemplo puedes ver una muestra de las posibilidades de esta opción.

ejemplo356.php Ver código fuente

Fuentes tipográficas

Para generar las nuevas fuentes es necesario disponer del programa pfm2afm.exe que viene incluido en el paquete tcpdf. Concretamente en el directorio /tcpdf/fonts/utils

Creación del fichero .afm

Este fichero, cuya extensión es el acrónimo de Adobe Font Metric contiene las información sobre ancho, alto y kerning de cada carácter de una fuente. El primer paso la utilización de una nueva fuente ha de ser la creación de ese fichero. El procedimiento requiere lo siguiente:

� – Copiar la fuente que pretendemos transformar al directorio C:/ServidoresLocales/Apache/htdocs/php/tcpdf/fonts/utils que es el que contiene el programa pfm2afm

� – Acceder a la ventana de MS-DOS situarnos en el directorio C:/ServidoresLocales/Apache/htdocs/php/tcpdf/fonts/utils y ejecutar el siguiente comando: ttf2ufm -a nombre_de_la_fuente_TrueType.ttf nombre_del_nuevo_fichero donde nombre_de_la_fuente_TrueType.ttf es el nombre y extensión de la fuente que acabamos de incluir en ese directorio ynombre_del_nuevo_fichero una palabra cualquiera que permita asignar nombre a los nuevos ficheros. Por razones de comodidad lo aconsejable será utilizar en mismo nombre de la fuente TrueType sin extensión.

¡Cuidado! En el caso de Ubuntu el proceso es idéntico. Bastará con abrir el terminal, situarse en el directorio fuentes. Desde allí se ejecuta el comando ttf2ufm de forma idéntica a la descrita para MS-DOS.

Definición de las fuentes

El directorio C:/ServidoresLocales/Apache/htdocs/php/tcpdf/fonts/utils incluye un script llamado makefont.php. La definición de fuentes requiere la ejecución de una de las funciones que contiene ese script llamada MakeFont() y que utiliza los siguientes parámetros:

MakeFont( fuente_truetype, fuente_afm, incrustado, codificacion, modificadores )

donde fuente_truetype es el nombre, extensión y path de la fuente TrueType a utilizar (fichero .ttf), fuente_afm es el nombre del fichero.afm creado para esta misma fuente mediante ttf2ufm. Estos dos parámetros son obligatorios.

Mediante el parámetro incrustado (por defecto true) se especifica si la fuente va a ser incrustada en los documentos pdf que la utilicen. El parámetro codificación permite especificar el mapa de fuentes a utilizar. Si no se indica toma el valor por defecto que es cp1252. Por último el paramétro opcional modificadores permite cambiar la codificación de algunos caracteres.

Page 357: Manual PHP

La ejecución de la función MakeFont() comentada anteriormente podría hacerse mediante un script similar a este:

<?php # incluimos el fichero makefont.php que está # en la ruta que se especifica (si lo tuvieramos en otra # ubicación bastaría cambiar el path del include include($_SERVER['DOCUMENT_ROOT']."/php/tcpdf/fonts/utils/makefont.php"); # ejecutamos la función MakeFont indicando como parametros # la ruta y el fichero ttf a convertir(la fuente trueType) # y como segundo parámetro la ruta y el nombre del fichero afm # creado en el proceso anterior MakeFont($_SERVER['DOCUMENT_ROOT']."/php/tcpdf/fonts/utils/bobcayge.ttf", $_SERVER['DOCUMENT_ROOT']."/php/tcpdf/fonts/utils/bobcayge.afm")){ ?>

cuya ejecución de ese script daría como resultado la creación en el directorio actual (es decir en el que contenga el script anterior) de dos ficheros con el mismo nombre y extensiones .z y .php. para poder usar la nueva fuente es necesarios incluirlos (ambos) en el directorio/tcpdf/fonts/.

Diferentes tipografías

Lo habitual es que entre las fuentes TrueType de un tipo determinado existan varios ficheros. Uno para la fuente normal, otro para la cursiva, etcétera. Cuando se trabaja con este tipo de fuentes y se pretende usar estilos distintos, lo aconsejable es definir una fuente para cada tipo y tratarlos, a la hora de escribir el código de los script como fuentes independientes.

De esta forma podríamos tener parejas de ficheros del estilo: fxx_normal.z y fxx_normal.php; fxx_cursiva.z y fxx_cursiva.php, etcétera. A la hora de utilizarlas se haría como si se tratara de fuentes de distintas familias y habría de evitarse incluir el estilo en SetFont. Por tanto, la sintaxis de la asignación de fuentes habría de ser:

$objeto->SetFont("nombre","", 9);

dejando el valor del segundo parámetro (estilo) como una cadena vacía. La única excepción a esta norma sería el caso del subrayadado que no requiere otra cosa que asignar el valor U al parámetro estilo.

Precauciones con las fuentes

El uso de fuentes externas puede producir efectos indeseados si, por alguna circunstancia, tal como ocurre en los ejemplos la fuente utilizada carece de algunos carácteres (eñes, letras con tilde, símbolos). Conviene tenerlo en cuenta para evitar que la elección de una tipografía pueda afectar a los contenidos del documento.

ejemplo357.php

En estos dos ejemplos pueden verse los resultados -con algunos problemas gráficos- de la sustitución de las fuentes en algunos de los ejemplos de páginas anteriores.

ejemplo358.php ejemplo359.php

Ficheros MariaDB y/o MySQL

Los servidores de bases de datos MariaDB y MySQL

Una de las opciones más interesantes de PHP es la posibilidad de gestionar bases de datos alojadas en equipos remotos. Existen multitud de aplicaciones de servidor de bases de datos (FilePro, dBase, DBM, Microsoft SQL, PostgreSQL, mSQL, InterBase, MariaDB yMySQL, entre otros) y PHP dispone de recursos para el manejo de todas las mencionadas.

Hemos optado por MySQL atendiendo a su disponibilidad en el servidor que aloja este dominio. MySQL utiliza el lenguaje SQL(acrónimo de Structured Query Language, es decir, Lenguaje estructurado de consultas) que hasta el momento ha

Page 358: Manual PHP

sido contemplado como un estandar para gestión de bases de datos. Falta por ver cual será la evolución futura en cuanto a gratuidad y servicio. El cambio de propietarios ha generado una cierta inquietud en ese sentido en muchos de los mentideros de la red. Sea cual fuere esa evolución futura los scripts desarrollados para MySQL son perfectamente compatibles con MariaDB, servidor de bases de datos desarrollado dentro de Open Database Alliance, un consorcio neutral diseñado para convertirse en el centro de la industria de la base de datos de código abierto.

¡Cuidado! Todos los ejemplos que incluimos aquí funcionan sin necesidad de hacer ninguna modificación en servidores de bases de datos MariaDB.

Bases de datos y tablas

Si es este tu primer contacto con el mundo de las bases de datos, quizá sea importante conocer su argot ya que en adelante tendremos que referirnos a tablas, campos, registros e índices y quizá no esté de más repasar un poco esas ideas.

Pensemos en la base de datos como un armario archivador –directorio– que alberga una serie de cajones –tablas– en los que se permite almacenar únicamente documentos de idéntico tipo –registros–. Imaginemos, por poner dos ejemplos, que las solicitudes de matrícula de un centro tienen un formato unificado y se guardan mismo cajón y que, con idéntico criterio, se haga lo mismo con los expedientes de los profesores .

Cada uno de los hipotéticos formularios de los supuestos anteriores tendría las mismas casillas –campos–, con la única diferencia de que los datos contenidos en esos campos –de igual forma, dimensión y tamaño en todos los registros– serían lo único que diferenciaría realmente las solicitudes de matrícula de dos alumnos.

Una oficina bien organizada no se conformaría con esto. A medida que crezca el número de solicitudes de los alumnos irán surgiendo las dificultades para localizar un registro (la ficha de un alumno). Sería una excelente idea organizar el archivo de tal forma que esa localización resulte lo más fácil y rápida posible. Una clasificación alfabética –índice alfabético– o por fecha de nacimiento –otro índice– podría facilitar sustancialmente las cosas. Las bases de datos MySQL disponen de todos esos rescursos.

Cuando hemos instalado MySQL en el directorio C:/ServidoresLocales/mysql y durante el proceso de instalación se creó un subdirectorio llamado data (C:/ServidoresLocales/mysql/data) destinado a albergar todas las bases de datos de nuestro servidor. En el caso de Ubuntu ese directorio es /var/lib/mysql. Cada una de las bases de datos que albergue el servidor estará contenida en un subdirectorio cuyo nombre coincide con el de la propia base de datos.

Tanto en el caso de Windows como en el de Ubuntu, el instalador crea una base de datos llamada mysql y, por tanto un subdirectorio para contenerla. En Windows será C:/ServidoresLocales/mysql/data/mysql mientras que en Ubuntu se trata de /var/lib/mysql/mysql.

Si visualizamos los contenidos de ese subdirectorio podremos ver que mysql contiene una serie de ficheros en los que un mismo nombre se repite con tres extensiones diferentes: .frm, .MYD y .MYI.

El directorio C:/ServidoresLocales/data

Las carpetas del subdirectorio data corresponden a cada una de las bases de datos que contiene

Page 359: Manual PHP

Cada una de las tablas que contiene una base de datos requiere tres ficheros de extensiones .frm, MYD y MYI

Cada fichero con extensión .frm contienen la estructura de una tabla. Es en alguna medida una especie de formulario en blanco oformulario tipo. Los ficheros que tienen extensión MYD contiene los datos y aquellos que acaban .MYI contienen los índices de cada tabla que cumplen una función idéntica a los índices de los libros. Por medio de ellos resulta mucho más rápido encontrar una determinada información y al igual que ocurre con los libros –índice general, onomástico, etcétera– pueden ser varios y de tipos distintos.

La base de datos mysql –creada durante la instalación– incluye una serie de tablas necesarias para la gestión del servidor. Una de ellas (puedes verla en la imagen) es la tabla user –la más importante para nuestros fines– que contiene información relativa a los usuarios del servidor de base de datos tales como: permisos y restricciones de acceso, nombres usuarios, contraseñas, etcétera.

¡Cuidado! El uso de los tres ficheros anteriores, es condición de las tablas tipo MyISAM .Otros tipos, que veremos en temas posteriores, tales como InnoDB almacenan la información con una estructura diferente.

¿Cómo empezar con las bases de datos?

Igual que en el caso del ejemplo de la secretaría, lo razonable será empezar creando nuestros armarios –bases de datos– para que posteriormente podamos ir diseñando los documentos –los campos y la estructura– de cada uno de los tipos de impreso (tablas) que vayamos a manejar.

En nuestros ejemplos nos referiremo siempre a una base de datos –a la que llamaremos ejemplos– y un usuario de nombre pepecon contraseña pepa que coinciden con el usuario creado por nuestro autoinstalador y por el referido en los procesos de instalación autónoma y configuración tanto del servidor MariaDB como de MySQL.

La tabla user

Activemos nuestro servidor MySQL de la forma que se indica aquí si tu sistema operativo es Linux Ubuntu. Si se trata de Windowspuedes ver aquí la forma de gestionarlo.

Una vez activos el servidor MySQL y Apache ya podemos utilizar phpMyAdmin. Lo habíamos instalado en un subdirectorio de htdocs llamado phpmyadmin. Así que accedamos a través de la dirección: http://localhost/phpmyadmin/ y se nos abrirá una página como la que sigue:

phpMyAdmin

Page 360: Manual PHP

Elegiremos la base de datos mysql. Al seleccionar mysql en el menú de la izquierda y pulsar sobre mysql (en negro en la parte superior de la lista de tablas) aparecerá la lista que está a la derecha de esta imagen.

Allí vemos la tabla user y un enlace activo que dice Examinar. Si pulsamos sobre él, podremos ver un contenido similar a este que vemos aquí debajo.

Como ves, hay cuatro usuarios y dos de ellos como nombre root y ninguna contraseña han sido creados automáticamente durante la instalación. El cuarto de ellos –el usuario pepe– es el que hemos creado durante el proceso de instalación

Esta configuración es insegura ya que con los nombres de usuario por defecto –root– y sin contraseña cualquiera podría acceder y manipular las bases de datos. Más adelante podremos borrar esos usuarios pero, por el momento, dejémoslos así y añadamos un nuevo usuario.

Si pulsamos la opción editar (el icono en forma de lápiz que hay a la derecha de la casilla de verificación del usuario sin nombre se nos abrirá una página como esta:.

Page 361: Manual PHP

Escribamos localhost en el campo Host, jose en el campo User, josefa en el campo Password y marquemos todas las opciones –una lista bastante larga por cierto– como YES (Y) y –muy importante– seleccionemos la función PASSWORD para el campo del mismo nombre.

Una vez realizado el proceso anterior deberá quedarnos como aparece en la imagen. Pulsaremos en el botón Continúe que hay al final de la página y habremos dado de alta al usuario jose con todos los privilegios para gestionar las bases de datos.

Si regresamos de nuevo a Examinar veremos que ya ha sido incluido el nuevo usuario y que el campo contraseña aparece encriptado como consecuencia de haber aplicado la función PASSWORD para garantizar la privacidad del usuario. MySQL requiere esta encriptación.

Respecto a los YES, la explicación es sencilla. Esas opciones permiten habilitar permisos para determinadas operaciones dentro de las bases de datos y lo único que hemos hecho ha sido conceder todas la facultades de gestión al usuario pepe.

Creación de un fichero INCLUDE

En los scripts PHP, con los que manejemos las bases de datos, vamos a necesitar insertar continuamente: nombre del servidor, nombre de usuario y contraseña.

Tanto la comodidad como la privacidad que hemos mencionado en páginas anteriores aconsejan guardar los datos de usuario en lugar seguro. Así pues lo aconsejable será crear un fichero –llamémosle mysql.inc.php– idéntico al que tenemos aquí debajo,(podemoscopiar y pegar) y que podríamos guardar en un directorio seguro tal como habíamos comentado aquí.

<?php $mysql_server="localhost"; $mysql_login="pepe"; $mysql_pass="pepa"; ?>

Campos y conexiones

Conexión con el servidor de bases de datos

Antes de empezar a trabajar con bases de datos es imprescindible que ambos servidores –Apache y MySQL– estén activos. Como paso inicial hemos de interconexionar ambos servidores de forma que sea posible tranferir información de uno a otro. Para ello es necesario utilizar siempre una función PHP con la siguiente sintaxis:

$conexion=mysql_connect(servidor, usuario, contraseña)

donde $conexion es la variable que recoge el identificador del enlace, servidor es la dirección del servidor de bases de datos, ("127.0.0.1" o 'localhost') , usuario es el nombre de uno de los usuarios registrados en la tabla user ("pepe" o "root") y contraseña la contraseña (en nuestro caso "pepa" ó "") . Estos tres valores –cadenas de texto– deben ir escritos entre comillas.

Para cerrar la conexión utilizaremos la función:

mysql_close ($conexion)

donde $conexion es el nombre de la variable en la que se recogió el indentificador del enlace en el momento de la apertura.

Aquí tienes el código fuente de un script que realiza la apertura de una conexión y después la cierra y desde aquí puedes comprobar su funcionamiento.

Si realizáramos una segunda conexión (con los mismos argumentos) sin haber cerrado la anterior no se efectuará un nuevo enlacesino que nos devolverá el anterior.

Page 362: Manual PHP

Sintaxis alternativa

Otra forma de efectuar la conexión es utilizar los valores registrados en el fichero mysql.inc.php –lo hemos creado en la página anterior– y eso requiere que insertemos en los scripts un include("C:/ServidoresLocales/Apache/seguridad/mysql.inc.php")(include("/var/seguridad/mysql.inc.php") en el caso de Ubuntu) o, si has elegido otra ubicación, la ruta completa hasta el fichero en el que hemos guardado las claves de conexión y para el que te hemos sugerido el nombre de mysql.inc.php.

En este supuesto como valores de los parámetros servidor, usuario y contraseña pondremos los nombres de las variables:$mysql_server, $mysql_login y $mysql_pass sin encerrarlas entre comillas. Aquí tienes el código de un script que realiza la apertura de una conexión y después la cierra y desde este enlace puedes comprobar el funcionamiento de esta sintaxis alternativa.

La instrucción OR DIE

Es esta una buena ocasión para hablar de una instrucción PHP que no hemos mencionado hasta el momento. Se trata de una opción alternativa a exit() que, como acabamos de ver en un ejemplo, interrumpe la ejecución de un script en el momento de ser ejecutada.

Cuando se produce un error en la ejecución de un script –no poder establecer conexión con MySQL, por ejemplo– no tiene sentido seguir ejecutándolo. Lo razonable será interrumpir el proceso y advertir del error. Si añadimos or die ('mensaje') a la instrucción$conexion=mysql_conect('servidor','usuario','contraseña') (sin paréntesis, ni comas, ni punto y coma, sólo separado por un espacio) y ponemos el punto y coma de fin de instrucción después de cerrar este último paréntesis

$conexion=mysql_conect('servidor','usuario','contraseña') or die ('mensaje')

en el caso de que se produzca un error se interrumpirá la ejecución del script y aparecerá en la ventana del navegador el texto incluido en mensaje. Este es el código fuente de un script que produce un error –la contraseña es incorrecta– y que utiliza esta nueva sintaxis. Pero si lo ejecutas verás que aparece un mensaje de error generado por PHP.

Este tipo de mensajes pueden deshabilitarse de la forma descrita aquí. Pero hay una técnica mucho más fácil. Bastará con insertar delante de la función una arroba (@) para evitar que aparezcan. En este otro script lo hemos incorporado y puedes comprobarlo aqui.

Información sobre bases de datos existentes

Antes de crear y/o borrar una base de datos puede ser conveniente y útil comprobar si ya existe. PHP dispone de herramientas para conocer el número de bases de datos existentes en el servidor, así como sus nombres.Como es lógico siempre necesitaremos tener establecida una conexión con el servidor de bases de datos.

$identificador=mysql_list_dbs($conexion)

dónde la variable $identificacor es un nuevo identificador imprescindible y previo a la determinación del número y los nombres de las bases de datos existentes en el enlace abierto (identificado por $conexion).

$numero=mysql_num_rows($identificador)

devuelve en la variable $numero el número de bases de datos existentes en el servidor y utiliza como parámetro ($identificador) el resultado obtenido mediante la función anterior.

mysql_db_name($identificador, indice)

devuelve el nombre de la bases de datos identificada por el número indice que debe pertenecer al intervalo [0,$n). Fíjate que i tiene que ser i<$n porque si, por ejemplo, $n=5 los cinco valores posibles de i serían: 0,1,2,3 y 4.

Una lista completa de todas las bases de datos existentes en el servidor podría hacerse mediante el siguiente proceso:

� – Abrir la conexión.

Page 363: Manual PHP

� – Invocar a mysql_list_dbs.

� – Contar el número de bases de datos con mysql_num_rows

� – Insertar un bucle: for ($i=0;$i<$num,$i++)

� – Presentar la lista de nombres mediante un bucle que lea los diferentes valores de $i en mysql_db_name($identificador,$indice)

Aquí tienes el código fuente de un ejemplo completo y desde aquí puedes ejecutarlo

Crear una base de datos

La creación de una base de datos también requiere una conexión previa y utiliza la siguiente sintaxis:

mysql_query ("CREATE DATABASE nombre") O mysql_query ("CREATE DATABASE IF NOT EXISTSnombre")

dónde nombre es el nombre de la nueva base de datos. Esta función devuelve TRUE si la base de datos es creada, y FALSE si no es posible hacerlo. Si intentamos crear una base de datos con un nombre ya existente la función nos devolverá FALSE. Aquí tienes el código de un ejemplo de creación de una base de datos. Si lo ejecutas dos veces podrás comprobar que en la segunda oportunidad te aparece el mensaje diciendo que no ha sido posible crearla.

Para evitar esa incomodidad se suele usar la segunda de las sentencia. Cuando se incluye IF NOT EXISTS la base de datos se crea únicamente en el caso de no existir pero, a diferencia de la otra opción, en caso de que ya existiera no produce ningún mensaje de error.

Borrar una base de datos

Para borrar una base de datos se requiere el uso de la siguiente función PHP:

mysql_query ("DROP DATABASE nombre")

donde nombre es el nombre de la base de datos y debiendo ponerse toda la cadena del paréntesis entre comillas. Esta función devuelve TRUE cuando se ejecuta con éxito, y FALSE en el caso contrario. Este es el código de un script que puede borrar la base creada anteriormente. Igual que ocurría al tratar de crearla, si intentamos borrar una base de datos inexistente nos devolverá FALSE.

Automatizar la conexión

Con nuestros conocimientos sobre PHP ya estamos en condiciones de hacer más cómoda la conexión. Creemos una función que realice de forma automática la conexión con MySQL y guardémosla en nuestro fichero mysql.inc.php

. <?php # estas son las variables anteriores $mysql_server="localhost"; $mysql_login="pepe"; $mysql_pass="pepa"; # creemos una nueva variable $c sin asignarle ningún valor # para que pueda recoger el identificador de conexión # una vez que se haya establecido esta $c; # escribamos la función que hace la conexión # como pretendemos que el valor del identificador # sea usado fuera de la función, para recuperar su valor # pasaremos ese valor por referencia anteponiendo & al # nombre de la variable

Page 364: Manual PHP

function conecta1(&$c){ # para usar las variables anteriores en la funcion # hemos de definirlas como globales global $mysql_server, $mysql_login, $mysql_pass; if($c=mysql_connect($mysql_server,$mysql_login,$mysql_pass)){ print "<br>Conexión establecida<br>"; }else{ print "<br>No ha podido realizarse la conexión<br>"; # el exit lo incluimos para que deje de ejecutarse # el script si no se establece la conexión exit(); } } # esta función asignará a $c el valor del identificador # repetimos la misma función con otro nombre # ahora quitaremos el mensaje de conexión establecida # consideraremos que si no hay mensaje se ha establecido # asi quedará limpia nuestra página function conecta2(&$c){ global $mysql_server, $mysql_login, $mysql_pass; if($c=mysql_connect($mysql_server,$mysql_login,$mysql_pass)){ }else{ print "<br>No ha podido realizarse la conexión<br>"; exit(); } } ?>

Si sustituyes el contenido de tu mysql.inc.php por el que tienes aquí arriba –puedes eliminar la líneas de comentario al hacerlo– estaremos en disposición de ejecutar scripts como este.

Estos ejemplos utilizan cada una de las dos funciones.

Ver código fuente Ver código fuente

Depurando los procesos de creación y borrado de bases de datos

Cuando intentamos crear una base de datos ya existente o borrar una inexistente las funciones mysql_query nos devuelven FALSE pero esa respuesta no nos dice la causa por la que no ha sido posible la ejecución de la instrucción.

Sería mucho más interesante comprobar la existencia o inexistencia de una base de datos antes de ejecutar esas instrucciones y que después de la comprobación se nos presentara un mensaje informativo. MySQL dispone de una sentencia para este fin, pero –aunque la vamos ver más adelante– olvidémosnos de su existencia e intentemos crear nuestro propio script de comprobación.

Combinando las instrucciones anteriores no resulta difícil hacerlo. Aquí tienes un ejemplo de código para efectuar esa comprobación al crear una base de datos y este otro código es para el caso de borrado.

Puedes experimentar con estos scripts, sustituirlos por otros propios en los que utilices tus propias funciones. Nada es absoluto.

Las bases de datos de nuestros ejemplos

En las páginas siguientes vas encontrarte con una serie de ejemplos. Por razones obvias no vamos a incluir aquí el acceso a los scripts que permiten la creación, modificación y borrado de tablas. Lo que si haremos será incluir enlaces que permitan visualizar el código fuente utilizado en esos procesos. Aquí tienes el script mediante el se crea la base de datos que utilizaremos en nuestros ejemplos.

Page 365: Manual PHP

Código fuente creación de base de datos

este otro código Pulsa en este enlace para que cree automáticamente la base de datos que va a contener los sucesivos ejemplos que hemos incluido en estos materiales.

Pasos previos a la creación de tablas

Conocidos los procesos de creación, listado y borrado de bases de datos ya estamos en disposición en empezar a tratar lo relativo a las tablas. Es muy necesario conocer los diferentes tipos de campos que pueden contener las tablas de MySQL. Saber las posibilidades que ofrece cada uno de ellos será fundamental a la hora de diseñar una tabla. En ese momento tendremos que decidir qué campos son necesarios, cuál es tipo requerido, cuáles han de ser sus dimensiones y también cuáles de ellos requerirán ser tratados como índices.

Tipos de campo bien elegidos y un tamaño adecuado a las necesidades reales de nuestro proyecto son las mejores garantías para optimizar el tamaño de la tabla y para hacerla realmente eficaz. El grado de eficiencia de una base de datos suele ser directamenteproporcional al tiempo invertido en el análisis de la estructura de sus tablas.

Tipos campos en MySQL

MySQL tiene habilitados diversos tipos de campos que en una primera aproximación podrían clasificarse en tres grupos:

� Campos numéricos

� Campos de fecha

� Campos de cadenas de caracteres

Campos numéricos

MySQL soporta los tipos numéricos exactos (INTEGER, NUMERIC, DECIMAL, y SMALLINT) y los tipos numéricos aproximados(FLOAT, DOUBLE precision y REAL). Los campos que contienen números enteros admiten el parámetro UNSIGNED, que implica que no admita signos, por lo que solo aceptaría enteros positivos. Todos los campos numéricos admiten el parámetro ZEROFILL cuya función es completar el campo con ceros a la izquierda hasta su longitud máxima.

Tipos de campos numéricos enteros

Estos son los distintos tipos de campos numéricos enteros que admite MySQL. Los parámetros señalados entre corchetes son opcionales.

TINYINT [(M)] [UNSIGNED] [ZEROFILL]

Número entero muy pequeño. Con la opción UNSIGNED puede tomar valores entre 0 y 255. En caso contrario, puede estar comprendido entre -128 y 127. El parámetro ZEROFILL sólo tiene sentido junto con la opción UNSIGNED ya que no es habitual rellenar los números negativos con ceros a la izquierda del signo. El valor por defecto de parámetro M (número de cifras) es 4 si no está activada la opción UNSIGNED. Si esta opción estuviera activada el valor por defecto seríaM=3. Para valores de M >valor por defecto reajusta el tamaño al valor por defecto. Si se asigna a M un valor menor que cuatro limita el número de caracteres al tamaño especificado considerando el signo sólo en los números negativos. Por ejemplo, si M=3 admitiría 148, pero si intentamos insertar -148 recortaría por la izquierda y solo insertaría -14. Si intentamos insertar un valor fuera de rango registraría el valor dentro del rango más próximo a él. P. ej.: Si tratamos de insertar el valor 437 escribiría 127 ó 255, este último en el caso de tener la opción UNSIGNED. Si pretendiéramos insertar -837 con la opción UNSIGNED escribiría 0 y sin ella pondría -128. El tamaño de un campo TINYINT es de 1 byte.

SMALLINT [(M)] [UNSIGNED] [ZEROFILL]

Número entero pequeño. Con la opción UNSIGNED puede tomar valores entre 0 y 65 535. En caso contrario, puede estar comprendido entre -32 768 y 32 767. Son válidos los comentarios hechos para TINYINT, excepto los relativos a los valores por defecto de M que en este caso serían 6 ó 5. Su tamaño es de 2 bytes.

MEDIUMINT [(M)] [UNSIGNED] [ZEROFILL]

Número entero mediano. Con la opción UNSIGNED puede tomar valores entre 0 y 16 777 215. En caso contrario, puede estar comprendido entre -8 388 608 y 8 388 607.

Page 366: Manual PHP

También son válidos los comentarios hechos para TINYINT, excepto los relativos al valor por defecto de M que en este caso serían 8. Su tamaño es de 3 bytes.

INT [(M)] [UNSIGNED] [ZEROFILL]

Número entero. Con la opción UNSIGNED puede tomar valores entre 0 y 4 294 967 295. En caso contrario, puede estar comprendido entre -2 147 483 648 y 2 147 483 647. Son válidos todos los comentarios de los casos anteriores. Su tamaño es de 4 bytes.

INTEGER [(M)] [UNSIGNED] [ZEROFILL]

Es un sinónimo de INT

BIGINT [(M)] [UNSIGNED] [ZEROFILL]

Número entero grande. Con la opción UNSIGNED puede tomar valores entre 0 y 18 446 744 073 709 551 615. En caso contrario, puede estar comprendido entre -9 223 372 036 854 775 808 y 21 474 839 223 372 036 854 775 807 647, pero al usarlo desde PHP estará sujeto a las limitaciones máximas de los valores numéricos de este. Son válidos todos los comentarios de los casos anteriores. Su tamaño es de 8 bytes.

Números de coma flotante

Por la estructura binaria de los microprocesadores y habida cuenta de que algunos números no enteros –sin ir más lejos, el 0.1– requerirían infinitos caracteres binarios para su representación exacta, se hace necesario introducir un redondeo en su tratamiento informático y como consecuencia de ello asumir que se generan errores de medida.

Esta circunstancia obligó al tratamiento de los números decimales mediante el llamado Standar de Aritmética de Punto Flotante, un algoritmo definido por la IEEE (Institute of Electrical and Electronics Engineers) que unificó los procesos de representación de números en ordenadores con lo que son uniformemente controlables los errores introducidos.

El Standar de Aritmética de Punto Flotante estableció dos niveles de precisión:

� Precisión Simple, en la que todo número debe ser almacenado en 32 bits (4 bytes).

� Doble precisión, en la que los números se almacenan en 64 bits (8 bytes).

Campos númericos de coma flotante

Estos son los distintos tipos de campos numéricos de coma flotante que admite MySQL. Los parámetros señalados entre corchetes son opcionales.

FLOAT(x) [ZEROFILL]

Número de coma flotante. Ignora la opción UNSIGNED, pero sí acepta ZEROFILL, por lo que debe prestarse atención a estas opciones ya que no sería demasiado habitual una presentación como esta: 000-3.47 El valor de x especifica la precisión. Si x<=24 será de precisión simple. cuando 24 <x <=53 lo convertirá automáticamente a doble precisión. Cuando no se especifica el valor de x considera el campo como de precisión simple. Su tamaño es de 4 bytes si x<=24 y de 8 bytes cuando 24 <x <=53

FLOAT [(M,D)] [ZEROFILL]

Número de coma flotante de precisión simple. Son válidos los comentarios relativos a las opciones UNSIGNED y ZEROFILL del caso anterior. Toma valores en los intervalos siguientes:

-3.402823466E+38 a -1.175494351E-38

0 y

1.175494351E-38 a 3.402823466E+38. M es la anchura máxima de visualización y D es el número de decimales. Si M > 24 se convierte automaticamente a doble precisión FLOAT sin argumentos representa un número de coma flotante y precisión simple.

DOUBLE [(M,D)] [ZEROFILL]

Número de coma flotante de doble precisión. Siguen siendo válidos los comentarios relativos a las opciones UNSIGNED y ZEROFILL del caso anterior. Toma valores en los intervalos siguientes:

Page 367: Manual PHP

-1.7976931348623157E+308 a -2.2250738585072014E-308

0 y

2.2250738585072014E-308 a 1.7976931348623157E+308 M es la anchura máxima de visualización y D es el número de decimales. DOUBLE sin argumentos representa un número de coma flotante y precisión doble.

REAL [(M,D)] [ZEROFILL]

Es sinónimo de DOUBLE.

DECIMAL [(M[,D])] [ZEROFILL]

Es un número de coma flotante y doble precisión que se almacena como un campo de tipo CHAR. El valor es guardado como una cadena donde cada carácter representa una cifra. La coma y el signo menos de los números negativos no son tenidos en cuenta en el valor de M -anchura máxima de visualización- aunque si se reserva -automáticamente- espacio para ellos en campo. Si D vale 0 no tendrá parte decimal. Los números toman valores en el mismo intervalo especificado para DOUBLE. Los valores por defecto de M y D son respectivamente 10 y 0. Ocupan M+2 bytes si D > 0; M+1 bytes si D = 0 ó D+2 bytes si M < D

NUMERIC(M,D) [ZEROFILL]

Se comporta de forma idéntica a DECIMAL

Campos de fecha

MySQL dispone de campos específicos para el almacenamiento de fechas. Son los siguientes:

DATE

Recoge una fecha dentro del intervalo 01-01-1000 a 31-12-9999. MySQL guarda los valores DATE con formato AAAA-MM-DD (año-mes-día) . Su tamaño es de 3 bytes.

DATETIME

Recoge una combinación de fecha y hora dentro del intervalo 00:00:00 del día 01-01-1000 y las23:59:59 del día 31-12-9999. MySQL guarda los valores DATETIME con formato AAAA-MM-DD HH:MM:SS (año-mes-día hora:minutos:segundos) . Su tamaño es de 8 bytes.

TIME

Recoge una hora dentro del intervalo -838:59:59 a 838:59:59. MySQL guarda los valores TIME con formato HH:MM:SS (horas:minutos:segundos) . Su tamaño es de3 bytes.

YEAR 0 YEAR(2) o YEAR(4)

Recoge un año en formato de cuatro cifras (YEAR o YEAR(4)) o en formato de dos cifras (YEAR(2))dentro del intervalo 1901 a 2155 en el caso de cuatro cifras o de1970 a 2069 si se trata de dos cifras. Su tamaño es de 1 byte.

TIMESTAMP [(M)]

Recoge un tiempo UNIX. El intervalo válido va desde 01-01-1970 00:00:00 a cualquier fecha del año 2037. El parámetro M puede tomar los valores: 14 (valor por defecto), 12, 8, o 6 que se corresponden con los formatos AAAAMMDDHHMMSS, AAMMDDHHMMSS,AAAAMMDD, o AAMMDD. Si se le asigna la opción NUL guardará la hora actual. Cuando se asigna 8 o 14 como parámetros es considerado como un número y para las demás opciones comouna cadena. Independientemente del valor del parámetro, un campo TIMESTAMP siempre ocupa 4 bytes.

Campos tipo cadena de caracteres

MySQL dispone de campos específicos para el almacenamiento de datos alfanuméricos. Son los siguientes:

Page 368: Manual PHP

CHAR (M) [BINARY]

Es una cadena de tamaño fijo que se completa a la derecha por espacios si es necesario. El parámetro M puede valer de 1 a 255 caracteres. Los espacios finales son suprimidos cuando la cadena es insertada en el registro. Los valores de tipo CHAR son elegidos y comparados sin tener en cuenta Mayúsculas / Minúsculas y utilizan el juego de carácteres por defecto. Se puede utilizar el operador BINARY para hacer la cadena sensible a Mayúsculas / Minúsculas. Se puede utilizar un campo tipo CHAR(0) con el atributo NULL para almacenar una valor booleano. En este caso ocupará un solo byte y podrá tener únicamente dos valores: NUL ó "". Su tamaño es de M bytes siendo 1 <= M <= 255 .

VARCHAR(M) [BINARY]

Es una cadena de caracteres de longitud variable. Su tamaño máximo -especificado en el parámetro M- puede estar comprendido entre 1 y 255 caracteres. Con la opción BINARY es capaz de discriminar entre Mayúsculas / minúsculas.

TINYBLOB o TINYTEXT

TINYBLOB y TINYTEXT son cadenas de caracteres de longitud variable con un tamaño máximo de 255 (28 - 1) caracteres. La diferencia entre ambas es que TINYBLOB discrimina entre Mayúsculas / minúsculas, mientras que TINYTEXT no lo hace. Ninguno de los campos BLOB y TEXT admite valores por DEFECTO Las versiones de MySQL anteriores a 3.23.2 permiten utilizar estos campos para indexar. Si se intenta guardar en un campo de este tipo una cadena de mayor longitud que la especificada solamente se guardarán los M primeros caracteres de la cadena.

BLOB o TEXT

BLOB y TEXT son cadenas de caracteres de longitud variable con un tamaño máximo de 65535 (216 - 1) caracteres. La diferencia entre ambas es que BLOB si discrimina entre Mayúsculas / minúsculas, mientras que TEXT no lo hace. Ninguno de los campos: BLOB y TEXT admite valores por DEFECTO

MEDIUMBLOB o MEDIUMTEXT

MEDIUMBLOB y MEDIUMTEXT son cadenas de caracteres de longitud variable con una longitud máxima de 16.777.215 (224 - 1) caracteres. Son válidas las especificaciones hechas en el apartado anterior. El tamaño máximo de los campos de este tipo está sujeto a limitaciones externas tales como la memoria disponible y el tamaño del buffer de comunicación servidor/cliente.

LONGBLOB o LONGTEXT

Su única diferencia con la anterior es el tamaño máximo de la cadena, que en este caso es 4.294.967.295 (232 - 1) caracteres.

ENUM('valor1','valor2',...)

Es una cadena de caracteres que contiene uno solo de los valores de la lista (valor1, valor2, etc. etc.). A la hora de insertar un nuevo registro en una tabla, el valor a especificar para un campo de este tipo ha de ser una cadena que contenga uno de los valoresespecificados en la tabla. Si se tratara de insertar un valor distinto de ellos insertaría una cadena vacía.

SET('valor1','valor2','valor3'...)

Es una cadena de caracteres formados por la unión de ninguno, uno o varios de los valores de una lista. El máximo de elementos es 64. Los valores que deben escribirse en los campos de este tipo han de ser numéricos, expresados en forma binaria o en forma decimal. En el supuesto de que contuviera tres valores los posibles valores a insertar en un campo de este tipo a la hora de añadir un registro serían los siguientes.

Incluir valores Código valor Cadena binaria

Equiv. decimal val1 val2 val3 val1 val2 val3

Si Sí Sí 1 1 1 111 7

Si Sí No 1 1 0 011 3

Si No Sí 1 0 1 101 5

No Sí Sí 0 1 1 110 6

No No Sí 0 0 1 100 4

No Sí NO 0 1 0 010 2

Page 369: Manual PHP

Si No No 1 0 0 001 1

No No No 0 0 0 000 0

Creación de tablas

Tipos de tablas

MySQL permite usar diferentes tipos de tablas tales como: ISAM, MyISAM o InnoDB.

Las tablas ISAM (Indexed Sequential Access Method) son las de formato más antiguo. Están limitadas a tamaños que no superen los 4 gigas y no permite copiar tablas entre máquinas con distinto sistema operativo.

Las tablas MyISAM son el resultado de la evolución de las anteriores adaptadas por MySQL durante mucho tiempo como el formato por defecto para sus servidores de bases de datos. En las versiones más recientes de MySQL está cambiando es criterio y empiezan a verse versiones que incluyen InnoDB como formato por defecto.

Las tablas del tipo InnoDB (desarrolladas por la compañía finlandesa InnoBase a la que deben su nombre) tienen una estructura distinta a la de MyISAM, ya que utilizan un sólo archivo por tabla en ver de los tres habituales en los tipos anteriores. Incorporan dos de ventajas muy importantes: permiten realizar transacciones y definir reglas de integridad referencial. Hablaremos más adelante de ello.

La definición de uno u otro formato (MyISAM o InnoDB) debe hacerse en el momento de la creación de tabla y solo reguiere agregarType=MyISAM ó Type=InnoDB a la sentencia MySQL encargada de crearla.

Creación de tablas

Las tablas son elementos de las base de datos. Por esa razón nos resultará imposible crear una tabla sin tener creada yseleccionada una base de datos. Es por eso que para la creación de una tabla se necesitan los siguientes requisitos:

� – Tener abierta una conexión con el servidor MySQL.

� – Tener seleccionada una base de datos.

Conexión con el servidor

La hemos comentado en la página anterior. Recuerda que requería la función: $conexion = mysql_connect ( servidor, usuario, contraseña ) y que esa conexión ha de ser establecida antes de cualquier otra intervención relacionada con accesos a bases de datos y tablas.

Selección de la base de datos

Dado que podemos manejar bases de datos distintas –en estos materiales usaremos ejemplos y practicas– es preciso decir a MySQL con qué base queremos trabajar.

mysql_select_db(nombre_base_datos, $conexion)

donde nombre_base_datos es el nombre de la base de datos (puede ser una cadena entrecomillada o el nombre de una variable previa que contenga ese nombre). En este último caso, como es habitual, el nombre de la variable no llevaría comillas. El segundo parámetro$conexion es el identificador de conexión. Es decir, la variable creada al establecer la conexión con MySQL. Este valor debe insertarse siempre. La razón es que MySQL permite mantener abiertas varias, de forma simultánea, varias conexiones (podríamos manejar más de un servidor de bases de datos) y en esas condiciones no pueden darse ambigüedades respecto a la base que pretendemos usar.

Creación de una tabla

En todas las transacciones PHP – MySQL habremos de utilizar instrucciones de ambos lenguajes. La forma habitual –hay algunas excepciones– en la que PHP se comunica con MySQL es por medio de la función genérica:

Page 370: Manual PHP

mysql_query("sentencia", $conexion);

donde la cadena sentencia contiene las instrucciones propias de MySQL y $conexion sigue siendo la variable que contiene el identificador de conexión.

La creación de tablas MySQL requiere una de estas dos sentencias:

CREATE TABLE IF NOT EXISTS tabla (campo1, campo2,... ) Type=tipo o CREATE TABLE tabla (campo1, campo2,... )) Type=tipo

donde tabla es una cadena que contiene el nombre de la tabla que pretendemos crear, dónde campo1, campo2, etc. son las definiciones de los campos que pretendemos que contenga la tabla y tipo puede ser MyISAM o InnoDB según el tipo de tabla que queramos crear.

La única diferencia entre ambas opciones es que la segunda daría un error si tratáramos de crear una tabla preexistente (deberíamos recurrir al procedimiento que hemos visto cuando creábamos bases de datos) mientras que la primera no da ese mensaje de error.

Definición de campos en una tabla MySQL

Cada uno de los campos que vayamos a crear en una tabla requiere una definición que debe contener lo siguiente:

– nombre del campo

Es una palabra cualquiera –distinta para campo de la tabla y que normalmente suele elegirse aludiendo al contenido. Por ejemplo,fecha_nacimiento, nombre_del_perro, etcétera. No va entre comillas nunca y MySQL diferencia mayúsculas/minúsculas.

Para utilizar como nombres de campo palabras reservadas del lenguaje MySQL –por ejemplo, create– debemos escribirlas entre ` `. Observa que no son comillas sino acentos graves. Lo más aconsejable es evitar esta situación.

– tipo y dimensiones

Los tipos de campos –los hemos visto en la página anterior– tienen que ajustarse a uno de los soportados por MYSQL. El tipo de campo se escribe a continuación del nombre sin otra separación que un espacio y requieren la sintaxis –estricta– correspondiente a cada tipo.

Cuando se establezca una dimensión como parámetro opcional de un campo deben tenerse en cuenta algunos detalles:

� – Su valor se escribe entre paréntesis y se incluye después del nombre del campo.

� – Si en un campo numérico introdujéramos valores que exceden los límites, su valor no se registraría en la tabla sino el valor dellímite más próximo correspondiente a ese tipo de campo.

� – Supongamos un campo tipo TINYINT que permite valores comprendidos entre -128 y 127. Si asignáramos en uno de sus registros un valor igual a 234 se escribiría en la tabla 127 (el límite superior) y si ponemos -834 escribiría el límite inferior, es decir, -128.

� – En caso de cadenas, si el valor introducido sobrepasara la longitud máxima permitida, la cadena sería recortada y únicamente se registraría el número máximo de caracteres permitidos.

– flags del campo ( son opcionales)

Puede utilizarse cualquiera de los permitidos para cada tipo de campo. Puedes verlos encerrados, entre corchetes, al lado de cada tipo de campo. Cuando sea necesario incluirlo han de escribirse inmediatamente después del nombre del campo o de la dimesión (en los casos en que se incluya).

Ejemplo de creación de una tabla

Page 371: Manual PHP

<?php /* nos conectamos con el servidor recogiendo en $c el identificador de conexión */ $c=mysql_connect ("localhost","pepe","pepa") or die ("Imposible conectar"); # seleccionamos una base de datos existente # de lo contrario nos daría un error # pondremos como nombre ejemplos nuestra base de datos # creada en la página anterior y usaremos $c # importante no olvidarlo mysql_select_db ("ejemplos", $c); /* ahora ya estamos en condiciones de crear la tabla podríamos escribir ya la instrucción mysql_query y meter detro la sentencia MySQL pero, por razones de comodidad crearemos antes una variable que recoja toda la sentencia y será luego cuando la ejecutemos. Definiremos una varable llamada $crear e iremos añadiendo cosas */ # la primera parte de la instrucción es esta (espacio final incluido $crear="CREATE TABLE IF NOT EXISTS "; # añadiremos el nombre de la tabla que será ejemplo1 # fijate en el punto (concatenador de cadenas) que permite # ir añadiendo a la cadena anterior $crear .="ejemplo1 "; #ahora pongamos el paréntesis (con un espacio delante) #aunque el espacio también podría detrás de ejemplo1 $crear .="( "; # insertemos el primer campo y llamemoslo num1 # hagamoslo de tipo TINYINT sin otras especificamos # sabiendo que solo permitira valores numéricos # comprendidos entre -128 y 127 $crear .="num1 TINYINT , "; # LOS CAMPOS SE SEPARAN CON COMAS por eso # la hemos incluido al final de la instrucción anterior # ahora num2 del mismo tipo con dimensión 3 y el flag UNSIGNED # Y ZEROFILL que: cambiará los límites de valores # al intervalo 0 - 255, y rellenará con ceros por la izquierda # en el caso de que el número de cifras significativas # sea menor de 3. # Fijate que los flags van separado unicamente por espacios $crear .="num2 TINYINT (3) UNSIGNED ZEROFILL, "; # en num3 identico al anterior añadiremos un valor por defecto # de manera que cuando se añadan registros a la tabla # se escriba automaticamente ese valor 13 en el caso # de que no le asignemos ninguno a ese campo # por ser numérico 13 no va entre comillas $crear .="num3 TINYINT (7) UNSIGNED ZEROFILL DEFAULT 13, "; # ahora un número decimal num4 tipo REAL con 8 digitos en total # de los cuales tres serán decimales y también rellenaremos con ceros # Pondremos como valor por defecto 3.14 $crear .="num4 REAL (8,3) ZEROFILL DEFAULT 3.14, "; # añadamos una fecha $crear .="fecha DATE, "; /* una cadena con un limite de 32 carácter con BINARY para que diferencie Pepe de PEPE */ $crear .="cadena VARCHAR(32) BINARY, "; /* un ultimo campo –opcion– del tipo ENUM que solo admita como valores SI, NO, QUIZA fijate en las comillas y en el parentesis ¡¡cuidado...!! aqui no ponemos coma al final es el último campo que vamos a insertar y no necesita ser separado. Si la pones dará un ERROR */ $crear .="opcion ENUM('Si','No','Quiza') ";

Page 372: Manual PHP

# solo nos falta añadir el paréntesis conteniendo toda la instrucción $crear .=")Type=MyISAM"; /* tenemos completa la sentencia MYSQL solo falta ejecutarla mediante mysql_query ya que la conexión está abierta y la base de datos ya está seleccionada */ /* pongamos un condicional de comprobación */ if(mysql_query($crear,$c)){ print "Se ha creado la base de datos<br>"; print "La sentencia MySQL podríamos haberla escrito asi:<br>"; print "mysql_query("."\"".$crear."\" , $c);"; }else{ print "Se ha producido un error al crear la tabla"; } ?>

Consideraciones sobre IF NOT EXISTS

Esta variante de CREATE –aplicable tanto en tablas como en bases de datos– tiene la ventaja de no dar mensajes de error en caso de intentar crear una tabla –o base– ya existente, pero puede darnos algún sobresalto porque no advierte que la tabla no ha sido creada y puede darnos la sensación de que puede haber reescrito una tabla anterior. Si se ejecuta reiteradamente el script no aparece ningún mensaje de error.

Código fuente creación de base de datos

Ver y modificar estructuras

Visualizar la estructura de una tabla

Ver la estructura de una tabla utilizando MySQL

La sentencia MySQL que permiten visualizar la estructura de una tabla es la siguiente:

SHOW FIELDS from nombre de la tabla

Lectura de resultados de sentencias MySQL

La sentencia SHOW FIELDS –como prácticamente ocurre con todas las sentencias MySQL– no devuelve los resultados en un formatolegible. Los valores devueltos requieren ser convertidos a un formato que sea interpretable por PHP. Esa traducción se realiza de la siguiente forma:

� – El resultado devuelto por MySQL a través de una llamada mysql_query() es recogido en una variable, de la forma siguiente:$resultado=mysql_query(sentencia, $conexion)

� –El resultado recogido en la variable $resultado, está estructurado en líneas. Por medio de la función: $linea =mysql_fetch_row ($resultado)

se recoge en una variable ($linea) el contenido de la primera línea situándose el puntero interno al comienzo de la línea siguiente. Por esta razón la lectura completa del contenido de la variable $linea requiere llamadas sucesivas a mysql_fetch_rowhasta que haya sido leída la última de las líneas del resultado de la llamada a MySQL.

La variable $linea tiene estructura de array escalar siendo cero el primero de sus índices. Cuando el puntero interno demysql_fetch_row() alcance el final de la última línea del resultado devolverá como resultado FALSE.

Por esa razón, la visualización de los resultados de una sentencia MySQL suele requerir dos bucles. Este es el esquema de la lectura:

Page 373: Manual PHP

$resultado=mysql_query(sentencia,$conexion); while($linea=mysql_fech_row($resultado){ foreach ($linea as $valor){ print $valor; } }

también cabría usar una sintaxis alternativa similar a esta:

$resultado=mysql_query(sentencia,$conexion); while($linea=mysql_fech_row($resultado)){ $matriz[]=$linea; }

con lo que estaríamos creando un array bidimensional con el contenido de los resultados de cada línea. En este caso el primer índicedel array $matriz sería un escalar que empezaría en cero y se iría autoincrementando en una unidad en cada uno de los ciclos del buclewhile.

El ejemplo tiene desarrollados ambos procedimientos.

<?php # asignamos a una variable el nombre de la base de datos $base="ejemplos"; # esta otra recoge el nombre de la tabla $tabla="ejemplo1"; # establecemos la conexión con el servidor $conexion=mysql_connect ("localhost","pepe","pepa"); # seleccionamos la base de datos mysql_select_db ($base, $conexion); #ejecutamos mysql_query llamando a la sentencia SHOW FIELDS $resultado=mysql_query( "SHOW FIELDS from $tabla",$conexion); # ejecutamos los bucles que comentamos mas arriba while($linea=mysql_fetch_row ($resultado)){ foreach($linea as $valor) { print $valor."<br>"; } } #tenemos que VOLVER a EJECUTAR LA SENTENCIA MySQL porque el puntero está # AL FINAL de la ultima línea de los resultados print("<BR> Los nuevos resultados son <br>"); $resultado=mysql_query( "SHOW FIELDS from $tabla",$conexion); while($linea=mysql_fetch_row ($resultado)){ $matriz[]=$linea; } # leemos ahora el array bidimensional foreach ($matriz as $indice=>$mi_linea){ foreach ($mi_linea as $indice2=>$valor){ print "<i>Indice</i>: ".$indice." <i>Indice2</i>: ".$indice2." <i>Valor</i>: ".$valor."<br>"; } } # cerramos la conexion con el servidor mysql_close($conexion); ?>

ejemplo373.php

El procedimiento anterior nos facilita información sobre la estructura de la tabla pero quizá no lo haga con toda la claridad que fuera de desear. Para mejorar las prestaciones de la anterior existe una función alternativa que es:

Page 374: Manual PHP

$matriz =mysql_fetch_array($resultado)

idéntica en cuanto a requerimientos pero recogiendo en un array los resultados de la sentencia. Ese array tiene además la peculiaridad de incluye cada uno de los valores en dos índices distintos. Uno de los índices es de tipo escalar. El otro es de tipo asociativo y sus índices son los valores del campo de la tabla del que se han extraido los resultadosativos. En este último caso incorporan como índice el nombre del campo de la tabla del que se han extraído los resultados.

$matriz =mysql_fetch_array($resultado, MYSQL_NUM)

idéntica a la anterior.La inclusión del parámetro MYSQL_NUM (sin comillas )limita sus resultado a los elementos del array de índice escalar.

$matriz =mysql_fetch_array($resultado, MYSQL_ASSOC)

También idéntica a las anteriores. La inclusión del parámetro MYSQL_ASSOC (sin comillas )limita ahora los resultado a los elementos del array asociativo.

<?php # asignamos a una variable el nombre de la base de datos $base="ejemplos"; # esta otra recoge el nombre de la tabla $tabla="ejemplo1"; # establecemos la conexión con el servidor $conexion=mysql_connect ("localhost","pepe","pepa"); # seleccionamos la base de datos mysql_select_db ($base, $conexion); #ejecutamos mysql_query llamando a la sentencia SHOW FIELDS $resultado=mysql_query( "SHOW FIELDS from $tabla",$conexion); print("<br> Los resultados con mysql_fech_array<br><br>"); while ($linea=mysql_fetch_array($resultado)){ # leemos el array de resultados de cada una de las lineas extraidas del resultado foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } /* vamos a separar los elementos escalares de los asociativos comprobando si es numérico el indice de cada array */ print "<br>Los elementos de array con indice numérico son estos<br>"; $resultado=mysql_query( "SHOW FIELDS from $tabla",$conexion); while ($linea=mysql_fetch_array($resultado,MYSQL_NUM)){ /* leemos el array de resultados de cada una de las lineas extraidas del resultado e insertamos un texto que nos ayude a ver lo contenidos de cada linea en la pantalla */ print "<br>Nueva línea<br>"; foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } /* visualizamos los resultados cuando el indice es NO NUMÉRICO */ print "<br>Los elementos de array con indice no numérico son estos<br>"; $resultado=mysql_query( "SHOW FIELDS from $tabla",$conexion); while ($linea=mysql_fetch_array($resultado, MYSQL_ASSOC)){ /* leemos el array de resultados de cada una de las lineas extraidas del resultado e insertamos un texto que nos ayude a ver lo contenidos de cada linea en la pantalla */ print "<br>Nueva línea<br>"; foreach($linea as $indice=>$valor) {

Page 375: Manual PHP

print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } mysql_close($conexion); ?>

ejemplo374.php

Como habrás podido observar al ejecutar el ejemplo SHOW FIELDS nos permite conocer los propiedades de cada uno de los campos de la tabla: Field (nombre del campo), Type (incluye el tipo, el tamaño y los flags), Null (especificaría si el campo permite o no permite valores nulos), Key (especificaría si ese campo es o es un índice de la tabla) y Default (que incluiría los eventuales valores asignados por defecto al campo).

Otras funciones informativas

Además de la información extraida mediante mysql_fetch_array y mysql_fetch_row el resultado de la ejecución de la sentencia SHOW FIELDS puede proporcionarnos información –incluyendo con parámetro $resultado– a través de las siguientes funciones:

mysql_num_fields ($resultado)

Está función –en la que $resultado es el identificador de resultado– devuelve el número de campos de la tabla.

mysql_num_rows ($resultado)

Devuelve el número de filas del resultado.

mysql_field_table($resultado, 0)

Devuelve el nombre de la tabla. Observa que se pasa con índice 0 ya que esta información parece ser la primera que aparece en la tabla.

mysql_field_type($resultado, $indice)

Nos devuelve el tipo de campo correspondiente a la posición en la tabla señalada por el índice $indice. Dado que la información deprimer campo está en el índice 0, el último valor válido de $indice será igual al número de campos menos uno.

mysql_field_flags($resultado, $indice)

Nos devuelve los flags del campo correspondientes a la posición en la tabla señalada por el índice $indice. Se comporta igual que la anterior en lo relativo a los índices.

mysql_field_len($resultado, $indice)

Nos devuelve la longitud del campo correspondiente a la posición en la tabla señalada por el índice $indice. Igual que las anteriores en lo relativo a los índices.

mysql_field_name($resultado, $indice)

Nos devuelve el nombre del campo correspondiente a la posición en la tabla señalada por el índice $indice. En lo relativo a los índices su comportamiento es idéntico a las anteriores.

Liberando memoria

Si queremos liberar la parte de la memoria que contiene un identificador de resultado, bastará con que insertemos la siguiente instrucción:

mysql_free_result($resultado)

Page 376: Manual PHP

Este proceso debe ser posterior a la visualización de los resultados.

ejemplo375.php Ver código fuente

Manipulación de tablas

Borrar tablas

Las sentencias MySQL que permiten borrar una tabla son las siguientes:

DROP TABLE IF EXISTS from nombre_de_la_tabla o DROP TABLE from nombre_de_la_tabla

la diferencia entre ambas radica en que usando la primera no se generaría ningún error en el caso de que tratáramos de borrar una tabla inexistente.

Aquí tienes el código fuente de un ejemplo:

Ver código fuente

Borrar uno de los campos de una tabla

La sentencia MySQL que permite borrar uno de los campos de una tabla es la siguiente:

ALTER TABLE nombre_de_la_tabla DROP nombre_del_campo

Es posible modificar la estructura de una tabla -en este caso borrar un campo- siguiendo un procedimiento similar a los anteriores. La única diferencia estriba en utilizar la sentencia MySQL adecuada. Resulta obvio que el campo debe existir para que pueda ser borrado y si no existiera, es obvio también que se produciría un error.

Aquí tienes el código fuente de un script que borra uno de los campos de una tabla.

Ver código fuente

Añadir un nuevo campo a una tabla

Las sentencia MySQL que permite añadir un nuevo campo a una tabla es la siguiente:

ALTER TABLE nombre_de_la_tabla ADD nombre del campo tipo [flags]

La sintaxis es similar a la de la creación de tablas: el nombre del campo debe ir seguido del tipo sin otra separación que el espacio.

Aquí tienes el código fuente de un script que añade uno de los campos de una tabla.

Ver código fuente

Añadir registros e índices

Manejando índices

Es muy frecuente la utilización de índices en las bases de datos. Pero... ¿qué son los índices? ¿para qué sirven?. Si nos planteamos cuál es la utilidad del índice de un libro la respuesta podría ser:

Page 377: Manual PHP

� – Facilitar la búsqueda de datos

� – Agilizar esa búsqueda

La utilidad de los índices en la bases de datos es bastante similar. Si seguimos con el ejemplo del libro veremos que caben las posibilidades de que tenga:

� – Un solo índice

� – Varios índices

Algunos libros sólo contienen un indice de contenidos. Sin embargo, es frecuente que también dispongan de otros, tales como:índices analíticos, índices onomásticos, etcétera. Cuando existe un solo índice es obvio que puede decirse de él que es único y cuando existen varios podemos decir que el índice de contenidos es el índice principal y los demás son índices sin más o índices auxiliares.

Coincidirás con nosotros en que en el Índice de contenidos de un libro de texto sólo existe una referencia al Tema XIII en la que puede decir: Tema XIII página 314. También coincidirás en que si en ese índice, además de lo anterior, dijera: Tema XIII página 714 nos encontraríamos en una situación confusa que nos obligaría a preguntarnos: ¿donde está el Tema XIII? ¿En la página 314? ¿En la 714? ¿En ambas?.

Es por eso que las tablas de las bases de datos no admiten nunca valores duplicados ni en los índices únicos ni tampoco en losíndices principales. Los índices auxiliares tienen un comportamiento distinto. El índice onomástico de un libro puede hacer referencia a varias páginas y puede tener duplicados. Por ejemplo: en un manual de Word puede existir un índice onomástico en el que se asocie la palabramacros con las páginas 37, 234 y 832 siempre que en esas páginas existan contenidos que aludan a la palabra macro.

Es por eso que las tablas de las bases de datos también admiten duplicados cuando se trata de índices auxiliares.

Sintaxis de la definición de índices MySQL

Tanto al crear una tabla como al modificarla pueden añadirse índices de la misma forma que se pueden insertar campos. La sintaxis (dentro de la sentencia MySQL que crea o modifica una tabla) es la siguiente:

PRIMARY KEY(campo)

donde campo es el nombre del campo que se establece como índice principal de la tabla.

El parámetro campo no va entrecomillado y PRIMARY KEY campo se incluye dentro de la sentencia CREATE como si se tratara de un campo más, delimitado por comas, salvo que estuviera al final de la sentencia CREATE, en cuyo caso se omitiría la coma final. Solo puede definirse un índice primario por tabla y el campo utilizado ha de ser un campo no nulo.

UNIQUE nombre (campo)

Similar a PRIMARY KEY en cuanto a que no admite valores duplicados, pero con dos diferencias importantes. UNIQUE permite la creación de más de un índice de este tipo por tabla y además no requiere que los campos sobre los que se define sean no nulos.

INDEX nombre (campo)

Con esta sintaxis se crea un índice secundario que debe tener un nombre (la posibilidad de que existan varios obliga resolver la posible ambigüedad por medio del nombre) y –como en los casos anteriores– el campo que será utilizado como índice.

INDEX nombre (campo(n))

Es un caso particular del anterior utilizable sólo en el caso de que el campo índice sea tipo CHAR o VARCHAR. Al tratarse de cadenas alfanuméricas, mediante esta opción es posible indexar por sus n primeros caracteres. El valor de n ha de ser menor o igual que 256 dado que el tamaño máximo de un índice está limitado en MySQL a 256 bytes.

Otra limitación de MySQL es el número máximo de índices de una tabla que no puede superar de dieciséis.

Page 378: Manual PHP

Los errores MySQL

PHP dispone de dos funciones que nos permiten detectar si una sentencia MySQL se ha ejecutado correctamente o si se ha producido algún error. Son las siguientes:

mysql_errno($conexion)

Indica el número de error que se ha producido en la transacción MySQL realizada a través del identificador de $conexion. Cuando elnúmero de error es CERO significa que no se ha producido error.

Otros valores bastante usuales son los siguientes:

Error número 1050

Indica que hemos tratado de crear una tabla ya existente.

Error número 1062

Indica que hemos tratado de introducir un valor con clave duplicada. Aparecerá cuando tratemos de introducir un valor ya existente en un campo con índice único o principal.

mysql_error($conexion)

Devuelve la descripción del error. Cuando el número de error es CERO devuelve una cadena vacía. Resulta de muchísima utilidad para depurar scripts.

Añadir registros a una tabla

La sentencia MySQL que permite añadir registros a una tabla es la siguiente:

INSERT tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

Vamos a desarrollar un ejemplo completo de creación de una tabla e inserción de registros utilizando diversos métodos. Para ello seguiremos el siguiente proceso:

Creación de la tabla

Empezaremos creando una tabla a la que llamaremos demo4 y que contendrá los siguientes campos:

� Contador, que será de tipo TINYINT(8) (número entero con un máximo de 8 dígitos) que contenga solo valores positivos, que se rellene automáticamente con ceros por la izquierda y que se autoincremente cada vez que añadimos un registro.

� DNI, destinado a recoger valores de números de DNI y que debe poder contener un máximo de ocho caracteres. Lo definiremos de tipo CHAR porque sus valores, pese a ser numéricos, nunca van a requerir ningún tratamiento aritmético.

� Nombre, Apellido1 y Apellido2 serán tres campos tipo VHAR de tamaños máximos respectivos de 20, 15 y 15 caracteres. Su finalidad la evidencian los nombres de campo. Les asignaremos el flag NOT NULL aunque no sea totalmente correcto dado que existen países en los que se utiliza un solo apellido.

� Nacimiento será un campo tipo DATE al que asignaremos como valor por defecto el de 1970-12-21. Recuerda que MySQL trata las fechas en ese orden (año-mes-dia) y que admite como separadores tanto - como / por lo que son válidas entradas con cualquiera de estos formatos: 1970-12-21 ó 1970/12/21, aunque esta segunda es convertida automaticamente al primer formato por MySQL.

� Hora será un campo tipo TIME al que asignaremos como valor por defecto el de 00:00:00. Recuerda que MySQL trata las horas en ese orden (hh:mm:ss) y que sólo admite como separador :. Este campo está destinado a recoger la hora de nacimiento que aunque no tiene demasiado sentido es una buena excusa para introducir este tipo de campo.

� Sexo será un campo tipo ENUM que tendrá dos opciones: M (masculino) y F(femenino) y al que asignaremos M como valor por defecto.

� Fumador será un campo tipo CHAR(0) que por su estructura -cadena de longitud cero- tendrá dos únicas opciones de valor: NULL ó"", que, como veremos, son valores distintos para MySQL.

Page 379: Manual PHP

� Idiomas será un campo tipo SET definido para los valores: Castellano, Francés, Inglés, Alemán, Búlgaro y Chino y que podrá contener, como ocurre con los campos de este tipo, ninguno, uno o varios de los valores de la lista.

� Índice primario será tratado como tal el campo DNI ya que se trata de un valor único para cada persona y que, como tal, no puede tener duplicados.

� Índices auxiliares. Para ejemplificar su tratamiento consideraremos el campo Contador como índice secundario.

El código fuente del fichero que genera esta tabla puedes verlo aquí debajo

Ver código fuente

Añadir un registro

Cuando se añade un registro en una tabla los valores pueden añadirse en la propia sentencia MySQL o ser recogidos de los valores de variables PHP previamente definidas.

En este ejemplo tienes el código fuente del primero de los casos, en el que se añade a la tabla anterior un registro cuyos valores son:

DNI Nombre Apellido1 Apellido2 Nacimiento Sexo Hora Fumador Idiomas

1234 Lupicinio Servidor Servido 1954-11-23 M 16:24:52 NULL 3

Ver código fuente

Presta atención a los siguientes aspectos:

� – En la sentencia no se alude al campo Contador. La razón es que se trata un campo AUTOINCREMENTAL y en ese tipo de campos los valores de los registros se escriben automáticamente cada vez que se añade uno nuevo.

� – El registro no se añadiría si el valor de DNI coincidiera con otra ya existente en la tabla. Recuerda que habíamos definido ese campo como índice primario. Si no hubiéramos incluido el aviso de error no tendríamos ninguna referencia sobre el éxito de la inserción y no detectaríamos el problema de duplicidad. Sencillamente ocurriría que el registro no se añadiría pero no nos enteraríamos de tal circunstancia.

� – Si en los valores de nombre y apellidos hubiéramos insertado textos más largos del tamaño establecido para ellos al crear la tabla, las cadenas se recortarían y sólo se añadirían -de izquierda a derecha- los n primeros caracteres de la cadena.

� – Si en la fecha de nacimiento hubiéramos introducido una cadena vacía nos habría puesto el valor por defecto, pero si hubiéramos introducido un valor no válido nos habría escrito 0000-00-00. Serían valores no válidos en este caso:

� – Los que tuvieran como valor de mes alguno no perteneciente al intervalo [1,12].

� – Los que tuvieran como valor de día un valor no válido en concordancia con el mes y el año. Admitiría 29 en un mes defebrero sólo en el caso de que el año fuera bisiesto.

� – Cuando la secuencia no coincidiera con esta AAAA-MM-DD.

� – Cuando los separadores no fueran (-) o (/).

� – En el campo Sexo si hubiéramos introducido una cadena vacía nos habría puesto M (valor por defecto) pero si hubiéramos intentado introducir un valor que no fuera M ni F nos habría insertado una cadena vacía.

� – El campo hora se comportaría de idéntica forma al de fecha salvo que aquí la secuencia es: hh:mm:ss, que la hora debe pertenecer al intervalo [0,23], los minutos y los segundos a [0,59] y que el único separador válido en este caso es (:).

� – El campo Fumador requiere particular atención ya que se trata de un campo CHAR(0) que sólo admite dos valores: NULL ocadena vacía, que como recordarás son distintos para MySQL.

� – Para introducir el valor NULL –utilizando el procedimiento de inserción de este ejemplo– tienes que escribir NULL, sin ponerlo entre comillas, tal como lo he hecho en el ejemplo .

� –Para introducir una cadena vacía en este campo bastaría con que pusieramos '' (¡ojo no es una comilla doble es la comilla sencilla (') repetida dos veces! Date cuenta de que si pusiéramos comillas dobles tendríamos un error ya que hay unas comillas dobles delante delINSERT que

Page 380: Manual PHP

se cierran al final de la sentencia MySQL y que no podemos volver a escribirlas entre ambas para evitar unfalso cierre de la cadena que contienen.

� –En este caso el valor del campo Idiomas puede contener valores decimales comprendidos entre 0 y 64 o entre sus equivalentes binarios que son 0 y 111111. El valor 64 lo justifica el hecho de que son seis los elementos que puede contener el campo (hemos definido: Castellano, Francés, Inglés, Alemán, Búlgaro y Chino que son seis y que el caso de insertarlos todos requeriría el número binario 111111, cuyo valor decimal es precisamente 64. El valor 3 significa lo mismo que su equivalente binario (11) o mejor (000011) lo cual quiere decir que, como el primer carácter de la derecha es uno el campo toma el primer elemento de la lista (Castellano), como el segundo (de derecha a izquierda) también esuno tomará también el segundo elemento de la lista (Francés) y por ser cero todos los demás no tomará ningún otro valor de la lista, con lo que la cadena resultante sería en este caso Castellano, Francés.

� –Fíjate también en que el valor 3 no lo hemos puesto entre comillas porque se trata de una expresión decimal. ¿Qué ocurría si hubiera puesto 11? ¿Lo habría interpretado como once (decimal) o como tres (binario)?. La solución es simple: '11' (entre comillas) sería interpretado como binario, sin comillas como decimal.

� – No es necesario introducir valores en todos los campos, es decir, que la lista de campos de la sentencia INSERT puede no contenerlos a todos.

� – No es necesario escribir el nombre de los campos en el mismo orden en el que fueron creados pero si es imprescindible quecampos y valores estén escritos en la sentencia INSERT exactamente en el mismo orden y también que en esa sentencia elnúmero de campos y el número de valores sea el mismo.

� – Recuerda que los values tipo numéricono se incluyen entre comillas, mientras que los no numéricos tienen que estar contenidos entre comillas incluso en el caso de no se inserten directamente sino a través de una variable PHP.

Añadir un registro a partir de datos contenidos en variables

También es posible añadir registros a partir de valores contenidos en variables PHP. Esta opción es, sin ninguna duda, la más utilizada, ya que lo habitual será escribir el contenido a añadir en un form y después -a través del method (POST o GET)- pasar al script de inserción esos valores como variables PHP.

Aquí tienes el código fuente de un ejemplo con la tabla anterior.

Ver código fuente

Quizá te resulten de alguna utilidad estos comentarios:

� – Observa en el código fuente que al insertar las variables en los VALUES de la sentencia MySQL ponemos cuando se trata de valores tipo cadena '$variable' (el nombre de la variable entre comillas) y cuando se trata de valores númericos sin comillas.

� – Si quieres introducir el valor NULL en un campo tipo VAR(0) define la variable así: $variable="NULL" y si quieres introducir unacadena vacía defínela de este otro modo:$var="''" -comillas dobles (") seguidas de dos comillas sencillas (') y para terminar otras comillas dobles (") y cuando hagas alusión a esa esta variable como un VALUE en la sentencia de inserción no la pongas entre comillas.

Variantes de la sentencia INSERT

La sentencia INSERT cuya sintaxis se indica más arriba como:

INSERT tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

permite algunos modificadores opciones tales como:

INSERT [LOW_PRIORITY | DELAYED] [IGNORE] tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

de ellos LOW_PRIORITY y DELAYED son incompatibles por lo que solo cabe uno u otro pero no ambos.

Veamos su utilidad. La inserción de registros y la lectura de una tabla son procesos incompatibles, pero cabe la posibilidad de que se intenten ejecutar simultáneamente. No olvides que estamos en Internet y es perfectamente posible que desde

Page 381: Manual PHP

dos ordenadores distintos, dos personas distintas estén accediendo a la misma tabla simultáneamente y que uno de los accesos sea de escritura, es decir: añadir,modificar o borrar campos en la tabla.

¿Quién tiene preferencia de paso? ¿Quien tiene que esperar?. Si la opción LOW_PRIORITY está activada, el proceso de escritura esperará a que terminen los procesos de lectura activos, pero si está activa la opción DELAYED el proceso de lectura se interrumpirá automáticamente para ceder el paso al de escritura.

Respecto a la opción IGNORE tiene utilidad cuando se trata de realizar una secuencia de inserciones. Si no está activa en el momento en el que aparezca una clave duplicada se interrumpirá el proceso de inserción, por el contrario, si estuviera activa el proceso de inserción continuará con los siguientes registros de la secuencia, aunque -como es lógico- seguirán sin insertarse los registros con clave duplicada.

Tablas para pruebas

Aquí tienes comentado un script que permite agregar aleatoriamente y de forma automática registros a la tabla demo4. Dado que en las páginas siguientes trataremos de consultas, va a resultarnos muy cómodo poder rellenarlas de forma automática.

Ver código fuente

Añadir a través de formularios

Los valores de SELECT MULTIPLE

La opción SELECT MULTIPLE dentro de un form típico de HTML permite elegir ninguno, uno o varios de los elementos de la lista. Basta con pulsar con el ratón sobre cada uno de los valores elegidos manteniendo pulsada la tecla Ctrl, es decir, puro Windows.

Para recoger los valores de esa opción se define -dentro de la etiqueta SELECT- un name tipo array. Bastaría con escribir:

SELECT MULTIPLE name=variable[ ] SIZE=6>

Como ves, variable es el nombre de la variable (esta vez sin $ delante, recuerda que no estamos en PHP sino en HTML) y va seguidode [ ] precisamente para indicar que es un array.

Lo que respecta a SIZE=6 no es otra cosa que el parámetro que indica cuántos elementos de la lista de opciones queremos que se visualicen simultáneamente en la página.

El elemento más importante son los values de cada option dentro de ese select. Los hemos escrito así:

<option value=1>Castellano <option value=2>Francés <option value=4>Inglés <option value=8>Alemán <option value=16>Búlgaro <option value=32>Chino

Fíjate que hemos mantenido exactamente el mismo orden en el que han sido definidos en el campo SET de la tabla. Observa también los valores: 1, 2, 4, 8, 16 y 32 que son precisamente las potencias de 2: 20, 21, 22, 23, 24, 25, y 26

Al ir seleccionando valores, van añadiéndose al array. Por ejemplo. Si seleccionamos Francés y Búlgaro el array sería este: var[0]=2,var[1]=16

Si sumamos esos valores (2 + 16) el resultado sería 18, y al convertir a binario este valor, resultará: 010010 que es como decirle a MySQL (mirando la cadena de derecha a izquierda, ¿lo recuerdas?) que incluya los valores segundo (Francés) y quinto (Búlgaro) del SELECT MULTIPLE que corresponden a las posiciones en las que la cadena binaria contiene un uno.

Page 382: Manual PHP

Creación del formulario

El caso más frecuente -casi el único- es que los registros de una tabla se añadan utilizando un formulario y enviando desde él los datos a un script PHP que ejecute la opción de añadir. Si no recuerdas el funcionamiento de este método, pulsa aquí

En el ejemplo hemos desarrollado un formulario para añadir registros a la tabla demo4 con las siguientes peculiaridades:

� Para los campos DNI, nombre y apellidos hemos utilizado input tipo texto y hemos recogido mediante la opción name cada uno de los campos en una variable independiente.

� Para los campos Fecha de nacimiento y hora de nacimiento hemos utilizado tres opciones select en cada una de ellas. La finalidad de estas opciones no es otra que impedir la introducción de fechas no válidas (en realidad no lo impedimos totalmente ya que, tal como está confeccionado, podría introducirse 31 de febrero, o 31 de abril). Ese aspecto es mejorable, pero para hacerlo –desde el propio formulario– tendríamos de recurrir a un lenguaje del lado del cliente (JavaScrpt por ejemplo).

� Los valores de esos tres campos (tanto en fecha como en hora) los recogemos en variables que son elementos de dos array escalares.

� El campo sexo la recogemos en input tipo radio y les asignamos valores M ó F que coinciden con los valores del campo ENUM de la tabla.

� Con el campo Fumador -opción Fumador/No fumador - hacemos exactamente lo mismo, pero asignándoles valores 1 o 0 ya que el formulario no permite la opción NULL ó cadena vacia. En el script posterior será cuando modifiquemos los valores de esas variables.

� Para el campo Idiomas utilizamos una opción select de tipo múltiple y para los values un pequeño truco que describimos aquí la izquierda.

Pues bien, aquí tienes, código fuente del formulario que hemos diseñado

Ver código fuente

Añadir nuevo registro con datos del formulario

Como recordarás, cuando se envía el contenido de un formulario mediante el method=POST y se indica como action un fichero PHPlos valores enviados son recogidos en este último fichero en variables de PHP que tienen como nombre $_POST['var'] –o$HTTP_POST_VARS['var']– donde cada una de los índices asociativos de los array (var) coinciden con los name de los diferentes campos del formulario.

A partir de ahí, bastaría con depurar los valores recibos, recoger en variables los valores depurados e incluirlos en la sentencia MySQL INSERT -la hemos visto en la página anterior- para añadirlos a la tabla correspondiente. Aquí tienes –comentado– el script:

Añadir registros Código fuente del script

En realidad, tal como habrás podido ver en el código fuente, la depuración ha sido la siguiente:

� Hemos creado un valor de fecha y hora en formatos MySQL válidos de la forma que describimos un poco más arriba.

� Hemos sumado todos los valores numéricos recibidos en el array obtenido del SELECT MULTIPLE y hemos asignado el resultado a la variable depurada que recoge el valor a escribir en el campo Idiomas. La justificación de esa suma la tienes al margen.

� La variable Fumador es la que tiene un poquito más de complicación. Veámosla con calma: Los valores que recibimos desde formulario son 1 o 0 y hemos de transformarlos en una cadena vacía o en NULL. Hemos insertado un operador condicional (un if... else) para convertir eso valores en: $var="'\N'" (comilla doble, comilla simple, barra invertida, N, comilla simple y comilla doble) ó $var="''" (comillas dobles, dos comillas simples y unas comillas dobles)

� Asignados los nuevos valores tenemos que recurrir a un pequeño truco. Venimos repitiendo que en la sentencia INSERT losnombres de las variables no numéricas que contienen los values hay que escribirlos dentro de comillas simples, pero en el caso de un campo tipo CHAR(0) hemos de hacer una excepción que sería no poner esas comillas al nombre de la variable. Al hacerlo así, se escribirían como valores –en la sentencia de inserción– uno de estos: ='\N' o =' ' (los valores de la variable) que al contenercomillas ya son interpretados por MySQL como una cadena.

Page 383: Manual PHP

Consultas en tablas

Sintaxis MySQL de selección de registros

Las sentencias de selección de registros requieren utilizar –entre otras– palabras clave como las que enumeramos a continuación. Observa que las hay dos tipos: obligatorias y opcionales. Observa también que algunas de las palabras clave son alternativas y por lo tanto, incompatibles en una misma sentencia. La inserción ha de hacerse respetando un orden tal y como se enumera aquí debajo. Si alteráramos ese orden (p. ejemplo: colocando GROUP BY antes de WHERE) se produciría un error y dejaría de ejecutarse la sentencia.

Los párrafos siguientes están organizados a dos niveles. Las intrucciones del primero de ellas tienen carácter obligatorio en la sentencia de consulta. Las del segundo, señaladas con letra cursiva, son opcionales.

� SELECT Es la primera palabra de la sentencia de búsqueda y tiene carácter obligatorio.

� SQL_BIG_RESULT Es una cláusula opcional que se usa para indicar al optimizador que el resultado va a tener una gran cantidad de registros. En ese caso, MySQL utilizará tablas temporales cuando sea necesario para optimizar la velocidad de gestión de la información. Esta cláusula también puede ser utilizada dentro de GROUP BY.

� SQL_BUFFER_RESULT Es opcional y su finalidad es la de forzar a MySQL a tratar el resultado en un fichero temporal. Ese tratamiento ayuda a MySQL a liberar recursos más rápidamente y es de gran utilidad (siempre desde el punto de vista de la rapidez) cuando es necesario un largo proceso de cálculo antes de enviar los resultados al cliente.

� HIGH_PRIORITY Esta cláusula, opcional da prioridad al comando SELECT sobre otros comandos que simultáneamente pudieran estar intentando acceder a la tabla para escribir en ella (añadir o modificar registros). Si esta opción está activa, los intentos de escritura que pudieran producirse de forma simultánea deberían esperar al final de este proceso para ejecutarse.

� campo1, campo2, ... Tienen carácter obligatorio y señalan los campos de la tabla que deben incluirse en la consulta. La consulta sólo devolverá información de aquellos campos que estén enumerados aquí. Si se desea incluir a todos campos bastará con excribir * en esta posición. En este caso *tiene la condición de carácter comodín cuyo significado es todos los campos. Los campos numéricos pueden llevar asociadas funciones MySQL que devuelven información estadística. La sintaxis de algunas de esas funciones es la siguientes:

� MAX(campo) Devuelve el mayor de los valores de ese campo de entre todos los registros de la la consulta. Los registros comparados puedes ser todos o una parte de ellos. Si la sentencia incluye la condición GROUP BY, en cuyo caso devolverá el máximo de cada grupo y si incluye la opción WHERE solo serán comparados los registros que cumplan tal condición.

� MIN(campo) Idéntica a la anterior en cuanto a criterios de selección, esta función devuelve el manorde los valores del campo en el ámbito establecido por las evuntuales condiciones que pueda incluir la sentencia.

� AVG(campo) Devuelve el valor promedio de todos los registros numéricos seleccionados con los mismos criterios del caso anterior.

� SUM(campo) Devuelve la suma de todos los valores del campo seleccionado y en el ámbito establecido por los parámetros de la consulta.

� STDDEV(campo) Devuelve la estimación de la desviación típica de la población formada por los valores del campo.

� COUNT(campo) Cuenta los valores no nulos del campo indicado.

� Cuando se aplica una de estas funciones el resultado de la consulta contiene una sola línea, salvo que se incluya opciónGROUP BY, en cuyo caso devolverá tantas líneas como grupos resulten.

� FROM tabla Esta expresión indica a MySQL el nombre de la tabla en la que debe efectuarse la consulta.

� WHERE definicion Esta instrucción tiene carácter opcional y su utilidad es la de filtrar la consulta estableciendo los criterios de selección de losregistros que debe devolver. Si se omite WHERE, la consulta devolverá todos los registros de la tabla. En la tabla de ejemplos tienes información sobre las formas de definir los criterios de selección de esta opción.

Page 384: Manual PHP

� GROUP BY definicion Tiene carácter opcional y su finalidad es la de presentar los resultados de la consulta agrupados según el criterio establecido en su definición. Resulta de gran utilidad cuando se pretende obtener valores estadísticos de los registros quecumplen determinadas condiciones (las condiciones del agrupamiento).

� ORDER BY definicion También tiene carácter opcional y su utilidad es la de presentar la información de la consulta ordenada por los contenidos de uno o varios campos. Siempre tiene como opción complementaria de que en cada campo utilizado para la ordenaciónpuede establecerse uno de estos criterios ASC (ascendente, es el valor por defecto) o DESC (descendente). Si no se establece ningún orden los resultados de la consulta aparecerán en el mismo orden en el que fueron añadidos los registros.

� LIMIT m, n Esta cláusula es opcional y permite establecer cuántos y cuáles registros han de presentarse en la salida de la consulta. Por ejemplo: LIMIT 4, 8 indicaría a MySQL que la consulta debería mostrar OCHO registros contados a partir del quinto (sí, el quinto porque LIMIT considera el primer registro como CERO). El criterio límite se aplica sobre los resultados de la salida, es decir, sobre los resultados seleccionados, ordenados yfiltrados siguiendo los criterios establecidos por las cláusulas anteriores. Si se escribe como un solo parámetro (LIMIT k), MySQL lo interpretará como que k es el segundo de ellos y que el primero es CERO, es decir: LIMIT 0, k

Algunos ejemplos de consultas

Las consultas de los datos y registros contenidos en una tabla ofrecen un amplísimo abanico de posibilidades a partir de las opciones descritas anteriormente. Veamos algunas de las posibilidades.

La consulta más simple

Si utilizamos la sentencia

SELECT * FROM tabla

obtendremos información sobre todos los campos (*) y la salida estará en el mismo orden en el que fueron añadidos los datos. Si visualizas este ejemplo, verás que aparecen ordenados por el valor autonumérico del campo Contador lo cual, como ves, resulta coherente con la afirmación anterior.

Ejecutar consulta Ver código fuente

Consultando sólo algunos campos

Ahora utilizaremos la sentencia

SELECT campo1,campo2, ... FROM tabla

y tendremos como resultado una lista completa, por el mismo orden que la anterior, pero sólo mostrando los campos indicados.

Ejecutar consulta Ver código fuente

¡Cuidado! En los comentarios contenidos en estos ejemplos puedes ver la forma en la que mysql_fetch_row ymysql_fetch_array tratan los índices escalares de los resultados que producen los SELECT de MySQL. Los valores de los índices se asignan a los contenidos de los campos por el mismo orden en el que estos se escriben en la sentencia SELECT. El campo1 (primero que se escribe) será recogido por el elemento de índice cero del array, el campo2 será recogido con índice uno y así sucesivamente

Consultando sólo algunos campos y limitando la salida a n registros

Page 385: Manual PHP

Ahora utilizaremos la sentencia

SELECT campo1,campo2, ... FROM tabla LIMIT (n, m)

y tendremos como resultado una lista que contendrá m registros a partir del n+1, por el mismo orden que la anterior, y mostrando los campos indicados.

Ejecutar consulta Ver código fuente

Consultando sólo algunos campos y ordenando la salida

Utilizaremos la sentencia MySQL de esta forma

SELECT campo1,campo2, ... FROM tabla ORDER BY campo_n [ASC|DESC], campo_m [ASC|DESC]

y tendremos como resultado una lista ordenada por el primero de los campos indicados en ORDER BY, y en caso de coincidencia de valores en ese campo, utilizaríamos el criterio de ordenación señalado en segundo lugar.

Ejecutar consulta Ver código fuente

Consulta seleccionando registros

Utilizaremos la sentencia MySQL de esta forma

SELECT campo1, ... FROM tabla WHERE condición

que nos devolverá la lista de registros que cumplen la condición indicada. Aquí tienes un ejemplo muy sencillo.

Ejecutar consulta Ver código fuente

Consultas condicionadas

La claúsula WHERE permite un variado abanico de condiciones, que trataremos de resumir aquí. Algunos de ellas son los siguientes:

Operador Tipo de campo

Sintaxis Descripción Código fuente

Ver ejemplo

= Numérico WHEREcampo=num Selecciona los registros que contienen en el campo un valor igual a num Ver Probar

= Cadena WHEREcampo="cadena" Selecciona los registros que contienen en el campo una cadena idéntica a cadena (*)

Ver Probar

< Numérico WHEREcampo<num Selecciona los registros que contienen en el campo un valor menor a num Ver Probar

< Cadena WHEREcampo<"cadena" Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmenores que los de la cadena, siendo n el número de caracteres que contiene cadena. (**)

Ver Probar

<= Numérico WHEREcampo<=num Selecciona los registros que contienen en el campo un valor menor O igual a num

Ver Probar

<= Cadena WHEREcampo<="cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmenores que los de la cadena, siendo n el número de caracteres que contiene cadena y añade respecto al caso anterior la opción de que en caso de que ambos valores fueran iguales también los presentaría (**)

Ver Probar

> Numérico WHEREcampo>num Selecciona los registros que contienen en el campo un valor mayor a num Ver Probar

> Cadena WHEREcampo>"cadena" Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmayores que los de la cadena, siendo n el número de caracteres que contiene cadena. (**)

Ver Probar

Page 386: Manual PHP

>= Numérico WHEREcampo>=num Selecciona los registros que contienen en el campo un valor mayor o igual a num Ver Probar

>= Cadena WHEREcampo>="cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmayores que los de la cadena, siendo n el número de caracteres que contiene cadena y añade respecto al caso anterior la opción de que en caso de que ambos valores fueran iguales también los presentaría (**)

Ver Probar

IN Numérico o Cadena

WHERE campoIN (valor1,valor2..)

Selecciona los registros que contienen en el campo valores que coinciden con alguno de los especificados dentro del paréntesis. Cuando se trata de valores no numéricoz han de ir entre comillas

Ver Probar

BETWEEN Numérico o Cadena

WHERE campoBETWEEN valor1 AND valor2

Selecciona los registros en los que los valores contenidos en el campo seleccionado están comprendidos en el intervalo valor1 (mínimo) – valor2 (máximo) incluyendo en la selección ambos extremos. Cuando los contenidos de los campos son cadenas sigue los mismos criterios que se indican para los demás operadores de comparación

Ver Probar

IS NULL Cadena WHERE campo IS NULL Selecciona los registros en los que los valores contenidos en el campo seleccionado son NULOS

Ver Probar

IS NOT NULL

Cadena WHERE campo IS NOT NULL Selecciona los registros en los que los valores contenidos en el campo seleccionado son NO NULOS

Ver Probar

(*) Cuando se trata de cadenas de caracteres, el concepto menor que significa anterior en la ordenación de los caracteres según su código ASCII y mayor quesignifica posterior en esa misma ordenación. (**) La discriminación de Mayúsculas/Minúsculas dependerá del tipo de campo. Recuerda que los tipo BLOB hacen esa discriminación, mientras que los de tipo TEXT son insensibles a Mayúsculas/Minúsculas.

Cuando se trata de comparar cadenas MySQL dispone de una potente instrucción (LIKE) que permite establecer los criterios de selección a toda o parte de la cadena. Su sintaxis contempla distintas posibilidades utilizando dos comodines>: % (que se comporta de forma similar al (*) en las búsquedas de Windows) y _ (de comportamiento similar a (?) en Windows). Aquí tienes algunas de sus posibilidades:

Sintaxis Descripción Código fuente

Ver ejemplo

WHERE campoLIKE '%cadena%'

Selecciona todos los registros que contengan la cadena en el campo indicado sea cual fuere su posición Ver Probar

WHERE campoLIKE 'cadena%'

Selecciona todos los registros en los que el campo indicado que contengan la cadena exactamente al principio del campo

Ver Probar

WHERE campoLIKE '%cadena'

Selecciona todos los registros en los que el campo indicado que contengan la cadena exactamente al final del campo

Ver Probar

WHERE campoLIKE '_cadena%'

Selecciona todos los registros en los que el primer caracter del campo puede ser cualquiera pero los siguientes han de serexactamente los indicados en cadena pudiendo ir seguidos de cualesquiera otros caracteres Ver Probar

El comodín (_) puede ir tanto al principio como al final y puede repetirse tantas veces como sea necesario. Por tanto sería correctoLIKE '___es%' y también LIKE 'a___es%' así como: LIKE '%a___es'.

La claúsula WHERE aun tiene más opciones. Acepta múltiples condiciones vinculadas por los operadores lógicos AND, OR, NOT o sus sintaxis equivalentes: &&, || y !.

El comportamiento de estos operadores es idéntico al descrito para sus homónimos de PHP en esta página

Utilizando funciones sobre campos

Por medio de la sintaxis

SELECT MAX(campo1), MIN (campo2), ... FROM tabla

obtendríamos UNA SOLA FILA cuyos valores serían los resultados de la aplicación de las funciones a todos los registros del campo indicado. Aquí tienes un ejemplo que determina todos los valores de esos estadísticos aplicados al campo Contador de nuestra tabla demo4.

Ejecutar consulta Ver código fuente

Page 387: Manual PHP

Aplicando la opción GROUP BY

Tal como señalamos al margen, las funciones anteriores pueden aplicarse a grupos de registros seleccionados mediante un criterioGROUP BY (nombre del campo)

En este ejemplo obtendremos los mismos parámetros estadísticos que en el anterior, pero ahora agrupados por sexo, lo que significaría que obtendremos dos filas de resultados. Aquí tienes el ejemplo

Ejecutar consulta Ver código fuente

Recuento de resultados

PHP dispone de dos funciones que permiten conocer el número de registros de la tabla afectados por una sentencia MySQL.

mysql_num_rows ($c )

Esta función devuelve un valor numérico que recoge el número de registros que cumplen las condiciones establecidas en unaconsulta. Sólo es válido para sentencia tipo SELECT

mysql_affected_rows($c )

En este caso la función devuelve también el número de registros afectados, pero sólo en el caso de que la sentencia MySQL haya producido modificaciones en los contenidos de la tabla. Es decir, sólo recoge resultados de sentencias que: añaden, modifican o borranregistros.

mysql_result($resultado,num, campo)

Esta función PHP permite obtener un solo campo de uno solo de los registros obtenidos como resultado de una consulta MySQL. El parámetro $resultado es la variable que recoge el resultado obtenido de la ejecución de mysql_query de forma idéntica a como lo hacíamos en otras consultas. El valor num es un número entero que indica el número de fila de la que queremos extraer el valor contenido en uno de sus campos. El valor campo indica el número del campo que tratamos de extraer. Este número (la primera posición siempre es cero) indica el número de orden del campo tal como está especificado en la sentencia SELECT. Si en esta sentencia se incluyera * (extraer todos los campos) consideraría el orden en el que está creada la estructura de la tabla que los contiene.

Este es el código fuente de un ejemplo comentado y este un enlace de prueba del script.

Manejo de fechas en las consultas

MySQL dispone de algunas cláusulas de gestión de fechas que pueden tener una gran utilidad a la hora de gestionar consultas. Son las siguientes:

DATE_FORMAT( campo,formato)

Las diferentes opciones de formato las tienes en la tabla. Es importante tener en cuenta que la sintaxis correcta es %Y (sin espacio) ya que si hubiera un espacio % Y interpretaría la letra Y como un texto a incluir.

CURDATE()

Dentro de DATE_FORMAT se puede incluir -en vez del nombre del campo- una cadena en la que se indique una fecha en formatoYYYY-MM-DD hh:mm:ss. Puedes verlo en los ejemplos. De igual modo es posible sustituir el nombre del campo -o la cadena- por la funciónCURDATE() que recoge la fecha actual del sistema (únicamente día, mes y año). A efectos de horas, minutos y segundos CURDATE() va a tomar el mediodía de la fecha actual.

CURTIME()

Se comporta de forma similar a CURDATE(). Devuelve la hora actual del sistema que alberga el servidor MySQL en formato hh:mm:ss

Page 388: Manual PHP

CURRENT_TIMESTAMP()

Se comporta de forma similar a CURDATE(). Devuelve la fecha y hora actual del sistema en formato YYYY-MM-DD hh:mm:ss

NOW()

Es un alias de CURRENT_TIMESTAMP().

Aquí tienes una tabla resumen y algunos ejemplos

Formato Descripción Sintaxis Ver código

Ver ejemplo

%d Día del mes en formato de dos dígitos DATE_FORMAT(Nacimiento,'%d') Ver Probar

%e Día del mes en formato de uno ó dos dígitos DATE_FORMAT(Nacimiento,'%e') Ver Probar

%D Número de día seguido del sufijo en inglés DATE_FORMAT(Nacimiento,'%D') Ver Probar

%m Número del mes en formato de dos dígitos DATE_FORMAT(Nacimiento,'%m') Ver Probar

%c Número del mes en formato de uno o dos dígitos DATE_FORMAT(Nacimiento,'%c') Ver Probar

%M Nombre del mes (en inglés) DATE_FORMAT(Nacimiento,'%M') Ver Probar

%b Nombre del mes abreviado (en inglés) DATE_FORMAT(Nacimiento,'%b') Ver Probar

%y Número del año en formato de dos dígitos DATE_FORMAT(Nacimiento,'%y') Ver Probar

%Y Número del año en formato de cuatro dígitos DATE_FORMAT(Nacimiento,'%Y') Ver Probar

%w Número de día de la semana 0=Domingo ... 6=Sábado DATE_FORMAT(Nacimiento,'%w') Ver Probar

%W Nombre del día de la semana (en inglés) DATE_FORMAT(Nacimiento,'%W') Ver Probar

%W Nombre abreviado del día de la semana (en inglés) DATE_FORMAT(Nacimiento,'%W') Ver Probar

%j Número de día del año en formato de 3 dígitos DATE_FORMAT(Nacimiento,'%j') Ver Probar

%U Número de semana del año considerando el DOMINGO como primer día de la semana (en formato de dos dígitos)

DATE_FORMAT(Nacimiento,'%U') Ver Probar

%u Número de semana del año considerando el LUNES como primer día de la semana (en formato de dos dígitos)

DATE_FORMAT(Nacimiento,'%u') Ver Probar

La fecha para los ejemplos siguientes la extraemos de una variable del tipo: $fecha="2005-10-12 14:23:42" ya que la tabla no contiene campos de fecha que incluyan horas, minutos y segundos

%H Hora con dos dígitos (formato 0 a 24 horas) DATE_FORMAT($fecha,'%H')

Ver Probar

%k Hora con uno ó dos dígitos (formato 0 a 24 horas) DATE_FORMAT($fecha,'%k')

%h Hora con dos dígitos (formato 0 a 12 horas) DATE_FORMAT($fecha,'%h')

%I Hora con uno ó dos dígitos (formato 0 a 12 horas) DATE_FORMAT($fecha,'%I')

%i Minutos con dos dígitos DATE_FORMAT($fecha,'%i')

%s Segundos con dos dígitos DATE_FORMAT($fecha,'%s')

%r Hora completa (HH:mm:ss) en formato de 12 horas indicando AM ó PM DATE_FORMAT($fecha,'%r')

%T Hora completa (HH:mm:ss) en formato de 24 horas DATE_FORMAT($fecha,'%T')

% texto Incluye el texto que se indica detrás del % DATE_FORMAT($fecha,'% texto')

%p Añade AM ó PM dependiendo de la Hora DATE_FORMAT($fecha,'%p')

Se pueden combinar a voluntad varias opciones utilizando una sintaxis de este tipo: '% Hoy es: %d - %m - %Y % es %W % estamos en el mes de %M % <br>y van transcurridos %j % dias de este año.<br>Son las %r'

Creación de tablas a partir de la consulta de otra tabla

Es frecuente -podría decirse que es lo habitual- relacionar tablas mediante campos con idéntico contenido. Supongamos que entre los individuos de nuestra tabla demo4 se pretende establecer un proceso de selección para elegir entre ellos un número determinado deastronautas, pongamos por caso.

Supongamos también, que la selección va a constar de tres pruebas que serán juzgadas y calificadas por tres tribunales distintos.

Page 389: Manual PHP

Una primera opción sería crear tres tablas –una para cada tribunal– e incluir en ellas todos los datos de cada uno de los individuos. Esa opción es factible pero no es ni la más cómoda, ni tampoco es la más rápida ni la que menos espacio de almacenamiento necesita. No debemos olvidar que una tabla puede tener una enorme cantidad de registros.

Una opción alternativa sería crear tres nuevas tablas que sólo contuvieran dos campos cada una. Por ejemplo el campo DNI y el campo Calificación. Como quiera que el campo DNI ha de contener los mismos valores en las cuatro tablas y además es un campo únicopodrían crearse las nuevas tablas y luego copiar en cada una de ellas todos los DNI de la tabla original. Nos garantizaría que no habría errores en los DNI y además nos garantizaría que se incluyeran todos los aspirantes en esas nuevas tablas.

Aquí tienes el código fuente de un script que crea esas tres tablas (a las que hemos llamado demodat1, demodat2 y demodat3.

Ver código fuente

Una consulta conjunta de varias tablas

MySQL permite realizar consultas simultáneas en registros situados en varias tablas.

Para ese menester se usa la siguiente sintaxis:

SELECT tabla1.campo1, tabla2.campo2, ... FROM tabla1, tabla2

en la que, como ves, modificamos ligeramente la sintaxis ya que anteponemos el nombre de la tabla al del campo correspondiente separando ambos nombres por un punto, con lo cual no hay posibilidad de error de identificación del campo incluso cuando campos de distinta tabla tengan el mismo nombre. Otra innovación -respecto a los ejemplos anteriores- es que detrás de la cláusula FROM escribimos los nombres de todas las tablas que está usando SELECT. A partir de ahí se pueden establecer todo tipo de relaciones para las sentencias WHERE, ORDER BY y GROUP BY utilizando para ellocampos de cualquiera de las tablas sin otra particularidad más que poner cuidado al aludir a los campos utilizando siempre la sintaxisnombre_tabla.nombre_campo. A modo de ejemplo –hemos procurado comentarlo línea a línea– aquí tienes un script PHP que hace una consulta conjunta de las tablasdemo4, demodat1, demodat2 y demodat3 y nos presenta una tabla con los datos personales y las puntuaciones de las tres pruebas así como las suma de puntos de las tres y, además, ordena los resultados -de mayor a menor- según la suma de las tres puntuaciones.

Ejecutar consulta Ver código fuente

Los arrays de la sentencia SELECT

Aunque están comentados en los códigos fuente de los scripts queremos hacer algunas precisiones sobre los resultados de las consultas de tablas. Se trata de los índices de los arrays que se obtienen mediante las funciones: mysql_fetch_array() y mysql_fetch_row().

Los índices escalares, en ambos casos, cuanto tratan información obtenida mediante una sentencia SELECT coinciden con el orden en el que han sido establecidos los campos en esa instrucción concreta. De modo que el primer de esos nombres de campos sería asociado con el índice cero de estos array, el segundo con el índice 1 y así sucesivamente.

En el caso del array asociativo devuelto por la primera de estas funciones, los índices coinciden siempre con los nombres de los campos de los que han sido extraídos los datos.

En el caso de que la consulta afecte a varias tablas (recuerda que los campos se asignan poniendo tabla.campo (nombre de la tabla y nombre del campo) el índice del array asociativo sería esa expresión con el punto incluido.

Page 390: Manual PHP

Modificar registros

Sintaxis MySQL de modificación de registros

Las sentencias MySQL que permiten la modificación de registros en las tablas pueden incluir algunas de las siguientes cláusulas que, al igual que ocurría en casos anteriores, pueden tener categoría de obligatorias u opcionales.

El orden en que deben estar indicadas ha de seguir la misma secuencia en la que están descritas aquí.

� UPDATE Tiene carácter obligatorio, debe ser la primera palabra de la sentencia e indica a MySQL que vamos realizar una modificación.

� LOW_PRIORITY Es opcional e indica a MySQL espere a que se terminen de hacer las consultas que en ese momento pudiera haber en proceso antes realizar la actualización.

� IGNORE Es opcional. Cuando se incluye en una sentencia el proceso de actualización no se interrumpe si aparece un conflicto de clave duplicada en uno de los registros en proceso. Simplemente ignora ese registro y continúa con los siguientes Si no se incluye, el proceso de modificación se interrumpe en el momento en que encuentre un conflicto de clave duplicada. Tanto con ignore como sin esa cláusula, en el caso de duplicidad de clave NUNCA se efectúan las modificaciones.

� tabla Es obligatoria y contiene el nombre de la tabla que pretendemos modificar.

� SET Tiene carácter obligatorio y debe estar delante de las definiciones de campo y valor.

� campo1 = valor1 Es obligatoria al menos una definición. Indica el nombre del campo a modificar (campo1) y el valor que se asignará a ese campo. Si se pretende modificar más de un campo se repetirá esta definición tantas veces como sea necesario, separando cada una de ellas por una coma.

� WHERE Es un campo opcional y su comportamiento es idéntico al señalado al mencionar el proceso de consultas.

� ORDER BY Tiene idéntica funcionalidad a la descrita al referirnos a consultas

Modificar un campo en todos los registros de una tabla

La sentencia MySQL, que permite modificar uno o varios campos en todos los registros de una tabla, es la siguiente:

UPDATE tabla SET campo1=valor1, campo2=valor2 ¡Cuidado! Hay que tener muy presente que con esta sentencia –en la que no aparece WHERE– se modificarán TODOS LOS REGISTROS DE LA TABLA y por lo tanto los campos modificados tendrán el mismo valor en todos los registros.

Algunas consideraciones sobre la sintaxis

Siempre que manejes PHP y MySQL debes tener muy presente lo siguiente:

� – MySQL requiere SIEMPRE que los valores tipo cadena que incluyen campos de fecha vayan entre comillas. Por el contrario, losnuméricos no deben llevar comillas.

� – Presta mucha atención a esto cuando escribas los valores directamente en la sentencia MySQL.

� –Cuando pases valores desde una variable PHP debes tener muy en cuenta las consideraciones anteriores y si el contenido de la variable es una cadena que va a ser tratada como tal por MySQL tienes dos opciones para evitar el error:

Page 391: Manual PHP

� • Definir la variable así: $variable ="'valor'" (comillas dobles, comilla simple al principio y comilla simple, comilla doble al final) y poner en la sentencia MySQL el nombre de la variable sin entrecomillar, o

� • Definir la variable PHP así: $variable ="valor" y al escribir el nombre de esa variable en la sentencia MySQL escribirlo entre comillas sencillas, es decir, así: '$variable'

� – No pienses que es caprichoso el orden que hemos puesto en las comillas. Recuerda que al llamar a la sentencia MySQL, el contenido de la sentencia va entre comillas (que por costumbre son comillas dobles, por esa razón todo entrecomillado que vaya dentro de esa sentencia ha de usar comillas simples para evitar un error seguro). De ahí que al definir una variable PHP en la forma $variable ="'valor'" las comillas dobles exteriores indican a PHP que se trata de una cadena, por lo que, al pasar la variable a MySQL éste recibirá el contenido de la cadena que es, logicamente: 'valor' y en este caso las comillas forman parte del valor, razón por el que no es necesario escribir -en la sentencia MySQL- el nombre de la variable entrecomillado.

En este primer ejemplo, hemos incluido una actualización de tablas que pondrá puntuación 7 en la primera de las pruebas a todos losaspirantes a astronautas de nuestro ejemplo. Es un caso de actualización sin la condición WHERE y tiene el código comentado.

Ejecutar la modificación Ver código fuente

Selección y modificación de un solo registro

Es una de las opciones más habituales. Es el caso en el que –mediante un formulario– asignamos una condición a WHERE y simultáneamente asignamos los nuevos valor del campo o campos elegidos. Requiere la siguiente sintaxis:

UPDATE tabla SET campo1=valor1, campo2=valor2 WHERE condición

La condición es fundamental en esta opción y normalmente aludirá a un campo índice (clave principal o única), de modo que sea un solo registro el que cumpla la condición. Podría ser el caso, en nuestro ejemplo, del campo DNI que por su unicidad garantizaría que la modificación solamente va a afectar a uno solo de los registros.

El ejemplo siguiente nos permitirá hacer modificaciones de este tipo en la tabla deomodat2. Observa el código fuente y verás que mediante un simple recurso JavaScript, el script que realiza la modificación nos reenvía al formulario con un mensaje de confirmación de la modificación.

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Modificación simúltanea de un campo en cualquiera de los registros Aquí tienes un ejemplo que permite visualizar el valor actual de todas las puntuaciones de la prueba 2 de los astronautas así como sus nombres y apellidos y DNI y en la cual se pueden modificar ninguno, uno, varios o todos los valores y posteriormente actualizarlos todos con los nuevos valores.

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Borrar registros y salvar datos

Sintaxis MySQL para borrado de registros

La sintaxis MySQL para las sentencia de borrado de registros de una tabla puede contener las siguientes cláusulas que, al igual que ocurría en casos anteriores, pueden tener categoría de obligatorias u opcionales. La secuencia en la que deben estar indicadas en la sentencia es idéntica al orden en que están descritas aquí.

� DELETE Tiene carácter obligatorio. Debe ser la primera palabra de la sentencia e indica a MySQL que tratamos de borrar uno o másregistros.

� LOW_PRIORITY

Page 392: Manual PHP

Es opcional e indica a MySQL que espere para realizar la actualización a que terminen las consultas del fichero (en el caso de haber alguna en proceso).

� FROM Tiene carácter obligatorio y debe preceder a la definición de la tabla en la que se pretende eliminar registros.

� tabla Es obligatoria e indica el nombre de la tabla en la que pretendemos efectuar el borrado o eliminación de los registros.

� WHERE Es un campo opcional y su comportamiento es idéntico al señalado en al mencionar el proceso de consultas.

� LIMIT n La opción LIMIT es opcional y propia de MySQL. Su finalidad es limitar el tiempo de ejecución del comando DELETE ya que cuando está activada devuelve el control al potencial cliente después de borrar n registros, con lo que en procesos de borrados muy largos (ficheros de gran tamaño) no obliga a esperar a borrado total para proceder a la consulta de la tabla. Cuando se utiliza esta opción, la sentencia DELETE debe repetirse hasta que el número de registros pendientes de borrado sea inferior al valor de n.

Borrar todos los registros de una tabla La sentencia MySQL que permite borrar todos los registros de una tabla es la siguiente: DELETE FROM tabla

Ten muy presente que con esta sentencia -en la que no aparece WHERE- se BORRARÁN TODOS LOS REGISTROS DE LA TABLA.

Respecto a otras posibles opciones, no difiere en nada de lo indicado en la página anterior. Simplemente habría que sustituir en aquellos script UPDATE por DELETE. Borrar un registro no es otra cosa que un caso particular de modificación.

Integridad referencial tras el borrado de una tabla

¿Recuerdas el ejemplo de las pruebas de selección de astronautas? ¿Recuerdas que las tres tablas de puntuaciones habían sido creadas a partir de la tabla de datos de los aspirantes? ¿Qué ocurriría si borrásemos uno o varios registros de una de ellas? ¿Qué ocurriría se después de crear esas tablas añadiésemos nuevos aspirantes a la lista de candidatos?

Es obvio que si no hacemos algo para evitarlo se perdería la integridad referencial –la relación uno a uno– entre los registros de esas tablas.

Ocurriría que no todos los individuos que están incluidos en una de esas tablas lo estarían en las demás y por tanto, al ejecutar consultas o modificaciones posteriores correríamos el riesgo de que se produjeran errores.

Esa situación es fácilmente evitable modificando ligeramente los scripts con los que se realizan los procesos de altas y bajas. Bastaría con añadirles algunas sentencias que cada vez que se efectúa un alta o baja en el fichero de datos personales efectúen el mismo proceso en todos los demás ficheros relacionados con aquel.

Aquí tienes comentado el código fuente de la modificación añadida al script que registra los nuevos aspirantes en el fichero de altas de la tabla demo4. Con esta modificación se actualizarían automáticamente los ficheros demodat1, demodat2 y demodat3 cada vez que se añadiera un nuevo aspirante.

El formulario no requiere ninguna modificación, Sólo es necesario realizar los cambios en el script que realiza la inserción.

Añadir nuevo aspirante Ver código fuente

Hecho este pequeño inciso -creemos que importante y necesario - continuaremos con la referencia al borrado de registros.

En este ejemplo, tienes el código fuente de un script que realiza el borrado de un registo –mediante un formulario en el que se inserta el DNI– tanto en la tabla demo4 como demodat1, demodat2 y demodat3 manteniendo la integridad referencial entre los cuatro ficheros.

Ver código fuente

Page 393: Manual PHP

Borrar registros seleccionándolos de una lista

En el ejemplo siguiente tienes el código para utilizar la cláusula WHERE en un proceso de borrado de registros que presenta un formulario que contiene una lista con todos los registros actuales y una casilla de verificación por cada uno.

Al marcar las casillas y enviar el formulario el script que recibe los datos procede al borrado de todos los registros marcados en todas la tablas afectadas.

Ver formulario Ver script Ejecutar ejemplo

Guardar y recuperar bases de datos y o tablas

Aunque es perfectamente factible desarrollar scripts propios que permitan guardar y recuperar tanto las estructuras como los datos de una tabla ó de la base de datos completa, mencionaremos aquí una de las posibilidades más cómodas de hacerlos.

PhpMyAdmin es una magnifica herramienta para hacer y recuperar copias de seguridad.

Si abrimos la utilidad http://localhost/phpmyadmin/ podremos ver los dos enlaces que ves en la imagen –SQL y Exportar– que permiten importar y exportar tanto estructuras como datos y estructuras.

Al pulsar sobre Exportar nos aparecerá una página como esta:

Page 394: Manual PHP

donde podremos elegir una, varias o todas la tablas y que según la opciones elegidas nos permite exportar estructuras y/o datos, según las casillas de verificación que tengamos marcadas. Además nos permite elegir el formato en el que queremos guardar la copia –en nuestro caso elegiríamos SQL– y también según esté o no activada la casilla de verificación Enviar visualizar el fichero generado o guardarlo con el nombre que hayamos consignado en la caja de texto Plantilla del nombre del archivo.

Para restaurar datos y/o estructuras desde un fichero de seguridad creado mediante el proceso anterior usaríamos la opción SQL de la primera imagen. A través de ella accederíamos a una página cuyo contenido estamos visualizando en esta última imagen.

Bastaría pulsar en examinar, buscar el fichero de seguridad y pulsar continúe. MySQL se encargaría de restaurar –en la base de datos a la que pertenezcan– todas las tablas contenidas en esa copia.

Optimización de tablas

Cuando se ejecuta la sentencia DELETE -pese a que son eliminados los valores de los campos- se conservan las posiciones de los registros borrados, con lo cual no se reduce el tamaño de la tabla. Esas posiciones de registro serán utilizadas por MySQL para escribir los registros que se vayan añadiendo después del proceso de borrado.

Para eliminar esos registros vacíos y reducir el tamaño de una tabla, MySQL dispone de una sentencia que es la siguiente:

OPTIMIZE TABLE tabla

Esta sentencia -que debe usarse después de un proceso de borrado amplio- depura la tabla eliminando los registros inutilizados por el proceso DELETE, con lo que logra una reducción del tamaño de la tabla a su dimensión óptima.

Imágenes en tablas

Imágenes en tablas

Peculiaridades de las tablas

Las tablas que han de contener imágenes deben tener campos del tipo BLOB, MEDIUMBLOB o LONGBLOB, (recuerda sus tipos) pudiendo elegir aquel de ellos que más se adecue al tamaño, en bytes, de las imágenes que se desean guardar en la tabla.

En el ejemplo hemos incluido un campo BLOB para incluir la imagen, insertando también campos para recoger su nombre, su tamaño (en bytes), su formato (el tipo de fichero transferido) así como un campo autoincremental.

<?php #el nombre de la tabla $base="ejemplos"; #definimos otra variable con el NOMBRE QUE QUEREMOS DAR A LA TABLA $tabla="fotos"; # establecemos la conexión con el servidor $conexion=mysql_connect ("localhost","pepe","pepa"); #Seleccionamos la BASE DE DATOS en la que PRETENDEMOS CREAR LA TABLA mysql_select_db ($base, $conexion);

Page 395: Manual PHP

$crear="CREATE TABLE IF NOT EXISTS $tabla ("; $crear.="num_ident INT(10) unsigned NOT NULL AUTO_INCREMENT,"; $crear.="imagen BLOB NOT NULL, "; $crear.="nombre VARCHAR(255) NOT NULL DEFAULT '',"; $crear.="tamano VARCHAR(15) NOT NULL DEFAULT '',"; $crear.="formato VARCHAR(10) NOT NULL DEFAULT '',"; $crear.="PRIMARY KEY (num_ident))type=MyISAM"; #Creamos la cadena, comprobamos si esa instrucción devuelve # VERDADERO o FALSO # y dependiendo de ellos insertamos el mensaje de exito o fracaso if(mysql_db_query ($base,$crear ,$conexion)) { echo "<h2> Tabla $tabla creada con EXITO </h2><br>"; }else{ echo "<h2> La tabla $tabla NO HA PODIDO CREARSE</h2><br>"; }; # cerramos la conexión... y listo... mysql_close($conexion); ?>

Aquí tiene el código fuente que te permitiría crear la tabla fotos e insertar automáticamente algunas imágenes de ejemplo.

Ejemplo de transferencia de imagen

Transferencia de la imagen

El formulario para realizar la transferencia de la imagen no tiene particularidades. Es un formulario habitual para transferencia de ficheros. Lo único reseñable sería incluir un campo oculto en el que pudiera especificarse una restricción en cuanto al tamaño máximo permitido para cada imagen y que debe estar acorde con el tipo de campo utilizado en la tabla.

<FORM ENCTYPE="multipart/form-data" ACTION="ejemplo211.php" METHOD="post"> <INPUT type="hidden" name="lim_tamano" value="65000"> <p><b>Selecciona la imagen a transferir<b><br> <INPUT type="file" name="foto"><br> <p><b>Título la imagen<b><br> <INPUT type="text" name="titulo"><br></p> <p><INPUT type="submit" name="enviar" value="Aceptar"></p> </FORM>

Ejemplo de transferencia de imagen

Comprobación del tipo de imagen

Al transferir imágenes jpg ó png el type MIME que recibía el servidor es distinto según el navegador que se utilice para hacer la transferencia.

Aquí debajo, en el código fuente del script que actualiza la base de datos, tienes los nombres de esos tipos asociados a los navegadores más usuales.

<?php $foto_name= $_FILES['foto']['name']; $foto_size= $_FILES['foto']['size']; $foto_type= $_FILES['foto']['type']; $foto_temporal= $_FILES['foto']['tmp_name'];

Page 396: Manual PHP

$lim_tamano= $_POST['lim_tamano']; $foto_titulo= $_POST['titulo']; /* limitamos los formatos de imagen admitidos a: png que segun del navegador que ulicemos puede ser: en IE image/x-png en Firefox y Mozilla image/png jpg que puede tener como tipo en IE image/pjpeg en Firefox y Mozilla image/jpeg gif que tiene como tipo image/gif en todos los navegadores Mira los comentarios al margen sobre la variable $extensión */ if ($foto_type=="image/x-png" OR $foto_type=="image/png"){ $extension="image/png"; } if ($foto_type=="image/pjpeg" OR $foto_type=="image/jpeg"){ $extension="image/jpeg"; } if ($foto_type=="image/gif" OR $foto_type=="image/gif"){ $extension="image/gif"; } # condicionamos la inserción a que la foto tenga nombre, # un tamaño distinto de cero y menor de límite establecido # en el formulario y que la variable extensión sea no nula if ($foto_name != "" AND $foto_size != 0 AND $foto_titulo !='' AND $foto_size<=$lim_tamano AND $extension !=''){ /*reconversion de la imagen para meter en la tabla abrimos el fichero temporal en modo lectura "r" binaria"b"*/ $f1= fopen($foto_temporal,"rb"); #leemos el fichero completo limitando # la lectura al tamaño de fichero $foto_reconvertida = fread($f1, $foto_size); #anteponemos \ a las comillas que pudiera contener el fichero # para evitar que sean interpretadas como final de cadena $foto_reconvertida=addslashes($foto_reconvertida); # abrimos la base de datos y escribimos las intrucciones de inserción # en el campo BLOB insertaremos la foto_reconvertida $base="ejemplos"; $tabla="fotos"; $conexion=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $conexion); $meter="INSERT INTO ".$tabla; $meter .=" (num_ident, imagen, nombre, tamano, formato) "; $meter .=" VALUES('','$foto_reconvertida','$foto_titulo',"; $meter .= "$foto_size, '$extension')"; if (@mysql_query($meter,$conexion)){ print "Foto guardada en la tabla"; }else{ print "Ha habido un error al guardar la foto"; } }else{ echo "<h2>No ha podido transferirse el fichero</h2>"; } mysql_close(); ?>

Hay otro aspecto a tener en cuenta. Esa discriminación de tipos se plantea únicamente cuando Apache recibe una transferencia. Cuando se visualiza un contenido las cabeceras tipo de contenido (header("content-type: xx")) pueden ser las mismas para todos los navegadores. Esa es la razón por la que a la hora de incluir el formato en la tabla utilizamos image/jpg, image/gif o image/png.

¿Cómo guardamos la imagen?

Page 397: Manual PHP

La información recibida a través del formulario requiere un ligero retoque antes de incluirla en le campo BLOB de la tabla. Esa reconversión requiere abrir la imagen en modo binario (rb) -parece que solo en el caso de Windows– leer el fichero completo y añadirle \ antes de las comillas mediante addslashes.

Una vez hecho el retoque ya puede guardarse sin más problema.

PNG con transparencias en Internet Explorer

Internet Explorer no permite visualizar de forma automática las transparencias de las imágenes con formato png. Existen en la redalgunos recursos que permiten solventar ese problema. Hemos elegido uno de ellos –pngfix.js- que puedes ver en este enlace.

Se trata de un fichero JavaScript que basta incluir en la cabecera HMTL de la página de la forma que ves en el ejemplo de la parte derecha. Cuando un navegador IE es detectado se ejecuta una función contenida en ese fichero que analiza la página, busca imágenes con extensión png y les aplica la transparencia adecuada.

Por esa razón, es probable que inicialmente (al cargar la página) se visualice la imagen opaca y que, posteriormente, adquiera la transparencia.

Visualización de imágenes

La lectura de una imagen utiliza solo dos instrucciones. Incluir la cabecera Header en el que se indica el tipo de contenido (el famoso nombre MIME de la imagen) y luego imprimir el contenido del campo. Pero (por aquello de que header debe ir incluida en el script antes que cualquier otra salida) si pretendemos incluir en una página algo más que una imagen tendremos que invocar esas dos funciones, de forma independiente, para cada una de ellas.

Por esa razón, en el ejemplo que tienes al margen, al desarrollar el ejemplo que permite visualizar todas las imágenes de la tabla hemos tenido que incluir un script que va leyendo la tabla que contiene las imágenes para extraer los campos informativos y a la hora de ver la imagen hemos de recurrir a la misma técnica que se utilizaba para ver las imágenes dinámicas. Es decir, poner una etiqueta de imagen de las de HTML pero -en vez de escribir el nombre de la imagen- poniendo incluyendo como nombre el del script que las visualiza y pasándole elnúmero (valor del campo autoincremental) de la imagen que pretendí visualizar.

Script para leer imágenes de la base datos <? $numero=$_REQUEST['n']; $base="ejemplos"; $tabla="fotos"; $conexion=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $conexion); $sacar = "SELECT * FROM ".$tabla." WHERE (num_ident=$numero)" ; $resultado = mysql_query($sacar,$conexion); while ($registro = mysql_fetch_array($resultado)){ $tipo_foto=$registro['formato']; header("Content-type: $tipo_foto"); echo $registro['imagen']; } mysql_close(); ?>

Ver imágenes guardadas

El problema de los PNG en IE

El JavaScript que asigna la transparencia a las imágenes en formato png las identifica buscando la coincidencia de los tres últimos caracteres del nombre de la imagen con la extensión png.

Cuando se trata de imágenes dinámicas el nombre de la imagen coinciden con el nombre de la llamada al script que se utiliza para su visualización. Por eso, para advertir a JavaScript de que se trata de una imagen png hemos incluido el condicional que puedes ver en el ejemplo. De esa forma, cuando se trata de una imagen en este formato incluimos en la petición una variable con el valor png de forma que pueda ser reconocida por pngfix.js y aplicada la transparencia requerida. Script para leer la base de datos <html>

Page 398: Manual PHP

<head> <!-- al margen te comentamos la razón por la que --> <!-- se incluyen estas líneas en rojo --> <!--[if IE ]> <script type="text/javascript" src="pngfix.js"></script> <![endif]--> </head> <body> <? $base="ejemplos"; $tabla="fotos"; $conexion=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $conexion); $sacar = "SELECT * FROM ".$tabla; $resultado = mysql_query($sacar,$conexion); while ($registro = mysql_fetch_array($resultado)){ print "<center>Titulo de la imagen: ".$registro['nombre']."<br>"; /* la inclusión de este condicional obedece a los problemas que plantea la visualización de las transparencias de las imágenes png en Internet Explorer. Al margen justificamos las razones de su inclusión */ if($registro['formato']=="image/png"){ print "<img src='ver_foto.php?n=".$registro['num_ident']."&v=png'><br>"; }else{ print "<img src='ver_foto.php?n=".$registro['num_ident']."'><br>"; } print "Tamaño de la imagen: ".$registro['tamano']." bytes </center>"; } mysql_close(); ?> </body> </html>

Tablas InnoDB

Las tablas InnoDB

Creación de una tabla InnoDB

La creación de tablas de este tipo no presenta ninguna dificultad añadida. El proceso es idéntico a las tablas habituales sin más que añadir Type=InnoDB después de cerrar el paréntesis de la sentencia de creación de la tabla.

Este script crea una tabla InnoDB con idénticos campos a los utilizados en el caso de la tabla MyISAM que hemos visto en páginas anteriores. La sintaxis, muy similar a la utilizada allí es esta:

<?php $base="ejemplos"; $tabla="demoinno"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); $crear="CREATE TABLE $tabla ("; $crear.="Contador TINYINT(8) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,"; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, ";

Page 399: Manual PHP

$crear.="Apellido2 VARCHAR (15) not null, "; $crear.="Nacimiento DATE DEFAULT '1970-12-21', "; $crear.="Hora TIME DEFAULT '00:00:00', "; $crear.="Sexo Enum('M','F') DEFAULT 'M' not null, "; $crear.="Fumador CHAR(0) , "; $crear.="Idiomas SET(' Castellano',' Francés','Inglés', ' Alemán',' Búlgaro',' Chino'), "; $crear.=" PRIMARY KEY(DNI), "; $crear.=" UNIQUE auto (Contador)"; $crear.=")"; # esta es la única diferencia con el proceso de # creación de tablas MyISAM $crear.=" Type=InnoDB"; if(mysql_query ($crear ,$c)) { echo "<h2> Tabla $tabla creada con EXITO </h2><br>"; }else{ echo "<h2> La tabla $tabla NO HA PODIDO CREARSE "; # echo mysql_error ($c)."<br>"; $numerror=mysql_errno ($c); if ($numerror==1050){echo "porque YA EXISTE</h2>";} }; mysql_close($c); ?>

Ver código fuente

¡Cuidado! Bajo Windows, al crear una base de datos o tabla InnoDB el nombre de la misma aparecerá en minúsculas independientemente de la sintaxis que hayamos utilizado en su creación. Para prever posibles conflictos al ejecutar los scripts en diferentes plataformas utilizaremos minúsculas para los nombres de tablas.

Una vez creadas, las tablas InnoDB se comportan –a efectos de uso– exactamente igual que las que hemos venido utilizando en las páginas anteriores. No es preciso hacer ningún tipo de modificación en la sintaxis. Por tanto, es totalmente válido todo lo ya comentado respecto a: altas, modificaciones, consultas y bajas.

Las transacciones

Uno de los riesgos que se plantean en la gestión de bases de datos es que pueda producirse una interrupción del proceso mientras se está actualizando una o varias tablas. Pongamos como ejemplo el cobro de nuestra nómina. Son necesarias dos anotaciones simultáneas: El cargo en la cuenta del organismo pagador y el abono en nuestra cuenta bancaria. Si se interrumpiera fortuitamente el proceso en el intermedio de las dos operaciones podría darse la circunstancia de que apareciera registrado el pago sin que se llegaran a anotar los haberes en nuestra cuenta.

Las transacciones evitan este tipo de situaciones ya que los datos se registran de manera provisional y no se consolidan hasta que una instrucción confirme que esas anotaciones tienen carácter definitivo. Para ello, MySQL dispone de tres sentencias: BEGIN, COMMIT yROLLBACK.

Sintaxis de las transacciones

Existen tres sentencias para gestionar las transacciones. Son las siguientes:

mysql_query("BEGIN",$conexion)

Su ejecución requiere que esté activa la conexión $c con el servidor de base de datos e indica a MySQL que en ese punto comienza una transacción. Todas las sentencias que se ejecuten a partir de ella tendrán carácter provisional y no se ejecutarán de forma efectiva hasta que encuentre una sentencia de finalización.

Page 400: Manual PHP

mysql_query("ROLLBACK",$conexion)

Mediante esta sentencia advertimos a MySQL que finaliza la transacción pero que no debe hacerse efectiva ninguna de las modificaciones incluidas en ella.

mysql_query("COMMIT",$conexion)

Esta sentencia advierte a MySQL que ha finalizado la transacción y que SÍ debe hacer efectivos todos los cambios incluidos en ella.

Precauciones a tener en cuenta

Cuando se utilizan campos autoincrementales en tablas InnoDB los contadores se van incrementando al añadir registros (incluso de forma provisional) con lo cual si se aborta la inclusión con un ROLLBACK ese contador mantiene el incremento y en inserciones posteriores partirá de ese valor acumulado.

Por ejemplo. Si partimos de una tabla vacía y hacemos una transacción de dos registros (número 1 y número 2 en el campo autoincremental) y la finalizamos con ROLLBACK, no se insertarán pero en una inserción posterior el contador autoincremental comenzará a partir del valor 2.

Las primeras transacciones

<?php $base="ejemplos"; # escribimos el nombre de la tabla en MINUSCULAS # para asegurar la compatibilidad entre plataformas $tabla="demoinno"; $conexion=mysql_connect("localhost","pepe","pepa"); mysql_select_db ($base, $conexion); # insertamos la sentencia BEGIN para indicar el comienzo # de una transacción mysql_query("BEGIN",$conexion); /* hasta que no aparezca una sentencia que finalice la transacción (ROLLBACK ó COMMIT) las modificaciones en la tabla serán registradas de forma "provisional") */ mysql_query("INSERT $tabla (DNI,Nombre,Apellido1,Apellido2, Nacimiento,Sexo,Hora,Fumador,Idiomas) VALUES ('111111','Alvaro','Alonso','Azcárate','1954-11-23', 'M','16:24:52',NULL,3)",$conexion); if (mysql_errno($conexion)==0){ echo "Registro AÑADIDO<br>"; }else{ if (mysql_errno($conexion)==1062){ echo "No ha podido añadirse el registro <br>"; echo "Ya existe un registro con este DNI<br>"; }else{ $numerror=mysql_errno($conexion); $descrerror=mysql_error($conexion); echo "Se ha producido un error nº $numerror<br>"; echo "<br>que corresponde a: $descrerror<br>"; } } # indicamos el final de la transacción, en este caso con ROLLBACK # por lo tanto el registro con DNI 111111 no será insertado en la tabla mysql_query("ROLLBACK",$conexion); # incluyamos una nueva transacción mysql_query("BEGIN",$conexion); mysql_query("INSERT $tabla (DNI,Nombre,Apellido1,Apellido2, Nacimiento,Sexo,Hora,Fumador,Idiomas) VALUES

Page 401: Manual PHP

('222222','Genoveva','Zarabozo','Zitrón','1964-01-14', 'F','16:18:20',NULL,2)",$conexion); if (mysql_errno($conexion)==0){ echo "Registro AÑADIDO"; }else{ if (mysql_errno($conexion)==1062){ echo "No ha podido añadirse el registro <br>"; echo "Ya existe un registro con este DNI"; }else{ $numerror=mysql_errno($conexion); $descrerror=mysql_error($conexion); echo "Se ha producido un error nº $numerror"; echo "<br>que corresponde a: $descrerror"; } } # indicamos el final de la transacción, en este caso con COMMIT # por lo tanto el registro con DNI 222222 si será insertado en la tabla mysql_query("COMMIT",$conexion); # leamos el contenido de la tabla para ver el resultado $resultado= mysql_query("SELECT * FROM $tabla" ,$conexion); print "<br>Lectura de la tabla depués del commit<br>"; while ($registro = mysql_fetch_row($resultado)){ foreach($registro as $clave){ echo $clave,"<br>"; } } mysql_close(); ?>

La integridad referencial

Elementos necesarios para la integridad referencial

La integridad referencial ha de establecerse siempre entre dos tablas. Una de ellas ha de comportarse como tabla principal (suele llamarse tabla padre y la otra sería la tabla vinculada ó tabla hijo. Es imprescindible:

� Que la tabla principal tenga un índice primario (PRIMARY KEY)

� Que la tabla vinculada tenga un índice (no es necesario que sea ni único ni primario) asociado a campos de tipo idéntico a los que se usen para índice de la tabla principal.

Si observas el código fuente del ejemplo que tienes a la derecha podrás observar que utilizamos el número del DNI (único para alumno) como elemento de vinculación de la tabla de datos personales con la que incluye las direcciones.

Integridad referencial en tablas InnoDB

Cuando se trabaja con varias tablas que tienen algún tipo de vínculo resulta interesante disponer de mecanismos que protejan o impidan acciones no deseadas. Supongamos, como veremos en los ejemplos posteriores que pretendemos utilizar una tabla con datos de alumnos y otra tabla distinta para las calificaciones de esos alumnos. Si no tomamos ninguna precaución (bien sea mediante los script o mediante el diseño de las tablas) podría darse la circunstancia de que incluyéramos calificaciones a alumnos inexistentes, en materias de las que no están matriculados, etcétera. También podría darse la circunstancia de que diéramos de baja a un alumno pero que se mantuvieran las calificaciones vinculadas a él. Todas estas circunstancias suelen producir efectos indeseados y las tablas InnoDB pueden ser diseñadas para prever este tipo de situaciones.

Sintaxis para la vinculación de tablas

Los vínculos entre tablas suelen establecer en el momento de la creación de la tabla vinculada.

CREATE TABLE tabla (campo1, campo2,... KEY nombre (campo de vinculacion ),

Page 402: Manual PHP

FOREIGN KEY (campo de vinculacion) REFERENCES nombre_de la tabla principal (Indice primario de la tabla principal) ) Type=InnoDB

donde el campo de vinculacion ha de ser un índice (no es necesario que sea PRIMARY KEY ni UNIQUE) y donde Indice primario de la tabla principal ha de ser un índice primario (PRIMARY KEY) de la tabla principal. Debe haber plena coincidencia (tanto en tipos como contenidos) entre ambos índices.

<?php $base="ejemplos"; $tabla1="principal"; $tabla2="vinculada"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); # creación de la tabla principal type InnoDB $crear="CREATE TABLE IF NOT EXISTS $tabla1 ("; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, "; $crear.="Apellido2 VARCHAR (15) not null, "; # el indice primario es imprescindible. Recuerda que debe # estar definido sobre campos NO NULOS $crear.=" PRIMARY KEY(DNI) "; $crear.=")"; $crear.=" Type=InnoDB"; # creamos la tabla principal comprobando el resultado if(@mysql_query ($crear ,$c)){ print "La tabla ".$tabla1." ha sido creada<br>"; }else{ print "No se ha creado ".$tabla1." ha habido un error<br>"; } # crearemos la tabla vinculada $crear="CREATE TABLE IF NOT EXISTS $tabla2 ("; $crear.="IDENTIDAD CHAR(8) NOT NULL, "; $crear.="calle VARCHAR (20), "; $crear.="poblacion VARCHAR (20), "; $crear.="distrito VARCHAR(5), "; # creamos el índice (lo llamamos asociador) para la vinculación # en este caso no será ni primario ni único # Observa que el campo IDENTIDAD de esta tabla CHAR(8) # es idéntico al campo DNI de la tabla principal $crear.=" KEY asociador(IDENTIDAD), "; #establecemos la vinculación de ambos índices $crear.=" FOREIGN KEY (IDENTIDAD) REFERENCES $tabla1(DNI) "; $crear.=") TYPE = INNODB"; # creamos (y comprobamos la creación) la tabla vinculada if(@mysql_query ($crear ,$c)){ print "La tabla ".$tabla2." ha sido creada<br>"; }else{ print "No se ha creado ".$tabla2." ha habido un error<br>"; } mysql_close(); ?>

Modificación de estructuras

La modificación de estructuras en tablas vinculadas puede hacerse de forma idéntica a la estudiada para los casos generales de MySQL siempre que esas modificaciones no afecten a los campos mediante los que se establecen las vinculaciones entre tablas.

Aquí tienes un ejemplo en se borran y añaden campos en ambas tablas. Como puedes ver la sintaxis es exactamente la misma que utilizamos en temas anteriores.

Page 403: Manual PHP

<?php $base="ejemplos"; $tabla="principal"; $tabla1="vinculada"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); if(mysql_query("ALTER TABLE $tabla ADD Segundo_Apellido VARCHAR(40)",$c)){ print "Sea ha creado el nuevo campo en ".$tabla."<br>"; } if(mysql_query("ALTER TABLE $tabla DROP Apellido2",$c)){ print "Sea ha borrado el campo Apellido 2 en ".$tabla."<br>"; } if(mysql_query("ALTER TABLE $tabla1 ADD DP VARCHAR(5)",$c)){ print "Sea ha creado el nuevo campo en ".$tabla1."<br>"; } if(mysql_query("ALTER TABLE $tabla1 DROP distrito",$c)){ print "Sea ha borrado el campo distrito en ".$tabla1."<br>"; } mysql_close(); ?>

Modificación o borrado de campos vinculados

Las sentencias MySQL que deban modificar o eliminar campos utilizados para establecer vínculos entre tablas requieren de un parámetro especial (CONSTRAINT) –puede ser distinto en cada una de las tablas– que es necesario conocer previamente.

La forma de visualizarlo es ejecutar la sentencia: SHOW CREATE TABLE nombre tabla que devuelve como resultado un array asociativo con dos índices. Uno de ellos -llamado Table- que contiene el nombre de la tabla y el otro -Create Table- que contiene la estructura con la que ha sido creada la tabla, pero incluyendo el parámetro CONSTRAINT seguido de su valor. Ese valor es precisamente el que necesitamos para hacer modificaciones en los campos asociados de las tablas vinculadas.

En ceste enlace podrás puedes ver el código fuente del script que ejecuta esa sentencia ejecución de esa sentencia.

Conocido el valor de parámetro anterior el proceso de borrado del vínculo actual requiere la siguiente sintaxis:

ALTER TABLE nombre de la tabla DROP FOREIGN KEY parametro

Cuando se trata de añadir un nuevo vínculo con una tabla principal habremos de utilizar la siguiente sentencia:

ALTER TABLE nombre de la tabla ADD [CONSTRAINT parametro] FOREIGN KEY parametro REFERENCES tabla principal(clave primaria)

El parámetro CONSTRAIT (encerrado en corchetes en el párrafo anterior) es OPCIONAL y solo habría de utilzarse en el caso de que existiera ya una vinculación previa de esa tabla.

En este ejemplo determinaremos el valor de CONSTRAINT y modificaremos campos asociados de la tabla vinculada.

<?php $base="ejemplos"; $tabla="vinculada"; $tabla1="principal"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); $resultado=mysql_query( "SHOW CREATE TABLE $tabla",$c); $v=mysql_fetch_array($resultado); # extreamos de Create Table la cadena que empiza por CONSTRAINT # y que acaba por FOREING KEY lo guardamos en el array $coin preg_match ('/CONSTRAINT.*FOREIGN KEY/', $v['Create Table'], $coin);

Page 404: Manual PHP

# extraemos el parametro quitando el CONSTRAINT que lleva delante # y el FOREIGN KEY que lleva al final $para=str_replace("FOREIGN KEY",'',str_replace("CONSTRAINT",'',$coin[0])); print "El valor de CONSTRAINT es: ".$para."<br>"; # eliminamos el vinculo con la clave externa incluyendo en la sentancia # el valor del parametro obtenido el proceso anterior if(mysql_query("ALTER TABLE $tabla DROP FOREIGN KEY $para",$c)){ print "Se ha realizado con éxito el borrado del vínculo<br>"; } # añadimos el nuevo vínculo (en este caso rehacemos el anterior # pero el proceso es idéntico) if(mysql_query("ALTER TABLE $tabla ADD CONSTRAINT $para FOREIGN KEY(IDENTIDAD) REFERENCES $tabla1(DNI)",$c)){ print "Se ha reestablecido con éxito vínculo<br>"; } mysql_close(); ?>

La función preg_match

En el ejemplo hemos utilizamos esta la función (relacionada con el uso de expresiones regulares): preg_match( patron, cadena, coincidencias ) dónde patron es un patrón de búsqueda, cad es la cadena que la han de realizarse las búsquedas y coin es un array que recoge todas las coincidencias encontradas en la cadena.

El patrón de búsqueda que hemos utilizado en el ejemplo /CONSTRAINT.*FOREIGN KEY/ debe interpretarse de la siguiente forma. Los caracteres / indican el comienzo y el final del patrón, el . indica que entre CONSTRAIT y FOREING se permite cualquier carácter y, además,* indica que ese caracter cualquiera puede repetirse cero o más veces.

Añadir registros a la tabla vinculada

Si ejecutas este ejemplo habiendo seguido la secuencia de estos materiales verás que se produce un error nº 1452. Cannot add or update a child row: a foreign key constraint fails (`ejemplos`.`vinculada`, CONSTRAINT `vinculada_ibfk_1` FOREIGN KEY (`IDENTIDAD`) REFERENCES `principal` (`DNI`)). Es lógico que así sea porque estamos intentando añadir un registro a la tabla vinculada y ello requeriría que en el campo DNI de la tabla principal existiera otro registro con un valor igual al que pretendemos introducir en la tabla vinculada.

Inserción en tabla vinculada Ver script

Añadiremos un registro a la tabla principal con el DNI anterior:

Inserción en tabla principal Ver script

y ahora ya podremos ejecutar -sin errores- el script que inserta datos en la tabla vinculada. Podremos ejecutar aquel script tantas veces como queramos ya que -el campo IDENTIDAD está definido como KEY y por tanto permite duplicados- no hemos establecido la condición de índice PRIMARIO ó UNICO.

Borrar o modificar registros en la tabla principal

Sin intentamos borrar un registro de la tabla principal mediante un script como el que tienes en este ejemplo verás que se produce unerror nº 1451 que corresponde a: Cannot delete or update a parent row: a foreign key constraint fails (`ejemplos`.`vinculada`, CONSTRAINT `vinculada_ibfk_1` FOREIGN KEY (`IDENTIDAD`) REFERENCES `principal` (`DNI`)) advirtiéndonos de que no se realiza el borrado porque existen registros en la tabla vinculada con valores asociados al índice del campo que pretendemos borrar y, de permitir hacerlo, se rompería la integridad referencial ya que quedarían registros huérfanos en la tabla vinculada.

Borrar tabla principal Ver script

Page 405: Manual PHP

Sin tratamos de modificar un registro de la tabla principal y la modificación afecta al índice que realiza la asociación con la tabla (o tablas) vinculadas se produciría -por las mismas razones y en las mismas circunstancias- un error 1451 que corresponde a: Cannot delete or update a parent row: a foreign key constraint fails (`ejemplos`.`vinculada`, CONSTRAINT `vinculada_ibfk_1` FOREIGN KEY (`IDENTIDAD`) REFERENCES `principal` (`DNI`)) que impediría la modificación.

Modificar DNI en tabla principal Ver script

Si -al tratar de borrar o modificar un registro de la tabla principal– no existieran en la tabla (o tablas vinculadas) registros asociados con él, el proceso de modificación se realizaría sin ningún problema y sin dar ningún mensaje de error o advertencia.

Borrado de tablas vinculadas

Si pretendemos eliminar una tabla principal sin haberlo hecho previamente con sus tablas vinculadas recibiremos un mensaje de error y no se producirá el borrado.

Las tablas vinculadas sí permiten el borrado y una vez que éstas ya han sido eliminadas (o quitada la vinculación) ya podrán borrarse sin problemas las tablas principales. El orden de borrado (si ambas pretenden borrarse desde el mismo script)es important.e Primero se borra la vinculada y luego la principal. Este es el código fuente del script que realiza es borrado según el orden correcto.

Opciones adicionales de FOREIGN KEY

La claúsula FOREIGN KEY permite añadirte -detrás de la definición ya comentada y sin poner coma separándola de ella- los parámetros ON DELETE y ON UPDATE en las que se permite especificar una de las siguientes opciones: ON DELETE RESTRICT

Esta condición (es la condición por defecto de MySQL y no es preciso escribirla) indica a MySQL que interrumpa el proceso de borrado y de un mensaje de error cuando se intente borrar un registro de la tabla principal cuando en la tabla vinculada existan registros asociados al valor que se pretende borrar.

ON DELETE NO ACTION

Es un sinónimo de la anterior.

ON DELETE CASCADE

Cuando se especifica esta opción, al borrar un registro de la tabla principal se borrarán de forma automática todos los de la tabla vinculada que estuvieran asociados al valor de la clave foránea que se trata de borrar. Con ello se conseguiría una actualización automática de la segunda tabla y se mantendría la identidad referencial.

ON DELETE SET NULL

Con esta opción, al borrar el registro de la tabla principal no se borrarían los que tuviera asociados la tabla secundaria pero tomarían valor NULL todos los índices de ella coincidentes con la clave primaria de la tabla principal.

Para el caso de ON UPDATE las opciones son estas:

ON UPDATE RESTRICT ON UPDATE CASCADE ON UPDATE SET NULL

Su comportamiento es idéntico a sus homónimas del caso anterior.

¡Cuidado! El uso de la opción SET NULL requiere que el campo indicado en FOREIGN KEY esté permita valores nulos. Si está definido con flag NOT NULL (como ocurre en los ejemplos que tienes al margen) daría un mensaje de error.

Page 406: Manual PHP

¡Cuidado! Al incluir ON DELETE y ON UPTADE (si se incluyen ambas) han de hacerse por este mismo orden. Si se cambiara este orden daría un mensaje de error y no se ejecutarían.

Automatización de procesos

Creamos dos tablas idénticas a las anteriores incluyendo algunos datos en ellas.

<?php $base="ejemplos"; $tabla1="principal1"; $tabla2="vinculada1"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); # creación de la tabla principal type InnoDB $crear="CREATE TABLE IF NOT EXISTS $tabla1 ("; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, "; $crear.="Apellido2 VARCHAR (15) not null, "; $crear.=" PRIMARY KEY(DNI) "; $crear.=")"; $crear.=" Type=InnoDB"; # creamos la tabla principal comprobando el resultado if(@mysql_query ($crear ,$c)){ print "La tabla ".$tabla1." ha sido creada<br>"; }else{ print "No se ha creado ".$tabla1." ha habido un error<br>"; } # crearemos la tabla vinculada $crear="CREATE TABLE IF NOT EXISTS $tabla2 ("; $crear.="IDENTIDAD CHAR(8) NOT NULL, "; $crear.="calle VARCHAR (20), "; $crear.="poblacion VARCHAR (20), "; $crear.="distrito VARCHAR(5), "; #creamos la tabla vinculada las opciones de DELETE y UPDATE $crear.=" KEY asociador(IDENTIDAD), "; #establecemos la vinculación de ambos índices $crear.=" FOREIGN KEY (IDENTIDAD) REFERENCES $tabla1(DNI) "; $crear.=" ON DELETE CASCADE "; $crear.=" ON UPDATE CASCADE "; $crear.=") TYPE = INNODB"; # creamos (y comprobamos la creación) la tabla vinculada if(@mysql_query ($crear ,$c)){ print "La tabla ".$tabla2." ha sido creada<br>"; }else{ print "No se ha creado ".$tabla2." ha habido un error<br>"; } # añadimos registros a la tabla principa1 mysql_query("INSERT $tabla1 (DNI,Nombre,Apellido1,Apellido2) VALUES ('111111','Robustiano','Iglesias','Pérez')",$c); mysql_query("INSERT $tabla1 (DNI,Nombre,Apellido1,Apellido2) VALUES ('222222','Ambrosio','Morales','Gómez')",$c); # añadimos registros a la tabla vinculada1 mysql_query("INSERT $tabla2 (IDENTIDAD,calle,poblacion,distrito) VALUES ('111111','Calle Asturias,3','Oviedo','33001')",$c); mysql_query("INSERT $tabla2 (IDENTIDAD,calle,poblacion,distrito) VALUES ('111111','Calle Palencia,3','Logroño','78541')",$c); mysql_query("INSERT $tabla2 (IDENTIDAD,calle,poblacion,distrito)

Page 407: Manual PHP

VALUES ('222222','Calle Anunciación,3','Algeciras','21541')",$c); mysql_close(); ?>

Ver contenidos de tablas Ver código fuente

Modificar registros en cascada

<?php $base="ejemplos"; $tabla="principal1"; $conexion=mysql_connect("localhost","pepe","pepa"); mysql_select_db($base,$conexion); # modificamos un registro mysql_query("UPDATE $tabla SET DNI='123456' WHERE DNI='111111'",$conexion); # borramos un registro mysql_query("DELETE FROM $tabla WHERE (DNI='222222')",$conexion); if (mysql_errno($conexion)==0){echo "<h2>Tablas actualizadas</b></H2>"; }else{ print "Ha habido un error al actualizar"; } mysql_close(); ?>

Importación / exportación de datos

Definición de tablas

En este ejemplo de tablas vinculadas con integridad relacional vamos a proponer la situación cuyo esquema puedes ver en el gráfico y cuya descripción es la siguiente:

Page 408: Manual PHP

� – Crearemos una primera tabla (alumnos) que va a contener datos personales de un grupo de personas. Para evitar duplicar un mismo alumno y alumnos sin DNI, vamos a utilizar como índice primario (PRIMARY KEY) el campo DNI (que es único para cada persona).

� – La condición de que el campo DNI sea PRIMARY KEY nos obliga a definirlo con el flag NOT NULL, dado que esta es una condición necesaria para la definición de índices primarios.

� – Crearemos una segunda tabla (domicilios) con los domicilios de cada uno de los alumnos, identificándolos también por su DNI. Para evitar que un mismo alumno pueda tener dos domicilios asignamos a al campo DNI de esta tabla la condición de índice único (UNIQUE). El índice UNIQUE podríamos haberlo creado también como PRIMARY KEY ya que la única diferencia de comportamiento entre ambos es el hecho que aquel admitiría valores NULOS pero, dado que debemos evitar domicilios sin alumnos, insertaremos el flag NOT NULL en este campo.

� El hecho de utilizar dos tablas no tiene otro sentido que la ejemplificación ya que lo habitual sería que todos los datos estuvieran en una misma tabla.

� – Vincularemos ambas tablas de modo que no puedan crearse direcciones de alumnos inexistentes y les pondremos la opción de actualización y borrado en cascada. De esta forma las acciones en la tabla de alumno se reflejarían automáticamente en la tabla de domicilios.

� – La tercera de las tablas (evaluaciones) tiene como finalidad definir las distintas evaluaciones que puede realizarse a cada alumno. Tendrá dos campos. Uno descriptivo (el nombre de la evaluación) y otro identificativo (no nulo y único) que será tratado como PRIMARY KEY para evitar duplicidades y porque, además, va a ser utilizado como clave foránea en la tabla notas.

� – La tabla notas va a tener tres campos: DNI, nº de evaluación y calificación. Estableceremos restricciones para evitar que:

� • Podamos calificar a un alumno inexistente.

� • Podamos calificar una evaluación no incluida entre las previstas.

� • Podamos poner a cada alumno más de una calificación por evaluación.

� – Creando una índice primario formado por los campos DNI y evaluación evitaremos la última de las situaciones y añadiendo una vinculación con las tablas alumnos y evaluaciones estaremos en condiciones de evitar las dos primeras.

Estructura de tablas y relaciones

Para desarrollar los ejemplos de este capítulo vamos a crear las tablas, cuyas estructuras e interrelaciones que puedes ver en el código fuente siguiente:

<?php $base="ejemplos"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); ############################################### # Creación de la tabla nombres con indice primario DNI # tabla de nombres con índice primario en DNI ############################################### $crear="CREATE TABLE IF NOT EXISTS alumnos ("; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, "; $crear.="Apellido2 VARCHAR (15) not null, "; $crear.=" PRIMARY KEY(DNI) "; $crear.=")"; $crear.=" Type=InnoDB"; if(mysql_query ($crear ,$c)){ print "tabla <b>nombres</b> creada<BR>"; }else{ print "ha habido un error al crear la tabla <b>alumnos</b><BR>"; } ###############################################

Page 409: Manual PHP

# Creación de la tabla direcciones # tabla de nombres con índice único en DNI # para evitar dos direcciones al mismo alumno # y clave foránea nombres(DNI) # para evitar direcciones no asociadas a un alumno # concreto. Se activa la opción de actualizar # en cascada y de borrar en cascada ############################################### $crear="CREATE TABLE IF NOT EXISTS domicilios ("; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="calle VARCHAR (20), "; $crear.="poblacion VARCHAR (20), "; $crear.="distrito VARCHAR(5), "; $crear.=" UNIQUE identidad (DNI), "; $crear.="FOREIGN KEY (DNI) REFERENCES alumnos(DNI) "; $crear.="ON DELETE CASCADE "; $crear.="ON UPDATE CASCADE "; $crear.=") TYPE = INNODB"; if(mysql_query ($crear ,$c)){ print "tabla <b>domicilios</b> creada<br>"; }else{ print "ha habido un error al crear la tabla <b>domicilios</b><BR>"; } ############################################### # Creación de la tabla nombres con indice primario EVALUACIONES # tabla de nombres con índice primario en NUMERO ############################################### $crear="CREATE TABLE IF NOT EXISTS evaluaciones ("; $crear.="NUMERO CHAR(1) NOT NULL, "; $crear.="nombre_evaluacion VARCHAR (20) NOT NULL, "; $crear.=" PRIMARY KEY(NUMERO)"; $crear.=")"; $crear.=" Type=InnoDB"; if(mysql_query ($crear ,$c)){ print "tabla <b>evaluaciones</b> creada<BR>"; }else{ print "ha habido un error al crear la tabla <b>evaluaciones</b><BR>"; } ############################################### # Creación de la tabla notas # indice UNICO para los campos DNI y evaluacion # con ello se impide calificar dos veces al mismo # alumno en la misma evaluacion # claves foráneas (DOS) # el DNI de nombres para evitar calificar a alumnos inexistentes # el NUMERO de la tabla evaluaciones para evitar calificar # DOS VECES en una evaluación a un alumno ############################################### $crear="CREATE TABLE IF NOT EXISTS notas ("; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="evaluacion CHAR (1) NOT NULL, "; $crear.="calificacion TINYINT (2), "; /* observa que este indice primario está formado por dos campos (DNI y evalucion) y que, como siempre en el caso de PRIMARY KEY ambos son de tipo NOT NULL */ $crear.=" PRIMARY KEY vemaos(DNI,evaluacion), "; /* Fijate en la secuencia siguiente: 1º.- Creamos el índice 2º.- Establecemos la clave foránea 3º.- Establecemo las condiciones ON DELETE 4º.- Establecemos las condiciones ON UPDTE Es muy importe mantener esta secuencia para evitar

Page 410: Manual PHP

errores MySQL */ $crear.=" INDEX identico (DNI), "; $crear.="FOREIGN KEY (DNI) REFERENCES alumnos(DNI) "; $crear.="ON DELETE CASCADE "; $crear.="ON UPDATE CASCADE,"; /* Esta tabla tiene dos claves foráneas asociadas a dos tablas la anterior definida sobre alumnos como tabla principal y esta que incluimos a continuación asociada con evaluaciones Como ves repetimos la secuencia descrita anteriormente Es importante establecer estas definiciones de una en una (tal como ves en este ejemplo) y seguir la secuencia comentada anteriormente */ $crear.=" INDEX evalua (evaluacion),"; $crear.="FOREIGN KEY (evaluacion) REFERENCES evaluaciones(NUMERO) "; $crear.="ON DELETE CASCADE "; $crear.="ON UPDATE CASCADE"; $crear.=") TYPE = INNODB"; if(mysql_query ($crear ,$c)){ print "tabla <b>notas</b> creada <BR>"; }else{ print "ha habido un error al crear la tabla <b>notas</b><BR>"; echo mysql_error ($c)."<br>"; echo mysql_errno ($c); } mysql_close(); ?>

¡Cuidado! Como puedes observar en la imagen, al definir la estructura de las tablas es muy importante prestar atención a que los campos vinculados sean del mismo tipo y dimensión. Observa también que los campos de referencia de los vínculos que se establecen (en las tablas primarias) tienen que ser definidos como PRIMARY KEY y que, por tanto, han de establecerse como no nulos (NOT NULL).

Importación y exportación de datos

Es esta una opción interesante por la posibilidad que ofrece de intercambiar datos entre diferentes fuentes y aplicaciones.

Importación de ficheros

MySQL permite insertar en sus tablas los contenidos de ficheros de texto. Para ello utiliza la sentencia que tienes al margen y que detallaremos a continuación.

� LOAD DATA INFILE Es un contenido obligatorio que defina la opción de insertar datos desde un fichero externo. nombre del fichero Se incluye inmediatamente después de la anterior, es obligatorio y debe contener (entre comillas) la ruta, el nombre y la extensión del fichero que contiene los datos a insertar.

� [REPLACE|IGNORE] Es opcional. Si se omite se producirá un mensaje de error si el fichero contiene valores iguales a los contenidos en los campos de la tabla que no admiten duplicados. Con la opción REPLACE sustituiría los valores existentes en la tabla y con la opción IGNORE

� INTO TABLE nombre Tiene carácter obligatorio y debe incluir como nombre el de la tabla a la que se pretenden agregar los registros.

� FIELDS Tiene carácter OPCIONAL y permite incluir especificaciones sobre cuales son los caracteres delimitadores de campos y los que indican el final del campo. Si se omite FIELDS no podrán incluirse los ENCLOSED BY ni TERMINATED BY de campo.

Page 411: Manual PHP

� ENCLOSED BY Permite espeficar (encerrados entre comillas) los caracteres delimitadores de los campos. Estos caracteres deberán encontrarse en el fichero original al principio y al final de los contenidos de cada uno de los campos (por ejemplo, si el carácter fueran comillas los campos deberían aparecer así en el fichero a importar algo como esto: "32.45"). Si se omite esta especificación (o se omite FIELDS) se entenderá que los campos no tienen caracteres delimitadores. Cuando se incluyen como delimitadores de campo las comillas (dobles o sencillas) es necesario utilizar una sintaxis como esta: '\"' ó '\'' de forma que no quepa la ambigüedad de si se trata de un carácter o de las comillas de cierre de una cadena previamente abierta.

� TERMINATED BY Se comporta de forma similar al anterior. Permite qué caracteres son usados en el fichero original como separadores de campos (indicador de final de campo). Si se omite, se interpretará con tal el carácter tabulador (\t). El uso de esta opción no requiere que se especifique previamente ENCLOSED BY pero si necesita que se haya incluidoFIELDS.

� LINES Si el fichero de datos contiene caracteres (distintos de los valores por defecto) para señalar el comienzo de un registro, el final del mismo o ambos, debe incluirse este parámetro y, después de él, las especificaciones de esos valores correspondientes a:

� STARTING BY Permite especificar una carácter como indicador de comienzo de un registro. Si se omite o se especifica como '' se interpretará que no hay ningún carácter que señale en comienzo de línea.

� TERMINATED BY Es el indicador del final de un registro. Si se omite será considerado como un salto de línea (\n).

Inserción de datos en tablas MySQL permite importar ficheros externos utilizando la siguiente sintaxis: LOAD DATA INFILE "nombre del fichero' [REPLACE | IGNORE] INTO TABLE nombre de la tabla FIELDS TERMINATED BY 'indicador de final de campo' ENCLOSED BY 'caracteres delimitadores de campos' LINES STARTING BY 'caracteres indicadores de comienzo de registro' TERMINATED BY 'caracteres indicadores del final de registro' En este ejemplo puedes ver un caso práctico de inserción de datos en las tablas creadas anteriormente. <?php $base="ejemplos"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); /* evitamos los conflictos de los saltos de linea segun el sistema operativo */ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $salto_linea="'\r\n'"; }else{ $salto_linea="'\n'"; } /* hemos creado un fichero de texto (datos_alumnos.txt) que contiene datos de algunos alumnos. Los diferentes campos están entre comillas y separados unos de otros mediante un punto y coma. Cada uno de los registros comienza por un asterios y los registros están separados por un salto de línea (\r\n) en Windows o (\n) en Linux. Por esa razón de compatibilidad de sistemas hemos incluido estos valores en una variable $Salto_linea cuyo contenido difiere sengun sistema operativo que esté ejecutando este script Incluimos estás especificaciones en la sentencia de inserción */ $query="LOAD DATA INFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/datos_alumnos.txt'."'"; $query.= " REPLACE INTO TABLE alumnos"; $query.= " FIELDS ENCLOSED BY '\"' TERMINATED BY ';' "; $query.=" LINES STARTING BY '*' TERMINATED BY $salto_linea "; if(mysql_query($query,$c)){ print "Datos de alumnos cargados<br>";

Page 412: Manual PHP

}else{ print mysql_error ($c)."<br>".mysql_errno ($c); } /* Para esta tabla usaremos el fichero datos_evaluaciones.txt Los diferentes campos están entre comillas y separados unos de otros mediante una coma. Cada uno de los registros comienza por un espacio y los registros están separados por un salto de línea (\r\n) en Windows o (\n) en Linux. Por esa razón de compatibilidad de sistemas hemos incluido estos valores en una variable $Salto_linea cuyo contenido difiere sengun sistema operativo que esté ejecutando este script Incluimos estás especificaciones en la sentencia de inserción */ $query="LOAD DATA INFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/datos_evaluaciones.txt'."'"; $query.= " REPLACE INTO TABLE evaluaciones"; $query.= " FIELDS ENCLOSED BY '\'' TERMINATED BY ',' "; $query.=" LINES STARTING BY ' ' TERMINATED BY $salto_linea "; if(mysql_query($query,$c)){ print "Datos de evaluaciones cargados<br>"; }else{ print mysql_error ($c)."<br>".mysql_errno ($c); } /* En este caso no incluimos especificación alguna. Bajo este suspuesto MySQL interpreta los valores por defecto que son: los campos no van encerrados, las líneas no tienen ningún carácter indicador de comienzo, los campos están separados mediante tabulaciones (carácter de escape \t) y el final de línea está señalado por un caracter de nueva línea (\n) */ $query="LOAD DATA INFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/datos_notas.txt'."'"; $query.= " IGNORE INTO TABLE notas"; if(mysql_query($query,$c)){ print "Datos de notas cargados<br>"; }else{ print mysql_error ($c)."<br>".mysql_errno ($c); } /* Se comporta como los casos anteriores con distincos caracteres para los diferentes eventos, tal como puedes ver en el código */ $query="LOAD DATA INFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/datos_domicilios.txt'."'"; $query.= " IGNORE INTO TABLE domicilios"; $query.= " FIELDS ENCLOSED BY '|' TERMINATED BY '*' "; $query.=" LINES STARTING BY '#' TERMINATED BY '}' "; if(mysql_query($query,$c)){ print "Datos de domicilios cargados "; }else{ print mysql_error ($c)."<br>".mysql_errno ($c); } mysql_close(); ?>

Consultar tablas

¡Cuidado! Al importar ficheros habrá de utilizarse el mismo formato con el que fueron creados tanto FIELDS como LINES.

Exportación de ficheros

Se comporta de forma similar al supuesto anterior. Utiliza la sintaxis siguiente: SELECT * INTO OUTFILE Inicia la consulta de los campos especificados después de SELECT (si se indica * realiza la consulta sobre todos los campos y por el

Page 413: Manual PHP

orden en el que fue creada la tabla) y redirige la salida a un fichero. nombre del fichero Es la ruta, nombre y extensión del fichero en el que serán almacenados los resultados de la consulta. FIELDS ENCLOSED BY TERMINATED BY LINES STARTING BY TERMINATED BY Igual que ocurría en el caso de importación de datos, estos parámetros son opcionales. Si no se especifican se incluirán los valores por defecto. FROM nombre Su inclusión tiene carácter obligatorio. El valor de nombre ha de ser el de la tabla sobre la que se realiza la consulta. Guardar datos en ficheros MySQL permite los contenidos de sus tablas a ficheros de texto. Para ello utiliza la siguiente sintaxis: SELECT * INTO OUTFILE "nombre del fichero' FIELDS TERMINATED BY 'indicador de final de campo' ENCLOSED BY 'caracteres delimitadores de campos' LINES STARTING BY 'caracteres indicadores de comienzo de registro' TERMINATED BY 'caracteres indicadores del final de registro' FROM nombre de la tabla <?php $base="ejemplos"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); $query="SELECT * INTO OUTFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/alumnos.txt'."'"; $query.= " FIELDS ENCLOSED BY '\"' TERMINATED BY ';' "; $query.=" LINES STARTING BY '*' TERMINATED BY '\r\n' "; $query.=" FROM alumnos"; if(mysql_query($query,$c)){ print "fichero alumnos.txt creado<br>"; }else{ print mysql_error ($c)."<br>". mysql_errno ($c); } $query="SELECT * INTO OUTFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/domicilios.txt'."'"; $query.= " FIELDS ENCLOSED BY '|' TERMINATED BY '*;' "; $query.=" LINES STARTING BY '#' TERMINATED BY '}' "; $query.=" FROM domicilios"; if(mysql_query ($query,$c)){ print "fichero domicilios.txt creado<br>"; }else{ print mysql_error ($c)."<br>".mysql_errno ($c); } $query="SELECT * INTO OUTFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/notas.txt'."'"; $query.=" FROM notas"; if(mysql_query ($query,$c)){ print "fichero notas.txt creado<br>"; }else{ print mysql_error ($c)."<br>".mysql_errno ($c); } $query="SELECT * INTO OUTFILE "; $query.="'".$_SERVER['DOCUMENT_ROOT'].'/php/evaluaciones.txt'."'"; $query.= " FIELDS ENCLOSED BY '\'' TERMINATED BY ',' ";

Page 414: Manual PHP

$query.=" LINES STARTING BY ' ' TERMINATED BY '\r\n' "; $query.=" FROM evaluaciones"; if(mysql_query ($query,$c)){ print "fichero evaluaciones.txt creado<br>"; }else{ print mysql_error ($c)."<r>".mysql_errno ($c); } mysql_close(); ?>

¡Cuidado! Al exportar ficheros en entornos Windows, si se pretende que en el fichero de texto aparezca un salto de línea no basta con utilizar la opción por defecto de LINES TERMINATED BY '\n' sino LINES TERMINATED BY '\r\n' (salto de línea y retorno) que son los caracteres que necesita Windows para producir ese efecto. Habrá de seguirse este mismo criterio cuando se trata de importar datos desde un fichero de texto.

Consultas de unión

Consultas usando JOIN

La claúsula JOIN es opción aplicable a consultas en tablas que tiene diversas opciones de uso. Iremos viéndolas de una en una. Todas ellas han de ir incluidas como parámetros de una consulta. Por tanto han de ir precedidas de:

SELECT * o de SELECT nombre_tabla.nombre_campo,..

donde nombre_tabla es un nombre de tabla y nombre_campo es el nombre del campo de esa tabla que pretendemos visualizar para esa consulta. Esta sintaxis es idéntica a la ya comentada en páginas anteriores cuando tratábamos de consultas en varias tablas.

Ahora veremos las diferentes posibilidades de uso de JOIN

FROM tabla1 JOIN tabla2

Suele definirse como el producto cartesiano de los elementos de la primera tabla (tabla1) por lo de la segunda (tabla2).

Dicho de una forma más vulgar, esta consulta devuelve con resultado una lista de cada uno de los registros de la primera tabla asociados sucesivamente con todos los correspondientes a la segunda. Es decir, aparecerá una línea conteniendo el primer registro de la primera tabla seguido del primero de la segunda. A continuación, ese mismo registro de la primera tabla acompañado del segundo de la segunda tabla, y así, sucesivamente hasta acabar los registros de esa segunda tabla. En ese momento, repite el proceso con el segundo registro de la primera tabla y, nuevamente, todos los de la segunda. Así, sucesivamente, hasta llegar al último registro de la primera tabla asociado con el último de la segunda.

En total, devolverá un número de líneas igual al resultado de multiplicar el número de registros de la primera tabla por los de la segunda.

FROM tabla2 JOIN tabla1

Si permutamos la posición de las tablas, tal como indicamos aquí, obtendremos el mismo resultado que en el caso anterior pero, como es lógico pensar, con una ordenación diferente de los resultados.

FROM tabla2 JOIN tabla1 ON condicion

Page 415: Manual PHP

El parámetro ON permite añadir una condición (condicion) a la consulta de unión. Su comportamiento es idéntico al de WHERE en las consultas ya estudiadas y permite el uso de las mismas procedimientos de establecimiento de condiciones que aquel operador.

FROM tabla1 LEFT JOIN tabla2 ON condicion

Cuando se incluye la cláusula LEFT delante de JOIN el resultado de la consulta es el siguiente:

� – Devolvería cada uno los registros de la tabla especificada a la izquierda de LEFT JOIN –sin considerar las restricciones que puedan haberse establecido en las claúsulas ON para los valores de esa tabla– asociándolos con aquellos de la otra tabla que cumplan las condiciones establecidas en la claúsula ON. Si ningún registro de la segunda tabla cumpliera la condición devolvería valores nulos.

FROM tabla1 RIGHT JOIN tabla2 ON condicion

Se comporta de forma similar al anterior. Ahora los posibles valores nulos serán asignados a la tabla indicada a la izquierde de RIGHT JOIN y se visualizarían todos los registros de la tabla indicada a la derecha.

JOIN múltiples

Tal como puedes observar en el ejemplo, es perfectamente factible utilizar conjuntamente varios JOIN, LEFT JOIN y RIGHT JOIN. Las diferentes uniones irán ejecutándose de izquierda a derecha (según el orden en el que estén incluidos en la sentencia) y el resultado del primero será utilizado para la segunda unión y así sucesivamente.

En cualquier caso, es posible alterar ese orden de ejecución estableciendo otras prioridades mediante paréntesis.

UNION de consultas

MySQL permite juntar en una sola salida los resultados de varias consultas. La sintaxis es la siguiente:

(SELECT ...) UNION ALL (SELECT ...) UNION ALL (SELECT ...) Cada uno de los SELECT ha de ir encerrado entre paréntesis. Ejemplo de consultas de unión (JOIN) <?php $base="ejemplos"; $c=mysql_connect ("localhost","pepe","pepa"); mysql_select_db ($base, $c); # vamos a crear un array con las diferente consultas # posteriormente lo leeremos y la ejecutaremos secuencialmente /* Devuelve todos los campos de ambas tablas. Cada registro de alumnos es asociado con todos los de domicilios*/ $query[]="SELECT * FROM alumnos JOIN domicilios"; /* Devuelve todos los campos de ambas tablas. Cada registro de domicilios es asociado con todos los de alumnos */ $query[]="SELECT * FROM domicilios JOIN alumnos"; /* Devuelve todos los campos de los registros de ambas tablas en los que coinciden los numeros del DNI*/ $query[]="SELECT * FROM alumnos JOIN domicilios ON domicilios.DNI=alumnos.DNI"; /* Idéntica a la anterior. Solo se diferencia en que ahora se visualizan antes los campos domicilios*/ $query[]="SELECT * FROM domicilios JOIN alumnos ON domicilios.DNI=alumnos.DNI";

Page 416: Manual PHP

/* devuelve cada uno de los registro de la tabla alumnos. Si existe un domicilio con igual DNI lo insertará. Si no existiera insertará valores nulos en esos campos $query[]="SELECT * FROM alumnos LEFT JOIN domicilios ON domicilios.DNI=alumnos.DNI"; /* Se comporta de forma idéntica al anterior. Ahora insertará todos los registros de domicilios y los alumnos coincidentes o en su defecto campos nulos.*/ $query[]="SELECT * FROM domicilios LEFT JOIN alumnos ON domicilios.DNI=alumnos.DNI"; /* Al utilizar RIGHT será todos los registros de la tabla de la derecha (domicilios) los que aparezcan junto con las coincidencias o junto a campos nulos. Aparecerán primero los campos de alumnos y detrá los de domicilios*/ $query[]="SELECT * FROM alumnos RIGHT JOIN domicilios ON (domicilios.DNI=alumnos.DNI AND alumnos.Nombre LIKE 'A%')"; /* Consulta de nombre, apellido y localidad de todos los alumnos cuyo nombre empieza por A */ $query[]="SELECT alumnos.Nombre, alumnos.Apellido1,alumnos.Apellido2, domicilios.poblacion FROM alumnos JOIN domicilios ON (domicilios.DNI=alumnos.DNI AND alumnos.Nombre LIKE 'A%')"; # una consulta resumen de nos permitirá visualizar una lista con nombre # y apellidos de alumnos su dirección y localidad del domicilio # el nombre de la evaluación y su calificación. # Si no hay datos de población insertará ---- en vez del valor nulo # y si no hay calificación en una evaluación aparecerá N.P. # La consulta aparecerá agrupada por evaluaciones /* iniciamos el select especificando los campos de las diferentes tablas que prentendemos visualizar */ $q="(SELECT alumnos.Nombre,alumnos.Apellido1,"; $q.=" alumnos.Apellido2,domicilios.calle,"; # al incluir IFNULL visualizaremos ---- en los campos cuyo resultado # sea nulo $q.=" IFNULL(domicilios.poblacion,'––––'),"; $q.=" evaluaciones.nombre_evaluacion,"; # con este IFNULL aparecerá N.P. en las evaluaciones no calificadas. $q.=" IFNULL(notas.calificacion,'N.P.')"; # especificamos el primer JOIN con el que tendremos como resultado una lista # de todos los alumnos con sus direcciones correspondientes # por efecto de la clausula ON. # Al poner LEFT se incluirían los alumnos que no tuvieran # su dirección registrada en la tabla de direccione $q.=" FROM (alumnos LEFT JOIN domicilios"; $q.=" ON alumnos.DNI=domicilios.DNI)"; # al unir por la izquierda con notas tendríamos todos los resultados # del JOIN anterior asociados con con todas sus calificaciones # por efecto de la claúsula ON $q.=" LEFT JOIN notas ON notas.DNI=alumnos.DNI"; # al añadir esta nueva unión por la DERECHA con la tabla evaluaciones # se asociaría cada uno de los resultados de las uniones anteriores # con todos los campos de la tabla evaluaciones con lo que resultaría # una lista de todos los alumnos con todas las calificaciones # incluyendo un campo en blanco (sería sustituido por N.P:) # en aquellas que no tuvieran calificación registrada $q.=" RIGHT JOIN evaluaciones"; $q.=" ON evaluaciones.NUMERO=notas.evaluacion "; /* la clausula WHERE nos permite restringir los resultados a los valores correspondientes únicamente a la evaluación número 1*/ $q.=" WHERE evaluaciones.NUMERO=1)"; # cerramos la consulta anterior con el paréntesis. Observa que lo # hemos abierto delante del SELECT e insertamos UNION ALL

Page 417: Manual PHP

# para que el resultado de la consulta anterior aparezca # seguido del correspondiente a la incluida después de UNION ALL $q.=" UNION ALL"; #iniciamos (también con paréntesis) la segunda consulta # que será identica a la anterior salvo el WHERE # será modificado para extraer datos de la evaluación nº2 $q.="(SELECT alumnos.Nombre,alumnos.Apellido1,"; $q.=" alumnos.Apellido2,domicilios.calle,"; $q.=" IFNULL(domicilios.poblacion,'––––'),"; $q.=" evaluaciones.nombre_evaluacion,"; $q.=" IFNULL(notas.calificacion,'N.P.')"; $q.=" FROM (alumnos LEFT JOIN domicilios"; $q.=" ON alumnos.DNI=domicilios.DNI)"; $q.=" LEFT JOIN notas ON notas.DNI=alumnos.DNI"; $q.=" RIGHT JOIN evaluaciones"; $q.=" ON evaluaciones.NUMERO=notas.evaluacion"; $q.=" WHERE evaluaciones.NUMERO=2)"; # hemos cerrado el parentesis de la consulta anterior # e incluimos un nuevo UNION ALL para consultar los datos # correspondientes a la tercera evaluación $q.=" UNION ALL"; $q.="(SELECT alumnos.Nombre,alumnos.Apellido1,"; $q.=" alumnos.Apellido2,domicilios.calle,"; $q.=" IFNULL(domicilios.poblacion,'––––'),"; $q.=" evaluaciones.nombre_evaluacion,"; $q.=" IFNULL(notas.calificacion,'N.P.')"; $q.=" FROM (alumnos LEFT JOIN domicilios"; $q.=" ON alumnos.DNI=domicilios.DNI)"; $q.=" LEFT JOIN notas ON notas.DNI=alumnos.DNI"; $q.=" RIGHT JOIN evaluaciones"; $q.=" ON evaluaciones.NUMERO=notas.evaluacion"; $q.=" WHERE evaluaciones.NUMERO=3)"; # incluimos la variable $q en el array de consultas $query[]=$q; # leemos el array y visualizamos el resultado # cada consulta a traves de la llamada a la funcion visualiza # a la que pasamos el resultado de la consulta # y la cadena que contiene las sentencias de dicha consulta foreach($query as $v){ visualiza(mysql_query($v,$c),$v); } function visualiza($resultado,$query){ PRINT "<BR><BR><i>Resultado de la sentencia:</i><br>"; print "<b><font color=#ff0000>"; print $query."</font></b><br><br>"; PRINT "<table align=center border=2>"; while ($registro = mysql_fetch_row($resultado)){ echo "<tr>"; foreach($registro as $valor){ echo "<td>",$valor,"</td>"; } } echo "</table><br>"; } ?>

Ejecutar script

Page 418: Manual PHP

Seguridad en MySQL

Inyección de código

Lo conocido como inyección de código o inyección SQL no es otra cosa que una técnica mediante la cual un intruso utiliza las vulnerabilidades de las sentencias MySQL para propósitos distintos a los previstos en la aplicación.

Veamos los siguientes ejemplos. Se trata de una consulta en nuestra tabla demo4 que debe extraer una lista de usuarios cuyo nombre coincida con el que se inserte en en un campo del formulario que se incluye en el documento.

Ver ejemplo Ver ejemplo Ver código fuente

La ejecución de primero de los ejemplos manteniendo el valor por defecto nos dará la respuesta esperada que es la lista de todas las personas de la tabla cuyo nombre coincide con Telesfora. Si hacemos lo mismo con el segundo ejemplo la respuesta será totalmente inesperada. Hemos pedido una lista de personas cuyo nombre coincida con ' or '34=34 y, contra todo pronóstico (no hay nadie que se llame así en nuestra tabla) nos aparece una lista de todos los registros de la tabla.

La explicación es la siguiente. La claúsula WHERE de la consulta contiene lo siguiente: WHERE (Nombre='$nombre'). Al recibir el valor' or '34=34 sustituirá $nombre por ' or '34=34 y se convertirá en: WHERE (Nombre='' or '34=34'). Es evidente que 34 es siempre igual a 34 como lo sería 13=13 o 1=1. Por lo tanto la condición se cumple sea cual sea el nombre y por esa razón nos aparece la lista de todos los registros. Puede que aquí no tenga demasiada importancia pero imagina que ese formulario se usa para comprobar un nombre de usuario y una contraseña en una tabla de usuarios. Estaríamos accediendo sin dificultad alguna a un lugar al que no nos estaría permitido.

Para evitar la posibilidad de ese efecto indeseado (inyección de código) PHP dispone de la función:

mysql_real_escape_string(cadena)

que hace una llamada a la librería MySQL del mismo nombre y para que «escape» los caracteres especiales contenidos en lacadena de forma que sea mucho más seguro su uso a través de mysql_query(). Los caracteres que son «escapados» son los siguientes:\x00, \n, \r, \, ', " y \x1a. Se convertirían en: \\x00, \\n, \\r, \\\, \', \" y \\x1a con lo cual la cadena ' or '34=34 se convertiría en \' or \'34=34 y la claúsula anteriormente comentada se convertiría en WHERE (Nombre='\' or \'34=34') con lo cual ya no se produciría el efecto indeseado y se evitaría el riesgo de uso inadecuado.

¡Cuidado! Dado que la función mysql_real_escape_string() se utiliza para hacer una llamada a la librería MySQL del mismo nombre, para su uso es necesario que el servidor MySQL esté activo. De lo contrario se produciría un error. Si se van a insertar datos binarios es necesario utilizar mysql_real_escape_string() o addslashes() con preferencia de la primera.

Aunque los desarrolladores de PHP anuncian su próxima desaparición, en la actualidad hay en el fichero php.ini una directiva llamadamagic_quotes_gpc cuyo valor por defecto es Off. Si cambiáramos esa directiva a On (o si usáramos un servicio externo que la tuviera configurada de esta forma) todos los datos recibidos por medio de los métodos GET o POST serían tratados por la función:

addslashes(cadena)

que devuelve la cadena después de haber «escapado» los carácteres ',", \ y NUL (el byte NULL). La acción de addslashes puede deshacerse utilizando la función

stripslashes(cadena_escapada)

que quita los carácteres de «escape» añadidos por addslashes devolviendo la cadena original.

Para comprobar como está configurada la directiva magic_quotes_gpc disponemos de la función:

Page 419: Manual PHP

get_magic_quotes_gpc()

que devuelve un valor booleano. True si está configurada como On y False en caso de estarlo como Off.

Filtrado de código

Mediante un proceso como el que puedes ver en el código fuente que se incluye a continuación puede realizarse un filtrado de los eventuales intentos de inyección de código. Su funcionamiento es el siguiente:

� – Leemos toda la información que se recibe en las variables superglobales $_GET y $_POST. Recorremos los arrays que recogen esos resultados sustituyendo sus valores por los obtenidos de la aplicación de una función que realiza el filtrado.

� – La función que realiza el filtrado comprueba el estado de magic_quotes. Caso de estar configuradas con On revierte los caracteres «escapados» para impedir que se dupliquen los carácteres de escape.

� – Comprueba si es numérico el valor recibido y en caso de serlo lo devuelve sin ninguna modificación.

� – Si el valor recibido no es numérico aplica la función mysql_real_escape_string y devuelve el resultado dentro de unas comillas simples. De esta forma podríamos despreocuparnos de poner comillas al valor de la variable en la claúsula WHERE. Si se trata de un número no las riquere y si se trata de una cadena el valor recibido ya las incluye.

<?php if(is_array($_GET)){ foreach ($_GET as $_GET_indice=>$_GET_valor){ $_GET[$_GET_indice]=filtro_seguridad($_GET_valor); } } if(is_array($_POST) ){ foreach ($_POST as $_POST_indice=>$_POST_valor){ $_POST[$_POST_indice]= filtro_seguridad($_POST_valor); } } function filtro_seguridad($valor){ /* comprueba si están activas las magic_quotes y, en caso de estarlo revierte su acción mediante stripslashes */ if (get_magic_quotes_gpc()) $valor = stripslashes($valor); /* por compatibilidad entre versiones Windows - Ubuntu abrimos una conexión con MySQL antes de aplicar mysql_real_escape_string */ $c=mysql_connect("localhost","pepe","pepa"); /* con la certeza de que estamos ante la cadena original aplicamos la función mysql_real_escape_string a todas las cadenas no numéricas */ if (!is_numeric($valor)) $valor = "'" .mysql_real_escape_string($valor) . "'"; /* cerramos la conexion y devolvemos el resultado*/ mysql_close($c); return $valor; } ?>

Hemos guardado este código en un fichero llamado no_inyeccion.inc.php" y hemos hecho dos modificaciones en el ejemplo anterior. Por una parte hemos incluido este fichero mediante la opción include y por otra hemos quitado las comillas a WHERE (Nombre='$nombre')dejándolo como WHERE (Nombre=$nombre). Este es el ejemplo resultante:

Ejecutar consulta Ver código fuente

Borrado de usuarios MySQL

Hasta el momento no hemos modificado ni los usuarios de MySQL ni la configuración de PHPMyAdmin. Aún tenemos los usuarios por defecto (root sin contraseña) y PHPMyAdmin sigue dándonos el mensaje de advertencia relativo al fallo de seguridad de la configuración.

Page 420: Manual PHP

El primer paso para corregir esa vulnerabilidad será borrar los usuarios dejando sólo el usuario pepe que es el que hemos creado al instalar MySQL. Para ello, con el servidor MySQL activo, abriremos http://localhost/phpMyAdmin/ editaremos la tabla user correspondiente a la base de datos mysql y borraremos los usuarios, dejando únicamente al usuario pepe.

Una vez realizados estos cambios, deberíamos cerrar el servidor MySQL y reiniciarlo con la nueva configuración. A partir de aquí, cuando tratamos de acceder de nuevo a PHPMyAdmin nos aparecerá un mensaje de error tal como el que vemos en la imagen.

PHPMyAdmin nos dará este mensaje de error porque su configuración por defecto utiliza el usuario root que ya no existe. ¡Acabamos de borrarlo!

Modificación de la configuración de PHPMyAdmin

Hemos de modificar el fichero config.inc.php que está en el sudirectorio libraires de MyAdmin. Durante el proceso de instalación habíamos hecho algunos cambios en este fichero (algunos de ellos para evitarnos tener que teclear continuamente nombres de usuario y contraseña) que ahora vamos a deshacer.

Fichero inicial config.inc.php

Guardar como config.inc.php Modificaciones en el fichero config.inc.php

Línea Cambios

168

Donde dice:

$cfg['Servers'][$i]['auth_type'] = 'config'; cambiar por:

$cfg['Servers'][$i]['auth_type'] = 'http';

218

Donde dice:

$cfg['Servers'][$i]['nopassword'] = true; cambiar por:

Page 421: Manual PHP

$cfg['Servers'][$i]['nopassword'] = false;

345

Donde dice:

$cfg['Servers'][$i]['AllowNoPasswordRoot'] = true; cambiar por:

$cfg['Servers'][$i]['AllowNoPasswordRoot'] =false;

Al cambiar config por http estaremos establecimiento este último valor como método de autentificación de los usuarios. Los otros dos cambios (que ahora hacemos igual a false) significan que no vamos a permitir el acceso sin contraseña a ningún usuario, incluido un eventual usuario root sin contraseña que pudiera quedar registrado desde el proceso inicial de instalación.

Acceso a PHPmyAdmin

A partir del momento que hayamos hecho los cambios anteriores, cada vez que tratemos de acceder a http://localhost/phpmyadmin/ va a aparecer una ventana como la que vemos en la imagen. Será necesario introducir un nombre de usuario válido (pepe en nuestro caso). Nos desaparecerá el mensaje de advertencia y ya podremos gestionar nuestras bases de datos de forma un poco más segura.

Empezando con XML

XML

XML son las siglas de eXtensible Markup Language (lenguaje de marcas extensible) que es un metalenguaje que nos permite definirlenguajes de marcado acordes a nuestras necesidades. Se trata básicamente de poder definir nuestras propias etiquetas (marcas) para establecer una estructura destinada al almacenamiento de datos. La libertad de establecimiento de nuestra estructura de marcas está únicamente limitada al cumplimiento de las normas del metalenguaje XML.

Los primeros pasos en XML

Los documentos XML deben tener como extensión xml y es muy aconsejable que estén codificados en en formato UNICODE. Nosotros, tal como detallamos en los ejemplos, vamos a guardar todos los documentos XML con codificación UTF-8. De esta forma estaremos evitando los problemas que nos han surgido en el primero de los ejemplos que incluimos un poco más adelante.

Todo documento XML debe comenzar con un prólogo de tipo:

<?xml version="1.0" ?>

Como puedes ver se utiliza una sintaxis muy próxima a la de PHP (<?php y ?>) esta vez con un xml obligatorio y situado inmediatamente detrás de <?. Cuando usamos la configuración short_open_tag=off (descrita aquí) estaremos diferenciando claramente las etiquetas php (<?php) de las XML que comienzan por <?xml.

Page 422: Manual PHP

Respecto al atributo version se le asigna el valor 1.0 ya que esta es la única versión existente hasta el momento. Esta primera línea de los documentos XML, conocida como prólogo, puede incluir otros atributos que comentaremos más adelante. Si creáramos un documento con esa única línea, la visualización sería la que puedes ver pulsando aquí.

Como puedes observar el mensaje de error se produce porque un documento XML ha de tener obligatoriamente un cuerpo formado por un único elemento (elemento raíz definido por una etiqueta de apertura y otra de cierre) que será que vaya a contener a todos los demás elementos que puedan incluirse en el documento.

Dicho de otra forma, los documentos XML han de incluir, a continuación de la línea prólogo, una etiqueta del tipo <nombre_etiqueta> y han de acabar con </nombre_etiqueta> pudiendo ser nombre_etiqueta cualquier palabra que cumpla la especificación XML 1.0 que establece que «un nombre empieza con una letra o uno o más signos de puntuación, y continúa con letras, dígitos, guiones, rayas, dos puntos o puntos, denominados de forma global como caracteres de nombre. Los nombres que empiezan con la cadena "xml" se reservan para la estandarización de esta o de futuras versiones de esta especificación».

Debemos tener muy presente que los nombres de las etiquetas o marcas son sensibles a mayúsculas y minúsculas. La estructura de un documento XML recuerda mucho los bucles anidados (siempre uno dentro del otro) y/o la interpretación matemática de las estructuras delimitadas por paréntesis, corchetes y/o llaves.

Cada elemento XML –delimitado por sus etiquetas de apertura y cierre– puede tener como contenido más elementos, cadenas de caracteres, ambas cosas, o carecer de contenido (elemento vacío).

Líneas de comentario

las líneas de comentario tienen una sintaxis idéntica a la utilizada para el mismo fin en la edición HTML. Todo comentario comienza con <!-- y acaba con -->

Etiquetas con atributos

De forma similar al HTML en las marcas (de apertura) XML pueden incluirse los atributos que se desee siempre que se utilice la siguiente sintaxis:

<marca atributo="valor" > ó <marca atributo='valor' >

siendo en este caso obligatorio que los valores vayan entre comillas (simples o dobles). Dentro de una misma etiqueta podemos incluir tantos atributos como deseemos y, de igual forma que en aquellas, también a los atributos podemos asignarles el nombre que deseemos.

Caracteres especiales

Puede ocurrir que los textos que pretendamos introducir dentro de un elemento requieran el uso de caracteres especiales que puedieran provocar conflictos con los utilizados para delimitar etiquetas y/o valores de los atributos. En XML existen entidades que pueden utilizarse para que esos caracteres sean interpretados como texto y no como elementos de marcado. Son estos:

Entidad Caracter

&amp; &

&lt; <

&gt; >

&apos; '

&quot; "

Otra forma de incluir información que contenga caracteres especiales puede ser esta:

<![CDATA[ caracteres raros ]]>

donde pueden incluirse cualesquiera caracteres con la excepción de la cadena ]]> que se confundiría con el cierre del bloque CDATA.

Page 423: Manual PHP

Documentos XML bien formados

Los documentos denominados bien formados (well formed en inglés) son aquellos que cumplen con todas las definiciones básicas de formato y pueden, por lo tanto, analizarse correctamente por cualquier parser (analizador sintáctico) que cumpla con la norma. Por el momento nos bastará con comprobar que los documentos estén bien formados y saber que bien formado y válido son cosas distintas y que un documento bien formado puede no ser válido o viceversa.

El sitio World Wide Web Consortium (W3C) dispone de un servicio que permite comprobar la buena formación de un documento XML.

En las imágenes podemos ver los resultados de la comprobación de un fichero. En la primera de la ventanas elegimos la opciónValidate by Direct Imput, pegamos el contenido de nuestro documento el área del texto del formulario, pulsamos el botón check y obtenemos el resultado de la validación y los eventuales mensajes de error o advertencia.

El documento XML más simple

Este es el ejemplo más simple de un documento bien formado en XML. Ambos ejemplos tienen el contenido que puedes ver en el código fuente. Si los compruebas pulsando los enlaces podrás observar que se comportan de forma diferente. El primero nos da un error mientras que el segundo parece correcto.

<?xml version="1.0" ?> <!-- Aquí los comentarios se ponen así -->

Page 424: Manual PHP

<cuerpo> Lo minimo minimorum </cuerpo>

ejemplo471.xml ejemplo472.xml

El origen del problema, en este caso, son los caracteres con tilde. El primer documento ha sido elaborado utilizando el Notepad++ y guardando con el formato por defecto (ANSI) de este editor. El segundo documento es el resultado de abrir el documento anterior con el bloc de notas de Windows y guardarlo sin modificar su contenido utilizando la opción de codificación UTF-8 tal como puedes ver en la imagen.

Habríamos obtenido igual resultado si al crear un archivo nuevo en Notepad++ hubiéramos elegido la opción UTF-8 tal como puedes ver en esta imagen.

Page 425: Manual PHP

Otros ejemplos

<?xml version="1.0" ?> <!-- Todo el contenido ha de ir dentro de esta etiqueta --> <ContieneTodo> <!-- La parte1 contiene información en forma de texto --> <parte1> Estos son los datos de la parte1 </parte1> <!-- Dentro de la parte2 incluiremos dos nuevas etiquetas con sus datos --> <parte2> Aqui pueden ir datos y también nuevas etiquetas <parte2_1> Incluimos más datos, ahora en la parte 2_1 </parte2_1> Fuera de la parte 2_1 podemos añadir datos <parte2_2> Una nueva etiqueta y nuevos datos </parte2_2> </parte2> <!-- Aqui incluimos apertura y cierre en la misma etiqueta es una etiqueta vacía --> <parte3 /> </ContieneTodo>

ejemplo473.xml

¡Cuidado! Recuerda que los nombres de las etiquetas son sensibles a mayúsculas/minúsculas por lo tanto < /contieneTodo>no cerraría < ContieneTodo> Tampoco debes olvidarte de guardar los documentos xml con codificación UTF-8.

Un ejemplo algo más completo de XML

<?xml version="1.0" ?> <!-- Después de prólogo incluimos la etiqueta de apertura del elemento raiz (cuerpo_del_XML) que será el que contenga todo y que se cerrará al final del documento --> <cuerpo_del_XML> <!-- Establecemos un elemento (alumnos) que va a contener los datos de todos los alumnos (lo marcamos en magenta) --> <alumnos> <!-- dentro de la etiqueta alumno (podremos poner cuantas queramos) incluimos inforamcion relativa a el. En este caso ponemos su número de orden como caracter y, además, dentro de etiquetas contenedoras los datos nombre, apellidos, sexo y comentarios relativos a cada uno de ellos --> <alumno> 1 <nombre>Juan</nombre> <apellidos>Fernández Gómez</apellidos> <sexo>Varón</sexo> <comentarios/> </alumno> <!-- En este caso incluiremos una entidad para representar el símbolo menor que en la etiqueta de comentarios --> <alumno> 2 <nombre>Luisa</nombre> <apellidos>García Bermúdez</apellidos>

Page 426: Manual PHP

<sexo>Hembra</sexo> <comentarios>Es la &lt; de 17 hermanos</comentarios> </alumno> </alumnos> <!-- Establecemos un elemento (profes) que va a contener los datos de los profesores (lo marcamos en magenta) --> <profes> <!-- El número de orden y el sexo de los profesores los insertamos ahora como atributos del elemento profe. Es una opción alternativa a la usada en el caso de los alumnos --> <profe sexo="Varon" numero="1"> <nombre>Alejandro</nombre> <apellidos>Iglesias Fernández</apellidos> <comentarios></comentarios> </profe> <!-- Los comentarios relativos a esta profesora los incluimos usando CDATA para evitar posibles conflictos con los caracteres especiales --> <profe sexo="Hembra" numero="2"> <nombre>Liliana</nombre> <apellidos>Fernández de Arriba</apellidos> <comentarios> <![CDATA[ Es la < de los ^profes^ y "profas" ]]> </comentarios> </profe> </profes> <!-- incluimos un nuevo elemento padres que, por el momento, dejamos vacío --> <padres> </padres> </cuerpo_del_XML>

ejemplo474.xml

Exportación de tablas MySQL a ficheros XML

No es infrecuente que se plantee la necesidad de intercambiar información entre dos formatos de almacenamiento. En los ejemplos siguientes hemos descrito el proceso de lectura de tablas MySQL y su transformación en ficheros con formato XML.

En este primer ejemplo hemos hecho la transformación considerando que cada campo de la tabla fuera convertido en un elemento del fichero XML

<?php /* establecemos una conexión con la base de datos ejemplos de MySQL */ $base="ejemplos"; $tabla="demo4"; $conexion=mysql_connect("localhost","pepe","pepa"); mysql_select_db($base,$conexion); /*leemos todos los campos de la tabla y todos los registros */ $resultado= mysql_query("SELECT * FROM $tabla" ,$conexion); /* inicializamos una variable con la primera etiqueta obligatoria de los ficheros xml. Ponemos \n para incluir un salto de línea */ $xml= "<?xml version=\"1.0\" ?>\n"; /* ponemos la etiqueta de apertura del elemento raiz. En este caso le asignamos el nombre de la tabla aunque podríamos ponerle cualquier otro nombre y lo agregamos alla variable que recogerá toda la información */ $xml.= "<".$tabla.">\n"; /* leemos todas la filas del resultado filtrando aquellos elementos de array asociativo serán precisamente esos indices los que utilizaremos para asignar nombre a los elementos hijos del array */ while ($registro = mysql_fetch_array($resultado,MYSQL_ASSOC)){

Page 427: Manual PHP

/* cada registro será un elemento xml. Le pondremos como nombre usuario aunque podríamos ponerles cualquier otro nombre y lo agregamos a la variable que recogerá todo*/ $xml.= "<usuario>\n"; /* leemos el buche de los resultado y vamos añadiendo etiquetas de apertura con el mismo nombre que el índice asociativo, con el valor contenido en el array (codificado utf8 para evitar problemas de lectura del xml y después ponemos la etiqueta de cierre con el mismo nombre que la de apertura) */ foreach($registro as $indice=>$valor){ $xml.= "<".$indice.">".utf8_encode($valor)."</".$indice.">\n"; } /* leido el registro completo añadimos la etiqueta de cierre del usuario */ $xml.= "</usuario>\n"; } /* añadimos la etiqueta de cierre del elemento raiz */ $xml.= "</".$tabla.">\n"; /* cerramos la conexión con la base de datos */ mysql_close(); /* escribimos el contenido de la variable que recoge toda la información en un fichero xml y colocamos un enlace para poder visualizarlo en el navegador */ if (file_put_contents("demo4.xml",$xml)){ print '<a href="demo4.xml">Ver el fichero demo4.xml que acaba de crearse</a>'; }else{ print "El fichero demo4.xml no ha podido crearse. Ha habido un error"; } ?>

Crear fichero XML

Este otro ejemplo es una modificación del anterior. En este caso hemos optado por convertir uno de los campos de la tabla en atributo de uno de los elementos del fichero XML.

<?php /* establecemos una conexión con la base de datos ejemplos de MySQL */ $base="ejemplos"; $tabla="demo4"; $conexion=mysql_connect("localhost","pepe","pepa"); mysql_select_db($base,$conexion); /*leemos todos los campos de la tabla y todos los registros */ $resultado= mysql_query("SELECT * FROM $tabla" ,$conexion); /* inicializamos una variable con la primera etiqueta obligatoria de los ficheros xml. Ponemos \n para incluir un salto de línea */ $xml= "<?xml version=\"1.0\" ?>\n"; /* ponemos la etiqueta de apertura del elemento raiz. En este caso le asignamos el nombre de la tabla aunque podríamos ponerle cualquier otro nombre y lo agregamos alla variable que recogerá toda la información */ $xml.= "<".$tabla.">\n"; /* leemos todas la filas del resultado filtrando aquellos elementos de array asociativo serán precisamente esos indices los que utilizaremos para asignar nombre a los elementos hijos del array */ while ($registro = mysql_fetch_array($resultado,MYSQL_ASSOC)){ /* cada registro será un elemento xml. Le pondremos como nombre usuario e incluiremos ahora el Sexo como atributo en la etiqueta usuario asignando como valor el contenido de el elemento del mismo nombre del array resultante de la lectura de la tabla*/ $xml.= "<usuario Sexo='".$registro['Sexo']. "' >\n";

Page 428: Manual PHP

/* leemos el buche de los resultado y vamos añadiendo etiquetas de apertura con el mismo nombre que el índice asociativo, con el valor contenido en el array (codificado utf8 para evitar problemas de lectura del xml y después ponemos la etiqueta de cierre con el mismo nombre que la de apertura Excluimos el de indice Sexo que ya ha sido incluido como atributo del usuario */ foreach($registro as $indice=>$valor){ if ($indice !='Sexo'){ $xml.= "<".$indice.">".utf8_encode($valor)."</".$indice.">\n"; } } /* leido el registro completo añadimos la etiqueta de cierre del usuario */ $xml.= "</usuario>\n"; } /* añadimos la etiqueta de cierre del elemento raiz */ $xml.= "</".$tabla.">\n"; /* cerramos la conexión con la base de datos */ mysql_close(); /* escribimos el contenido de la variable que recoge toda la información en un fichero xml y colocamos un enlace para poder visualizarlo en el navegador */ if (file_put_contents("demo4_1.xml",$xml)){ print '<a href="demo4_1.xml">Ver el fichero demo4_1.xml que acaba de crearse</a>'; }else{ print "El fichero demo4_1.xml no ha podido crearse. Ha habido un error"; } ?>

Crear fichero XML

Manipulando ficheros XML desde PHP

La extensión DOM

La Extension DOM de PHP permite manipular documentos XML mediante las funciones y procedimientos DOM incluidos en una clase de PHP 5 llamada DOMDocument. Esta extensión viene incluida por defecto en PHP y no requiere ninguna modificación del fichero php.ini para ser utilizada.

Page 429: Manual PHP

El DOM (Document Object Model) presenta un documento XML como una estructura de árbol.

Tipos de nodos

Cada uno de los elementos (nodos) que puede contener un documento corresponde a uno de los tipos siguientes:

1 ELEMENT NODE

2 ATTRIBUTE NODE

3 TEXT NODE

4 CDATA SECTION NODE

5 ENTITY REFERENCE NODE

6 ENTITY NODE

7 PROCESSING INSTRUCTION NODE

8 COMMENT NODE

9 DOCUMENT NODE

10 DOCUMENT TYPE NODE

11 DOCUMENT FRAGMENT NODE

12 NOTATION NODE

siendo los verdaderamente útiles para nuestros fines los señalados en rojo en la tabla anterior. Se refieren a los componentes de los documentos XML descritos en la página anterior.

Organización de los nodos

En esta ventana estás visualizando el documento ejemplo474.xml que hemos creado en la página

Los componentes de un documento XML están organizados por niveles de dependencia y dentro de cada uno de esos niveles poritems numerados (siempre a partir de cero) según el orden de creación de cada uno de ellos.

Como podrás observar el nivel superior del documento permite un único elemanto con la única excepción de admitir también la inclusión de líneas de comentario.

Por esa razón el comentario que dice: «Después de prólogo...» se identifica como item cero item(0) mientras que el elemento raiz del documento se idenficaría como item(1).

Por otra parte, los elementos de cada nivel se pueden identificar como hijos (childNodes) de un nodo padre (parentNodes). De esta forma, del elemento que contiene el nombre Juan podríamos decir que: es el hijo número uno item(1) del nodo llamado alumno que, a su vez, es el hijo número tres item(3)) de un padre llamado alumnos que es también el tercer hijo item(3))del elemento raíz que es el item señalado con el número uno item(1)) entre los hijos del objeto XML.

Indicada a modo de árbol geneológico, la secuencia: objeto XML -> childNodes ->item(1) -> childNodes ->item(3) -> childNodes ->item(3) -> childNodes ->item(1) nos conduciría hasta el elemento que contiene el valor Juan.

Otra forma de identificar nodos utilizando sus nombres.

objeto XML -> documentElement->getElementsByTagName('alumno')->item(0)->getElementsByTagName('nombre')->item(0)

nos haría el siguiente recorrido. objeto XML->documentElement nos llevaría al nodo raíz del documento. Por medio degetElementsByTagName('alumno') obtendríamos la lista de todos los alumnos contenidos en el documento. A través de item(0) nos situaríamos el el nodo del primero de los alumnos. Por medio de getElementsByTagName('nombre') buscaríamos la lista de hijos del anterior cuyo nombre es nombre. De ellos tomaríamos el primero (en este caso el único) mediante item(0).

Page 430: Manual PHP

La clase DOMDocument

El constructor de la clase DOMDocument creará un objeto utilizando la sintaxis siguiente:

$objeto= new DOMDocument('version','codificacion')

donde $objeto es el identificador del nuevo objeto y donde version y codificacion son dos cadenas opcionales que incluirían respectivamente el número de la versión del nuevo documento XML (actualmente solo es posible la versión 1.0) y el formato de codificación del documento con valores tales como 'utf-8' o ''.

Propiedades de DOMDocument

Los objetos creados por la clase DOMDocument poseen, entre otras, las siguientes propiedades:

encoding

Recoge en una cadena el tipo de codificación del documento tal como se especifica en el prólogo del mismo. Si no hubiera sido establecida el valor de esta propiedad sería una cadena vacía.

xmlVersion

Recoge en una cadena la versión del documento tal como se especifica en el prólogo del mismo. Como ocurría en el caso anterior, si no hubiera sido establecida su valor sería una cadena vacía.

formatOutput

Da formato a la salida aplicando sangrías y espacios extra. Permite valores booleanos (true ó false). Por defecto toma el valor false.

preserveWhiteSpace

Es un valor booleano (TRUE por defecto) que indica si deben mantenerse los espacios redundandes en los contenidos del documento.

nodeType

Esta propiedad no es modificable. Devuelve un número comprendido entre 1 y 12 (son los de la tabla de tipos de nodos incluida más arriba) que se corresponde con el tipo de nodo al que pertenece el objeto actual.

nodeName

Esta propiedad, que tampoco es modificable, contiene el nombre del nodo actual.

nodeValue

En esta propiedad se recoge el valor del contenido del nodo actual. Es modificable de la siguiente forma:

$objeto->nodeValue=nuevo_valor

Métodos de DOMDocument

Algunas de las funciones (métodos) de esta clase son las siguientes:

$objeto->load('fichero');

este método permite incluir en un objeto DOMDocument el contenido de un fichero cuyo nombre y path se especifican en el parámetrofichero.

Page 431: Manual PHP

$raiz=$objeto->documentElement

este método permite recoger en un objeto llamado $raiz el contenido del nodo raíz del objeto XML.

$hijos=$objeto->childNodes

este método permite recoger en un objeto llamado $hijos el conjunto de los nodos hijos del $objeto

$un_hijo=$objeto->childNodes->item(indice)

este método permite recoger en un objeto llamado $un_hijo el nodo hijo del $objeto cuya posicion es señalada por el número indice.

$otro_objeto=$objeto->getElementsByTagName('nombre')

este método crea un objeto $otro_objeto que contiene todos los nodos del objeto $objeto cuyo nombre coincide con nombre.

$otro_elemento=$objeto->getElementsByTagName('nombre')->item(indice)

es una ampliación de caso anterior. Ahora $otro_elemento contiene únicamente el nodo que ocupa la posición señalada por indice en el conjunto de nodos cuyo nombre coincide con nombre.

$otro_elemento=$objeto->getElementsByTagName('nombre')->item(indice)->getElementsByTagName('otro_nombre')->item(otro_indice)

Una nueva ampliación de casos anteriores. Ahora $otro_elemento contiene únicamente el nodo que ocupa la posición señalada porotro_indice en el conjunto de nodos cuyo nombre coincide con otro_nombre y que a la vez son hijos del elemento de índice indice del conjunto formado por los nodos de nombre nombre contenidos en el $objeto. La cadena podría alargarse con nuevas inclusiones.

En este ejemplo puedes ver la forma de acceder a los diferentes nodos de un documento XML.

<?php /* creamos un objeto DOM con nombre $mi_XML*/ $mi_XML = new DOMDocument(); /* incluimos el fichero ejemplo474.xml en ese objeto */ $mi_XML->load('ejemplo474.xml'); /* leamos algunas de las propiedades de este objeto */ print "La propiedad encoding vale: ".$mi_XML->encoding."<br>"; print "La propiedad xmlVersion vale: ".$mi_XML->xmlVersion."<br>"; print "La propiedad formatOutput vale: ".$mi_XML->formatOutput."<br>"; print "preserveWhiteSpace vale: ".$mi_XML->preserveWhiteSpace ."<br>"; print "Como llegar al valor Juan <br><b>"; print $mi_XML->childNodes->item(1)->childNodes->item(3)->childNodes->item(3)-> childNodes->item(1)->nodeValue; /* para mayor comodidad podríamos haber creado un objeto de esta forma: */ $juanito=$mi_XML->childNodes->item(1)->childNodes->item(3)->childNodes->item(3)->childNodes->item(1); /* al imprimir el valor de la propiedad nodeValue del nuevo objeto dará el mismo resultado que en el caso anterior */ print "</b><br />también así se llega: ".$juanito->nodeValue; print "</b><br />Otra forma de llegar: "; print $mi_XML->documentElement->getElementsByTagName('alumno')->item(0)-> getElementsByTagName('nombre')->item(0)->nodeValue; /*$mi_XML->documentElement nos llevaría al nodo raíz. getElementByTagName('alumno') nos daría los nodos (contenidos en el raiz) que tienen por nombre alumno. El item(0) que le sigue indicaría que vamos al primero de los nodos alumno y allí mediante getElementsByTagName('nombre')

Page 432: Manual PHP

buscaríamos el array que contiene los elementos nombre de ese alumno concreto. Como solo hay un nombre estará identificado por el índice 0 (item(0)) y de este ya extraeríamos el valor */ print "</b><br>Vamos a conocer el nombre del nodo padre de Juan:<br><b>"; print $juanito->parentNode->nodeName; print "</b><br>Vamos a conocer el nombre del nodo abuelo de Juan:<br><b>"; print $juanito->parentNode->parentNode->nodeName; print "</b><br />El bisabuelo de Juan se llama:<br><b>"; print $mi_XML->childNodes->item(1)->childNodes->item(3)->childNodes->item(3)->childNodes->item(1)-> parentNode->parentNode->parentNode->nodeName; print "</b><br />¿Tendrá Juan tatarabuelo ?<br><b>"; print $juanito->parentNode->parentNode->parentNode->parentNode->nodeName."</b>"; /* creemos un nuevo objeto con nombre $hijuela */ $hijuela=$mi_XML->childNodes->item(1)->childNodes->item(3)->childNodes->item(7)->childNodes->item(7); /* visualizemos las propiedades del nuevo objeto */ print "<br><i>El nodo se llama </i> ".$hijuela->nodeName; print " <i>es del tipo:</i> ".$hijuela->nodeType; print " y <i>contiene esto</i>: ".$hijuela->nodeValue; ?>

Ejecutar el script

Crear documentos XML mediante la clase DOMDocument

La creación de documentos XML por medio de la clase DOMDocument requiere la utilización de algunos métodos aún no descritos. Son los siguientes:

$elemento = $objeto->createElement('nombre','contenido');

Recoge en $elemento un nuevo elemento del $objeto asignándole el nombre e incluyendo como contenido especificado en la cadenacontenido. Si no se especifica esta última tomará por defecto el valor de una cadena vacía.

$comentario = $objeto->CreateComment('texto');

Recoge en $comentario un nuevo comentario del $objeto incluyendo como contenido especificado en la cadena texto.

$cdata = $objeto->createCDATASection('texto');

Recoge en $cdata un nuevo elemento (del tipo CDATA) del $objeto incluyendo como contenido especificado en la cadena texto.

¡Cuidado! Los métodos crean el elemento, el comentario o el CDATA pero no lo incluye en el documento XML. Para esto es necesario recurrir a los métodos que se incluyen a continuación. $objeto->(.. nodos)->appendChild($elemento); $objeto->(.. nodos)->appendChild($comentario); $objeto->(.. nodos)->appendChild($cdata);

Incluyen en el nivel indicado por $objeto->(.. nodos) un nuevo elemento, comentario o CDATA que han de ser creados mediante los métodos Create... descritos anteriormente.

$objeto->insertBefore($nuevo_elemento, $otro_elemento);

Incluye un elemento $nuevo_elemento precediendo a $otro_elemento incluido como segundo parámetro.

Page 433: Manual PHP

$objeto->insertBefore($nuevo_elemento, $otro_elemento->nextSibling);

Incluye $nuevo_elemento en el mismo nivel y a continuación del del $otro_elemento que precede los caracteres obligatorios ->nextSibling.

$objeto->(.. nodos)->AddAttribute(atributo, valor);

Añade un nuevo atributo al elemento $objeto->(.. nodos) y le asigna el valor indicado por valor.

$objeto->(.. nodos)->->setAttribute(atributo, valor);

Modifica el valor del atributo correspondiente al elemento $objeto->(.. nodos).

$lectura= $objeto->(.. nodos)->->getAttribute(atributo, valor);

Recoge en la variable $lectura el valor del atributo correspondiente al elemento $objeto->(.. nodos).

$objeto->saveXML();

Recoge en una cadena el árbol de contenidos XML del objeto $objeto. Resulta muy útil para visualizar los contenidos y formatos de los documentos XML con los que estamos trabajando.

$objeto->save('fichero');

Permite guardar en un fichero cuyo path, nombre y extensión se indica en el parámetro fichero el contenido del objeto $objeto.

En este ejemplo incluimos las diferentes opciones de uso de estos métodos para crear un documento XML.

<?php header("content-type: text/xml"); /* Empezamos incluyendo una cabecera que especifica el tipo de documento resultante de la ejecución de este script. Será text/xml. De esta forma los navegadores, IE, Firefox y Opera nos permitirán ver la estructura del documento. Creamos un nuevo objeto DOMDocument en cuyo prólogo incluiremos la versión de XML (1.0) y la forma de codificación (utf8) dentificaremos el objeto mediante el nombre \$mi_XML */ $mi_XML = new DOMDocument('1.0','utf-8'); /* Las propiedades $mi_XML->encoding habrá tomado el valor utf-8. y $mi_XML->xmlVersion tendrá como valor 1.0 $mi_XML->formatOutput toma por defecto el valor false, podemos modificar esa propiedad (a true) con: */ $mi_XML->formatOutput=true; /* La propiedad $mi_XML->preserveWhiteSpace toma true por defecto. Podriamos cambiarla a false de la forma siguiente: */ $mi_XML->preserveWhiteSpace=false; /* Creado el preámbulo del objeto $mi_XML el paso siguiente sería crear los diferentes elementos que lo componen. El elemento raiz de nuestro esquema se llama es profesores. Lo creamos de la siguiente forma: */ $raiz=$mi_XML->CreateElement('profesores'); /* Debemos añadirlo como un hijo (Child) del objeto principal */ $mi_XML->appendChild($raiz); /* vamos a agregar algunod de comentarios. Empezamos por crearlos */ $c1=$mi_XML->CreateComment('Voy fuera de profesores');

Page 434: Manual PHP

$c2=$mi_XML->CreateComment('Voy dentro de profesores'); $c3=$mi_XML->CreateComment('Quiero ser el primero'); $c4=$mi_XML->CreateComment('Quiero ponerme antes de los profes'); $c5=$mi_XML->CreateComment('Me insertarán de otra forma'); /* los insertamos (lo importante es donde) El primer comentario será un hijo del objeto principal y se insertará detrás del último de los ya insertados (detrás del elemento profesores en este caso*/ $mi_XML->appendChild($c1); /* este comentario iría dentro del elementos profesores ya que $raiz es la variable sobre la que fue creado el elemento profesores */ $raiz->appendChild($c2); /* colocaremos a hora $c3 en primera posicion mediante insertBefore */ $mi_XML->insertBefore($c3,$raiz); /* el comentario $c4 lo vamos a colocar justamente detras de $c3 $c3 "seré el siguiente hermano de $c3" */ $mi_XML->insertBefore($c4,$c3->nextSibling); $mi_XML->getElementsByTagName('profesores')->item(0)->appendChild($c5); /* crearemos un nuevo elemento (profe) */ $un_profe=$mi_XML->CreateElement('profe'); /* lo insertamos dentro de del nodo $raiz (profesores) */ $raiz->appendChild($un_profe); /* iremos añadiendo a este profe recien creado los diferentes elementos segun el diseño del gráfico del organigrama */ $un_profe->setAttribute('situacion',utf8_encode("Func en practicas")); $p1=$mi_XML->CreateElement('nombre','Antonio'); $p2=$mi_XML->CreateElement('apellidos','López del Río'); $un_profe->appendChild($p1); $mi_XML->getElementsByTagName('profe')->item(0)->appendChild($p2); $mi_XML->getElementsByTagName('profe')->item(0)->setAttribute('sexo',"varon"); $p3=$mi_XML->CreateElement('nacimiento','21-01-1978'); $p3->setAttribute('zodiaco','Acuario'); $un_profe->appendChild($p3); /* ahora va la retribución */ $p4=$mi_XML->CreateElement('retribucion'); $p4->setAttribute('IRPF','15 %'); $p4->setAttribute('sexenios','5'); $p5=$mi_XML->CreateElement('base'); $p6=$mi_XML->CreateElement('complemento'); $p5t=$mi_XML->createTextNode('1678,32 €'); $p6t=$mi_XML->createTextNode('1678,32 €'); $un_profe->appendChild($p4); $p4->appendChild($p5); $p4->appendChild($p6); $p5->appendChild($p5t); $p6->appendChild($p6t); $p7=$mi_XML->CreateElement('valoracion'); $cdata =$mi_XML->createCDATASection("Inspección día 23/08/1998\n Llega tarde y sin corbata"); $p7->appendChild($cdata); $un_profe->appendChild($p7); /* agregado masivo de datos a partir de arrays que pueden proceder de la lectura de una base de datos */ $nombres=array('Juan','Perico','Andres','Dolores','Rosario','Alejandra'); $apellidos=array('Fernández González','Ruíz del Olmo','García García','Díaz Alonso', 'Carbajal Berrocal','Altamira del Busto'); $sexos=array('Varon','Varon','Varon','Hembra','Hembra','Hembra'); $situaciones=array ('Catedrático','Profesor','Interino','Interina','Catedrática','Profesora'); $nacimiento=array('14-12-1967','21-02-1981','23-04-1978','16-06-1967','05-08-1981','03-10-1987'); $signo=array('Aries','Acuario','Libra','Tauro','Sagitario','Cancer');

Page 435: Manual PHP

$sueldo_base=array('1.247,32 €','1.257,22 €','1.217,02 €','1.307,20 €','2.047,95 €','1.300,00 €'); $complementos=array('1.300 €','1.124 €','1.800 €','1.417 €','1.632 €','1.497 €'); $irpf=array('13%','12%','23%','21%','18.5%','11%'); $sexenios=array(1,2,3,4,5,6); $valoracion=array('Excelente profesional','Manifiestamente mejorable','Necesita reciclaje', 'Impecable','Excepcional','Promete grandes éxitos'); $cuenta=$mi_XML->getElementsByTagName('profe')->length; for ($i=0;$i<sizeof($nombres);$i++){ $un_profe=$mi_XML->CreateElement('profe'); $raiz->appendChild($un_profe); $un_profe->setAttribute('situacion',$situaciones[$i]); $p1=$mi_XML->CreateElement('nombre',$nombres[$i]); $p2=$mi_XML->CreateElement('apellidos',$apellidos[$i]); $un_profe->appendChild($p1); $mi_XML->getElementsByTagName('profe')->item($i+$cuenta)->appendChild($p2); $mi_XML->getElementsByTagName('profe')->item($i+$cuenta)->setAttribute('sexo',$sexos[$i]); $p3=$mi_XML->CreateElement('nacimiento',$nacimiento[$i]); $p3->setAttribute('zodiaco',$signo[$i]); $un_profe->appendChild($p3); $p4=$mi_XML->CreateElement('retribucion'); $p4->setAttribute('IRPF',$irpf[$i]); $p4->setAttribute('sexenios',$sexenios[$i]); $p5=$mi_XML->CreateElement('base'); $p6=$mi_XML->CreateElement('complemento'); $p5t=$mi_XML->createTextNode($sueldo_base[$i]); $p6t=$mi_XML->createTextNode($complementos[$i]); $un_profe->appendChild($p4); $p4->appendChild($p5); $p4->appendChild($p6); $p5->appendChild($p5t); $p6->appendChild($p6t); $p7=$mi_XML->CreateElement('valoracion'); $cdata =$mi_XML->createCDATASection($valoracion[$i]); $p7->appendChild($cdata); $un_profe->appendChild($p7); } echo $mi_XML->saveXML(); $mi_XML->save('ejemplo478.xml'); ?>

Ejecutar el script Ver documento XML

Como muestra de cual es el procedimiento para interpretar y reconocer los valores de los nodos de un documento hemos incluido este ejemplo.

Ejemplo481.php Ver código fuente

Extraer datos de un fichero XML

En la página anterior veíamos un ejemplo de como confeccionar un documento XML partiendo de los contenidos en una tabla MySQL. En este ejemplo veremos algunas opciones para efectuar el proceso inverso. Partiendo del documento XML que hemos elaborado en el ejemplo anterior recogeremos en variables PHP los contenidos de aquel documento.

Page 436: Manual PHP

<?php /* creamos un nuevo objeto y cargamo el fichero XML creado en el ejemplo anterior */ $mi_XML = new DOMDocument(); $mi_XML->load('ejemplo478.xml'); /* determinamos el numero de nodos con nombre "profe" */ $numero_profes=$mi_XML->getElementsByTagName('profe')->length; /* creamos nuevos objetos que contenga los nodos con los diferentes nombres Esta manera de hacerlo puede plantear un problema. De haber otros nodos, por ejemplo alumnos que contuvieran nombres o apellidos los recogeria todos sin discriminar a que nodo padre pertenecen */ $los_profes=$mi_XML->getElementsByTagName('profe'); $los_nombres=$mi_XML->getElementsByTagName('nombre'); $los_apellidos=$mi_XML->getElementsByTagName('apellidos'); $el_nacimiento=$mi_XML->getElementsByTagName('nacimiento'); $la_retribucion=$mi_XML->getElementsByTagName('retribucion'); $la_base=$mi_XML->getElementsByTagName('base'); $el_complemento=$mi_XML->getElementsByTagName('complemento'); $la_valoracion=$mi_XML->getElementsByTagName('valoracion'); /* recorremos el bucle de los diferentes objetos extrayendo sus valores mediante nodeValue en el caso de nodos y por medio de getAttribute en el caso de los atributos. Vamos recogiendo los diferentes valores en arrays escalares */ for ($i=0;$i<$numero_profes;$i++){ $sexo[]=$los_profes->item($i)->getAttribute('sexo'); $situacion[]=$los_profes->item($i)->getAttribute('situacion'); $nombre[]=$los_nombres->item($i)->nodeValue; $apellidos[]=$los_apellidos->item($i)->nodeValue; $nacimiento[]=$el_nacimiento->item($i)->nodeValue; $zodiaco[]=$el_nacimiento->item($i)->getAttribute('zodiaco'); $base[]=$la_base->item($i)->nodeValue; $irpf[]=$la_retribucion->item($i)->getAttribute('IRPF'); $sexenios[]=$la_retribucion->item($i)->getAttribute('sexenios'); $complemento[]=$el_complemento->item($i)->nodeValue; $valoracion[]=$la_valoracion->item($i)->nodeValue; } /* insertamos el encabezado de una tabla para presentar los resultados que también podrían ser utilizados para crear una tabla en MySQL o para cualquier otro proposito en el que se manejen variables PHP */ ?> <table> <tr style="font-size:10px"><td>Nombre</td><td>Apellidos</td> <td>Sexo</td><td>Situación</td><td>Fecha nacimiento</td> <td>Zodiaco</td><td>Sueldo base</td><td>Complementos</td> <td>IRPF</td><td>Sexenios</td><td>Valoración</td></tr> <?php /*mediante este bucle creamos las sucesivas filas de la tabla */ for ($i=0;$i<sizeof($nombre);$i++){ print "<tr style='font-size:10px'><td>".$nombre[$i]."</td>"; print "<td>".utf8_decode($apellidos[$i])."</td><td>".$sexo[$i]."</td>"; print "<td>".utf8_decode($situacion[$i])."</td><td>".$nacimiento[$i]."</td>"; print "<td>".$zodiaco[$i]."</td><td>".utf8_decode($base[$i])."</td>"; print "<td>".utf8_decode($complemento[$i])."</td><td>".$irpf[$i]."</td>"; print "<td>".$sexenios[$i]."</td><td>".utf8_decode($valoracion[$i])."</td></tr>"; } print "</table><br /><br /><br />"; /* como opción alternativa para extraer los mismos valores asegurándose que no se incluyen más que los correspondientes a los profesores podemos extraerlos del nodo profes. Extraemos los atributos de cada uno de los objetos profe (sexo y situación) y los demás valores los extraemos siempre del item(0) ya que el objeto nombre es hijo (único) de profes */

Page 437: Manual PHP

$prof=$mi_XML->getElementsByTagName('profe'); for ($i=0;$i<$numero_profes;$i++){ $n_sexo[]=$prof->item($i)->getAttribute('sexo'); $n_situacion[]=$prof->item($i)->getAttribute('situacion'); $n_nombre[]=$prof->item($i)->getElementsByTagName('nombre')->item(0)->nodeValue; $n_apellidos[]=$prof->item($i)->getElementsByTagName('apellidos')->item(0)->nodeValue; $n_nacimiento[]=$prof->item($i)->getElementsByTagName('nacimiento')->item(0)->nodeValue; $n_zodiaco[]=$prof->item($i)->getElementsByTagName('nacimiento')->item(0)->getAttribute('zodiaco'); $n_base[]=$prof->item($i)->getElementsByTagName('base')->item(0)->nodeValue; $n_irpf[]=$prof->item($i)->getElementsByTagName('retribucion')->item(0)->getAttribute('IRPF'); $n_sexe[]=$prof->item($i)->getElementsByTagName('retribucion')->item(0)->getAttribute('sexenios'); $n_complemento[]=$prof->item($i)->getElementsByTagName('complemento')->item(0)->nodeValue; $n_valoracion[]=$prof->item($i)->getElementsByTagName('valoracion')->item(0)->nodeValue; } /* repetimos el proceso anterior con un pequeño cambio. Durante la codificación/decodificación del símbolo del euro hemos perdido ese carácter que ahora aparece sustituido por un signo de interrogación. str_replace nos permite corregir esa deficiencia visual tal como puedes ver si comparas las dos tablas resultantes de la ejecución de este script */ ?> <table> <tr style="font-size:10px"><td>Nombre</td><td>Apellidos</td> <td>Sexo</td><td>Situación</td><td>Fecha nacimiento</td> <td>Zodiaco</td><td>Sueldo base</td><td>Complementos</td> <td>IRPF</td><td>Sexenios</td><td>Valoración</td> <?php for ($i=0;$i<sizeof($nombre);$i++){ print "<tr style='font-size:10px'><td>".$n_nombre[$i]."</td>"; print "<td>".utf8_decode($n_apellidos[$i])."</td><td>".$n_sexo[$i]."</td>"; print "<td>".utf8_decode($n_situacion[$i])."</td><td>".$n_nacimiento[$i]."</td>"; print "<td>".$n_zodiaco[$i]."</td><td>".str_replace('?', '€', utf8_decode($n_base[$i]))."</td>"; print "<td>".str_replace('?', '€', utf8_decode($n_complemento[$i]))."</td><td>".$n_irpf[$i]."</td>"; print "<td>".$n_sexe[$i]."</td><td>".utf8_decode($n_valoracion[$i])."</td></tr>"; } print "</table><br /><br /><br />"; ?>

Ejecutar el script

Importar datos de XML a otro XML

Además de los métodos descritos anteriormente disponemos de:

$objeto->importNode(nodo_a_importar, boleano);

Agrega al objeto actual el contenido del objeto nodo_a_importar. La opción booleano debe tener el valor TRUE cuando se pretenda importar los atributos junto con los elementos del nodo.

Este ejemplo es una pequeña muestra de las posibilidades que ofrece PHP para la modificación de los contenidos de ficheros XML.

Page 438: Manual PHP

<?php header ("content-type: text/plain"); /* creamos dos objetos uno llamado importador que será el que recoja los datos de un documento preexistente y mi_XML que será el objeto que contendrá el nuevo fichero */ $importador = new DOMDocument(); $mi_XML = new DOMDocument('1.0','utf-8'); /* cargamos el documento ejemplo478.xml completo */ $importador->load('ejemplo478.xml'); /* extraemos el objeto raiz llamado profesores y objeto único, por eso el item(0))(olvidando líneas de comentario del orginal) y lo ponemos en un objeto nuevo llamado colocar */ $colocar = $importador->getElementsByTagName("profesores")->item(0); /* el objeto mi_XML aun no contiene nada asi que crearemos un nuevo objeto y se lo agregaremos. Al ser el primer elemento será el elemento raiz del nuevo documento*/ $nueva_raiz=$mi_XML->CreateElement('plantilla_completa'); $mi_XML->appendChild($nueva_raiz); /* insertamos los elementos extraidos del fichero importado que habíamos recogido en una variable llamada colocar */ $agregar = $mi_XML->importNode($colocar, true); /* colocamos el nuevo objeto agregar como hijo de la raiz de documento */ $nueva_raiz->appendChild($agregar); /*añadimos un nuevo atributo al elemento profesores (unico, era el raiz del fichero que acabamos de importar) */ $mi_XML->getElementsByTagName("profesores")->item(0)->setAttribute('nivel','Secundaria'); /*creamos un objeto con con datos del profesores del nodo importador repitiendo lo que hemos hecho conaterioridad */ $extraer = $importador->getElementsByTagName("profesores")->item(0); /* importamos el nodo completo */ $agregar = $mi_XML->importNode($extraer, true); /* insertamos nuevamente el nodo profesores recogido previamente en el objeto agregar */ $nueva_raiz->appendChild($agregar); /* asignamos al nodo profesores recien creado item(1) el atributo nivel */ $mi_XML->getElementsByTagName("profesores")->item(1)->setAttribute('nivel','Primaria'); /* creamos un objeto primarios que contiene todos los elementos profe de estos ultimos profesores */ $primarios=$mi_XML->getElementsByTagName("profesores")->item(1)->getElementsByTagName("profe"); /* modificamos el atributo situación de los profes de primarios */ for($i=0;$i<$primarios->length;++$i){ $cambiar=$primarios->item($i)->getAttribute('situacion'); switch ($cambiar){ case "Catedrática": $primarios->item($i)->setAttribute('situacion','Profesora de Segundo Ciclo'); break; case "Profesor": $primarios->item($i)->setAttribute('situacion','Profesor de Primer Ciclo'); break; case "Profesora": $primarios->item($i)->setAttribute('situacion','Profesora de Primer Ciclo'); break; case "Catedrático": $primarios->item($i)->setAttribute('situacion','Profesor de Segundo Ciclo'); break; } } /* vamos a modificar datos de nombres y apellidos para evitar los duplicado consecuencia de haber importado dos veces el mismo nodo */

Page 439: Manual PHP

$nuevos_apellidos=array('Alonso Galguera','Ruíz de Alda','Alonso Aller', 'Menéndez López','García Uría','Alvarez del Campo','Feito Parrondo'); $nuevo_nombre=array ('Luis','Eduardo','Fernando','Gonzalo','Carmela', 'Luisa','Servanda'); /* recorremos los elementos primarios cambiando el valor de los nodos nombre (con item0 por tener solo un elemento) por el nuevo nombre y los apellidos por lo nuevos apellidos y además hacemos una sustitución de la valoración en el caso de producirse una coincidencia */ for($i=0;$i<$primarios->length;++$i){ $primarios->item($i)->getElementsByTagName("nombre")->item(0)->nodeValue=$nuevo_nombre[$i]; $primarios->item($i)->getElementsByTagName("apellidos")->item(0)-> nodeValue=utf8_encode($nuevos_apellidos[$i]); if($primarios->item($i)->getElementsByTagName("valoracion")->item(0)-> nodeValue=="Necesita reciclaje"){ $primarios->item($i)->getElementsByTagName("valoracion")->item(0) ->firstChild->nodeValue=utf8_encode("Además de reciclaje\n necesita un milagro"); } /* quitamos el atributo zodiaco a todos profes de primara */ $primarios->item($i)->getElementsByTagName("nacimiento")->item(0)->removeAttribute('zodiaco'); /* quitamos el atributo sexenios a todos aquellos cuya situación empiece por Inter */ if(substr($primarios->item($i)->getAttribute("situacion"),0,5)=="Inter"){ $primarios->item($i)->getElementsByTagName("retribucion")->item(0)->removeAttribute('sexenios'); } } /*Busco a Servanda le cambio el nombre por Severina*/ for($i=0;$i<$primarios->length;++$i){ $nombre=$primarios->item($i)->getElementsByTagName("nombre")->item(0)->nodeValue; if ($nombre=="Servanda"){ $primarios->item($i)->getElementsByTagName("nombre")->item(0)->nodeValue="Severina"; } } /* visualizamos los resultados */ echo $mi_XML->saveXML(); ?>

Ejecutar el script

PDO – Bases de datos SQLite / MySQL

Bases de datos SQLite

SQLite es una librería que permite gestionar bases de datos SQL sin necesidad de tener instalado un servidor como requieren MySQL y otros gestores. Ello significa que en la mayoría de los casos la gestión de bases de datos personales podría realizarse sin necesidad de instalar ningún servidor adicional a Apache + PHP.

Una base de datos SQLite es simplemente un fichero cuyo nombre y extensión podemos asignar con total libertad y que puede estar alojado en cualquier directorio de nuestro servidor PHP con la única condición de que ese directorio –en el caso de trabajar sobre Ubuntu– tenga permisos de escritura.

Aunque PHP dispone de todo un conjunto de funciones específicas para las bases de datos SQLite similares a las vistas para el caso de MySQL nosotros vamos a ver la forma de gestionar bases de datos (tanto SQLite como MySQL) de un modo distinto. Lo haremos por medio de la extensión PDO de PHP.

Page 440: Manual PHP

De esta forma, además de familiarizarnos con el manejo de las bases de datos SQLite, conoceremos una forma alternativa de gestionar MySQL. Es por ello que a lo largo de esta página y las siguientes nos referirmos con frecuencia a ejemplos, situaciones y operaciones ya estudiadas en MySQL. Ahora trataremos de hacer algo muy similar a aquello pero utilizando de forma exclusiva la extensiónPDO.

PDO - PHP Data Objects

PHP Data Objects (PDO) es una extensión de PHP para la formalización de las conexiones de PHP con una de base de datos por medio de una interfaz uniforme. PDO proporciona una capa de abstracción de acceso a datos, que significa que, se utilizarán las mismas funciones para la gestión de las bases de datos con independencia del tipo de base que estemos utilizando.

Ello significa que, con muy ligeras modificaciones, un mismo script podría gestionar diferentes tipos de bases de datos tales como: SQLite, MySQL, Postgres, Oracle ó Firebird.

Configuración de PHP

El uso de PDO requiere el uso de una extensión específica de PHP para cada uno de los tipos de bases de datos que pretendamos gestionar. En nuestro caso vamos a manejar MySQL y SQLite lo que requiere que, bajo Windows, usemos las extensionesextension=php_pdo_sqlite.dll y extension=php_pdo_mysql.dll Habremos de editar el fichero php.ini, buscar esas dos extensiones, descomentarlarlas (quitar el punto y coma que llevan delante), guardar los cambios y reiniciar el servidor.

Cuando se trata de Ubuntu es preciso realizar la siguiente instalación:

sudo apt-get install php5-sqlite

con lo cual, en cualquiera de los dos casos, una vez reiniciado el servidor la página http://localhost/info.php debería mostrarnos una información similar a esta:

Gestión de bases de datos SQLite y MySQL mediante PDO

Dada la gran similitud entre los procedimientos (esa es una de las ventajas de utilizar PDO) vamos a intentar ir viendo de forma simultánea el uso de este interface tanto para SQLite como para MySQL. Lo iremos haciendo en distintos epígrafes procurando mostrar las afinidades y diferencias.

Después de haber visto el tema relativo al manejo de clases y objetos y la aplicación de la clase TCDF para la gestión de ficheros PDF te resultará fácil y familiar el manejo del PDO ya que lo que haremos en estos temas será manejar objetos de la clase PDO.

Page 441: Manual PHP

Aunque iremos viendo los diferentes métodos de esta clase tiene particular interés que empecemos conociendo un los más importantes:

$objeto->query(SENTENCIA)

que permite ejecutar cualquier SENTENCIA SQL sobre el $objeto sea cual fuere el tipo de base de datos en uso. Quiere ello decir que usaremos este mismo método para cualquier proceso de: creación, modificación, baja, o consulta de bases datos y tablas.

Creación de bases y/o acceso a bases de datos

Creación y/o conexión a una base de datos SQLite

Requiere únicamente la creación de un objeto PDO con la siguiente sintaxis:

$objeto= new PDO ('sqlite: fichero ')

dónde $objeto es el identificador de un nuevo objeto, sqlite: es una palabra reservada y obligatoria que especifica el tipo de base de datos y fichero es la ruta y nombre de un fichero destinado a guardar la nueva base de datos. Si el fichero no existe se crearía uno nuevo y en caso de que ya existiera no se sobreescribiría manteniendo toda la información que pudiera contener.

En este ejemplo puedes ver la forma de crear bases de datos SQLite. Si lo ejecutas podrás ver que se han creado los dos ficheros en los directorios indicados y que, su longitud es cero bytes. Es lógico que así sea ya que aún no contienen información alguna.

<?php /* creamos un objeto PDO con el nombre $dbSQLite. Es importante que el parámetro que se incluye comience por sqlite: (esa palabra es la indica el driver debe utilizar PHP para gestionar la base de datos es SQLite)y a continuación la cadena con la ruta y nombre del fichero que contendrá la base de datos */ $dbSQLite= new PDO('sqlite:./images/baseSQLite.zpq'); /* una opción un poco más sofisticada podría utilizar una función y devolvernos un mensaje de confirmación o de error. */ function conecta($nombre){ if ($db = new PDO('sqlite:'.$nombre)){ print "Creado o abierto el fichero SqLite: ".$nombre.'<br />'; return($db); } else { print "Error: No puede conectarse con la base de datos.<br />"; print "<br />".$db->errorInfo()."<br />"; exit(); } } /* si hacemos una llamada a esta función pasándole como parámetro el nombre de la base de datos y (opcionalmente) la ruta habremos creado un nuevo objeto y un nuevo fichero de base de datos */ $dbSQLite1= conecta('practicar'); /* destruimos el objeto con ello desconectamos de la base de datos */ $dbSQLite1=NULL; $dbSQLite=NULL; /* destruimos los objetos (la conexión) pero los ficheros se mantienen*/ ?>

ejemplo482.php

¡Cuidado! Es importante elegir un lugar adecuado para almacenar las bases de datos SQLite. Al tratarse de un único fichero si lo dejamos dentro del directorio raíz (htdocs en la configuración aquí descrita) o en uno de sus directorios es obvia su vulnerabilidad. Fíjate que este enlace http://www.rinconastur.net/php/ejemploSQLite permite descargar sin problema

Page 442: Manual PHP

alguno la base de datos SQLite que hemos incluido como ejemplos. Además, al carecer de contraseña, los datos que contiene son totalmente accesibles. La forma de evitar –o al menos paliar– ese riesgo sería almacenar este fichero fuera del document_root (htdocs) de la misma forma que hacíamos con las contraseñas de acceso a MySQL

Creación y conexión con una base de datos MySQL

Utilizando la clase PDO el acceso a una base de datos MySQL (como es lógico, el servidor de bases de datos MySQL tiene que estar activo) requiere únicamente la creación de un objeto PDO con la siguiente sintaxis:

$objeto= new PDO ("mysql: servidor " ,usuario,contraseña )

dónde $objeto es el identificador de un nuevo objeto, mysql: es una palabra reservada y obligatoria que especifica el tipo de base de datos, servidor es una cadena formada por: host=nombre_del_servidor_MySQL (en nuestro caso localhost), usuario el nombre de usuario (pepe en el caso de nuestros ejemplos) y contraseña (la contraseña de usuario, pepa en nuestro caso).

La cadena que estamos señalando como servidor admite una opción un poco más amplia que tendría la forma:host=nombre_del_servidor_MySQL ; dbname=nombre_de_la_base_de_datos. En ese caso el objeto devuelto sería la conexión al servidor MySQL y el acceso a la base de datos señalada mediante nombre_de_la_base_de_datos. Sería una situación equivalente a la resultante del uso de mysql_select_db cuando usábamos las funciones MySQL de PHP.

En este ejemplo puedes ver la forma de acceder a bases de datos MySQL. Por mantener el paralelismo con SQLite nos hemos visto obligados a incluir dos pasos. En el primero se comprueba la existencia de la base de datos y se crea si no existera. En el segundo se produce el acceso a la propia base de datos.

<?php function conecta($base){ $db = new PDO('mysql:host=localhost','pepe','pepa'); /* crea la base de datos si no existe */ $db->query("CREATE DATABASE IF NOT EXISTS ".$base); /* crea un nuevo objeto usando la base de datos pasada como parámetro sin riesgo de error por inexistencia de la base */ if($db1 = new PDO('mysql:host=localhost;dbname='.$base,'pepe','pepa')){ print "Creado o abierta la base de datos MySQL: ".$base.'<br />'; return($db1); } else { print "Error: No puede conectarse con la base de datos.<br />"; print "<br />".$db1->errorInfo()."<br />"; exit(); } } /* Hacemos una llamada a esta función pasándole como parámetro el nombre de la base de datos */ $dbSQLite1= conecta('practicar'); ?>

ejemplo483.php

¡Cuidado! Presta atención a la sintaxis sin olvidar la correcta ubicación de las comillas. $objeto=new PDO ("mysql:host=localhost","pepe","pepa") o $objeto=new PDO ("mysql:host=localhost; dbname=ejemploSQLite", "pepe", "pepa") sería la sintaxis válida. Utiliza siempre comillas dobles (estas " ") en este tipo de sentencias. Si los nombres de bases de datos, usuarios, host y/o contraseñas proceden de variables la sintaxis que deberás utilizar será esta: new PDO("mysql:host=$host;dbname=$base_datos",$usuario,$password)

Unificación del proceso de conexión

Page 443: Manual PHP

Nuestro propósito es ir desarrollando, de forma simultánea, ejemplos con ambas bases de datos. Eso no requerirá manejar, en paralelo, objetos de ambos tipos. Para facilitar un poco las cosas,en el ejemplo siguiente veremos como unificar en una sola función los procesos de conexión a ambas bases de datos.

De esa forma, modificando un solo parámetro, podríamos crear indistintamente objetos de uno u otro tipo.

<?php function conecta($base,$tipo){ if ($tipo=='MySQL'){ $db = new PDO('mysql:host=localhost','pepe','pepa'); /* crea la base de datos si no existe */ $db->query("CREATE DATABASE IF NOT EXISTS ".$base); /* crea un nuevo objeto usando la base de datos pasada como parámetro sin riesgo de error por inexistencia de la base */ if($db1 = new PDO('mysql:host=localhost;dbname='.$base,'pepe','pepa')){ print "Creado o abierta la base de datos MySQL: ".$base.'<br />'; return($db1); } else { print "Error: No puede conectarse con la base de datos.<br />"; print "<br />".$db1->errorInfo()."<br />"; exit(); } } if ($tipo=='SQLite'){ if ($db = new PDO('sqlite:'.$base)){ print "Creado o abierto el fichero SqLite: ".$base.'<br />'; return($db); } else { print "Error: No puede conectarse con la base de datos.<br />"; print "<br />".$db->errorInfo()."<br />"; exit(); } } } /* Hacemos una llamada a esta función pasándole como parámetros nombre de la base de datos y tipo*/ $dbDual= conecta('practicar','MySQL'); ?>

Caso MySQL Caso SQLite

Desconexión de una base de datos SQLite

Aunque en SQLite no cabe hablar de desconexión, utilizaremos esa palabra por similitud con lo visto para MySQL. En este caso lo que llamaremos desconexión no será otra cosa que la destrucción del objeto creado a acceder. Para ello bastaría con escribir:

$objeto= NULL

En cualquier caso resulta innecesario. En PHP los objetos se destruyen al finalizar la ejecución del script mediante el que han sido creados.

Borrar bases de datos SQLite

Como quiera que una base de datos SQLite no es otra cosa que un fichero, para eliminarla sería suficiente borrar tal ficheromediante la función PHP:

unlink(nombre_del_fichero)

Cuando se trata de eliminar bases de datos (como es lógico se eliminarían las eventuales tablas e información que pudiera contener) basta proceder de igual forma que para borrar cualquier otro fichero.

Page 444: Manual PHP

<?php /* borramos las bases de datos creadas en el ejemplo anterior */ unlink('./images/baseSQLite.zpq'); unlink('practicar'); ?>

ejemplo486.php

Borrar bases de datos MySQL

Cuando se trata de borrar bases de datos MySQL se utiliza el método:

$objeto->query(DROP DATABASEnombre_de_la_base)

como verás la sentencia SQL es la que hemos manejado con anterioridad y que puedes recordar en desde este enlace. El ejemplo siguiente contiene un script que borra la base de datos MySQL creada en los ejemplos anteriores.

Ver código fuente

Formas de almacenamiento de SQLite

A diferencia de MySQL, SQLite maneja solamente cinco formas de almacenamiento:

� – NULL.– Su valor es un valor nulo.

� – INTEGER.– Es un entero con signo, almacenado en 1, 2, 3, 4, 6, u 8 bytes dependiendo de la magnitud del número.

� – REAL.– El valor es un número de punto flotante de 8 bytes.

� – TEXT.– El valor es una cadena de texto, almacenados utilizando la codificación de base de datos (UTF-8, UTF-16BE o UTF-16LE).

� – BLOB.– El valor son los datos de entrada almacenados tal cual fueron introducidos.

En cualquier caso SQLite es extremadamente flexible en este sentido. Cualquier campo, con excepción de los definidos comoINTEGER PRIMARY KEY, puede utilizarse para almacenar un valor de cualquier tipo.

SQLite no dispone de un tipo específico para almacener fechas. Sin embargo dispone de fuciones internas permiten tratar datos de fecha y hora a partir de los siguientes formatos:

� – Para fechas con formato: YYYY-MM-DD HH:MM:SS.SSS deben utilizarse campos tipo TEXT.

� – Fechas expresadas en cuenta de días julianos –la función juliantojd() nos permite conocer esos valores– deberemos almacenarlo en un campo de tipo REAL.

� – Fechas expresadas en tiempo Unix se almacenarían en un campo de tipo INTEGER

Parámetros de los campos

Además del nombre de campo y la declaración de tipo de datos, SQLite permite incluir opcionalmente unos parámetros adicionales similares a los utilizados por MySQL. Entre ellos los siguientes: PRIMARY KEY, UNIQUE, INTEGER PRIMARY KEY, CHECK, NOT NULL,DEFAULT, AUTOINCREMENT.

La diferencias más sustanciales respecto a MySQL son estas:

� – SQLite no admite campos con formatos similares a los tipos ENUM y SET.

� – SQLite no admite que se incluyan los flags: ZEROFILL ni UNSIGNED que sí se usan en MySQL.

� – Los campos autoincrementales en SQLite solo son posibles si se les define como INTEGER PRIMARY KEY AUTOINCREMENT.

¡Cuidado!

Page 445: Manual PHP

SQLite ignora los valores que se trate de asignar al tamaño del campo. SQLite no impone ninguna limitación en el tamaño de un campo siempre que no sobrepase el valor máximo preestablecido que está establecido en 109 bytes.

Flexibilidad en la declaración de tipos de datos

SQLite es enormemente flexible en cuanto a la declaración del tipos de datos contenidos en sus tablas. En este sentido debemos diferenciar tres elementos: tipos declarados, afinidades y forma de almacenamiento.

Cuando definimos tablas en MySQL es necesario hacer una estricta declaración de tipos para cada uno de los campos. Recuerda que la sintaxis era similar a esta: numero1 VARCHAR(32). En el caso de SQLite las cosas son más sencillas ya que la propia librería dispone de recursos capaces de interpretar de forma inteligente los tipos declarados y traducirlos a una de sus cinco formas de almacenamiento. Para ello sigue las reglas especificadas en la tabla siguiente:

Regla Funcionamiento

1 Si el tipo declarado contiene la cadena INT se le asigna afinidad INTEGER

2 Si el tipo declarado contiene cualquiera de las cadenas: CHAR, CLOB, o TEXT, se asignará afinidad TEXT.

3 Si el tipo declarado contiene la cadena BLOB, o si no se define ningún tipo, se le asignará afinidad NONE

4 Si el tipo declarado contiene cualquiera de las cadenas: REAL, FLOA, o DOUB se el asigna afinidad REAL

5 En cualquier otro caso la afinidad se considera NUMERIC

Quiere esto decir que podríamos permitirnos la frivolidad de definir un tipo de campo como tipo paraguas. Las reglas de afinidadanteriores lo convertirían de forma automática en NUMERIC dado que no contiene ninguna de las cadenas indicadas en las reglas anteriores. Esta peculiaridad de SQLite resulta muy útil cuando se trata de compatibilizar su sintaxis con la de otros gestores de bases de datos más estrictas en cuanto a su sintaxis. En ejemplos posteriores veremos como SQLite es capaz de manejar, sin apenas modificaciones, definciones de tablas propias de MySQL.

Esta tabla es un resumen de las afinidades de tipos entre las definiciones de los gestores más habituales y SQLite.

Nombres comunes en SQL utilizables en la declaración de tipos Afinidad Regla

INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, UNSIGNED BIG INT, INT2 ,INT8 INTEGER 1

CHARACTER(20), VARCHAR(255), VARYING CHARACTER(255), NCHAR(55), NATIVE CHARACTER(70), NVARCHAR(100), TEXT, CLOB

TEXT 2

BLOB (sin especificar tipos de datos) NONE 3

REAL, DOUBLE, DOUBLE PRECISION, FLOAT REAL 4

NUMERIC, DECIMAL(10,5), BOOLEAN, DATE, DATETIME NUMERIC 5

La flexibilidad de SQLite aún tiene un nuevo aspecto diferencial. ¿Qué puede ocurrir si tratamos de agregar datos alfanuméricos a un campo definido como NUMERIC?

SQLite intentará adecuar los contenidos de los campos a la forma de establecimiento establecida en la definición de tabla y/o las reglas de afinidad y cuando esto no resulta posible adecúa la forma de almacenamiento a los contenidos. En la tabla tienes descritas algunas situaciones en las que se producen adaptaciones de este tipo.

Afinidad Comportamiento

TEXT – Un campo con afinidad TEXT almacena todos los datos utilizando las formas NULL, TEXT o BLOB. – Si se inserta un dato numérico en un campo con afinidad TEXT será convertido en cadena antes de ser almacenado

NUMERIC

– Un campo con afinidad NUMERIC puede contener valores de cualquiera de las cinco clases de almacenamiento. Cuando se insertan textos la clase de almacenamiento del texto el campo intenta convertirse a INTEGER o REAL (en orden de preferencia). Realiza esa transformación si se conservan los 15 primeros dígitos significativos del número (conversión sin pérdidas y reversible). – Si la conversión sin pérdida no es posible, entonces el valor se almacena con la clase de almacenamiento TEXT. – No se intenta convertir los valores NULL o BLOB. – Una cadena con aspecto de punto flotante con un punto decimal o notación exponencial,que pueda ser expresada como un entero será convertida en entero. Por ejemplo, la cadena 3 .0 e +5 se almacena en una columna con afinidad NUMERIC con el valor 300.000, no como el valor de punto flotante 300.000,0.

INTEGER – Una columna que utiliza afinidad INTEGER se comporta igual que una columna con afinidad NUMÉRICO. La diferencia entre la afinidad INTEGER y NUMERIC sólo es evidente en una expresión de conversión

REAL

– Un campo con afinidad REAL se igual que con afinidad NUMERIC excepto que fuerza valores enteros en la representación de punto flotante. – Los valores de punto flotante sin ningún componente fraccionaria (por ejemplo 23.0) se escriben en el disco como enteros con el fin de ocupar menos espacio. Cuando son leidos recuperan su condición de punto flotante.

NONE Una columna con afinidad NONE no establece ninguna preferencia de clase de almacenamiento.

Page 446: Manual PHP

Errores en la clase PDO

La clase PDO dispone de un método que nos permite obtener información sobre los errores que ocasionalmente puedan producirse en el manejo de sus métodos y/o propiedades. Su sintaxis es esta:

$error=$objeto->errorInfo()

dónde el array escalar $error resultante de la ejecución del método errorInfo está formado por tres elementos cuyos contenidos serían los siguientes:

� $error[0] – Identificador alfanumérico de cinco caractes

� $error[1] – Código del error

� $error[2] – Mensaje explicativo del error

Creación de tablas

La creación de tablas SQLite, tiene una sintaxis prácticamente igual a la utilizada por MySQL. Requiere una de estas dos sentencias

CREATE TABLE IF NOT EXISTS tabla (campo1, campo2,... ) o CREATE TABLE tabla (campo1, campo2,... )

La única diferencia entre ambas opciones es que la segunda daría un error si tratáramos de crear una tabla preexistente mientras que la primera no da ese mensaje de error.

La definición de los campos es mucho menos estricta que en MySQL. Sería suficiente con asignarles un nombre sin tan siquiera declarar un tipo. La declaración de tipos, si se opta por hacerla, tiene la amplitud comentada en párrafos anteriores respecto a las afinidades y los parámetros restrictivos –los llamábamos flags de campo en MySQL– tienen como allí carácter opcional y, tal como indicamos un poco más arriba, son básicamente los mismos que MySQL con las excepciones ya comentadas.

La ejecución de las sentencias SQLite por medio de la clase PDO requiere utilizar el método siguiente:

$resultado=$objeto->query(sentencia)

dónde $resultado puede recoger un valor booleano (que confirmaría la correcta ejecución de la sentencia o los eventuales errores producidos durante la ejecución de la misma) o el objeto con el resultado de la ejecución de dicha sentencia (una consulta, por ejemplo).

Base de datos ejemplos

Utilizaremos una base de datos con nombre ejemploSQLite tanto en SQLite como en MySQL. En el primero de los casos será un fichero con ese nombre (ejemploSQLite)que se creará en el servidor. En el segundo será una nueva base de datos añadida a nuestro servidor MySQL. En el mismo script crearemos una tabla en ambas bases (utilizando la misma sentencia) tal como puedes ver en el código fuente.

Ver código fuente

PDO – Altas SQLite / MySQL

Los diferentes elementos de una tabla

Cuando estudiábamos las tablas MySQL habíamos creado una tabla cuyo código fuente puedes ver aquí. Vamos a evaluar ese código y efectuar algunas modificaciones para poder utilizarlo en SQLite. Para mayor comodidad hemos guardado la funcion conecta(), que permitía instanciar el constructor de PDO, en un fichero llamado conecta.inc.php para poder incluirlo de una forma más cómoda en todos nuestros scripts.

Page 447: Manual PHP

Las diferencias sintácticas en la definición de la tabla de ambos tipos de base de datos (MySQL y SQLite) son las que tienes resumidas en esta tabla:

Diferencias de sintaxis en definición de tablas

MySQL Contador TINYINT(8) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT

SQLite Contador INTEGER PRIMARY KEY AUTOINCREMENT SQLite no admite ni ZEROFILL ni UNSIGNED. Los campos auticrementales en SQLite tienen que ser definidos obligatoriamente como: INTEGER PRIMARY KEY AUTOINCREMENT

MySQL DNI CHAR(8) NOT NULL

SQLite DNI CHAR(8) NOT NULL UNIQUE Los indices únicos se definen en SQLite incluyendo la palabra UNIQUE como un flag en la definición del campo.

MySQL Sexo Enum('M','F') DEFAULT 'M' not null

SQLite Sexo VARCHAR DEFAULT 'M' not null SQLite no permite campos del tipo Enum. Por esa razon lo convertimos en VARCHAR. Ya buscará SQLite las afinidades de esta palabra. En cualquier caso no va a presentarnos demasiados problemas. Bastaría con utilizar algún recurso de PHP para restringir el contenido de ese campo a un valor 'M' o 'F'.

MySQL Idiomas SET(' Castellano',' Francés','Inglés',' Alemán',' Búlgaro',' Chino')

SQLite Idiomas VARCHAR Tampoco están permitidos en SQLite los campos tipo SET. Lo sustituimos por VARCHAR. Por cuestión de compatibilidad entre ambas tablas optamos por hacer de este mismo tipo, VARCHAR, el campo Idiomas de la tabla MySQL. Dado que este tipo va a recoger un número en la tabla siempre será posible emular su comportamiento incluyendo una función PHP que interprete ese valor

MySQL PRIMARY KEY(DNI)

SQLite En SQLite las claves primarias, únicas e indices se incluyen en la propia definición del campo. Por lo tanto esta línea no es requerida.

MySQL UNIQUE auto (Contador)

SQLite En SQLite las claves primarias, únicas e indices se incluyen en la propia definición del campo. Por lo tanto esta línea no es requerida.

MySQL type=MyISAM

SQLite La definición de tipo de tabla es exclusiva de MySQL. Cuando se trata de SQLite no se requiere esa definición

Respetando las exigencias comentadas en la tabla anterior hemos creado este script mediante el cual construiremos dos tablas de características bastante similares.

<?php include('conecta.inc.php'); /* Hacemos dos llamada a esta función conecta incluida en el fichero conecta.inc.php como parámetros nombre de la base de datos y tipo. Creamos dos objetos, uno para MySQL y en la otra el SQLite */ $dbMySQL= conecta('ejemploSQLite','MySQL'); $dbSQLite= conecta('ejemploSQLite','SQLite'); /* asignamos a la variable tabla en nombre de la nueva tabla */ $tabla='demo4'; /* transcribimos el código del ejemplo 379 */ $crear="CREATE TABLE IF NOT EXISTS $tabla ("; $crear.="Contador TINYINT(8) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,"; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, "; $crear.="Apellido2 VARCHAR (15) not null, "; $crear.="Nacimiento DATE DEFAULT '1970-12-21', "; $crear.="Hora TIME DEFAULT '00:00:00', "; $crear.="Sexo Enum('M','F') DEFAULT 'M' not null, "; $crear.="Fumador CHAR(0) , ";

Page 448: Manual PHP

$crear.="Idiomas SET(' Castellano',' Francés','Inglés',' Alemán',' Búlgaro',' Chino'), "; $crear.=" PRIMARY KEY(DNI), "; $crear.=" UNIQUE auto (Contador)"; $crear.=")type=MyISAM"; $crear1="CREATE TABLE IF NOT EXISTS $tabla ("; $crear1.="Contador INTEGER PRIMARY KEY AUTOINCREMENT,"; $crear1.="DNI CHAR(8) NOT NULL UNIQUE, "; $crear1.="Nombre VARCHAR (20) NOT NULL, "; $crear1.="Apellido1 VARCHAR (15) not null, "; $crear1.="Apellido2 VARCHAR (15) not null, "; $crear1.="Nacimiento DATE DEFAULT '1970-12-21', "; $crear1.="Hora TIME DEFAULT '00:00:00', "; $crear1.="Sexo VARCHAR DEFAULT 'M' not null, "; $crear1.="Fumador CHAR(0) , "; $crear1.="Idiomas VARCHAR "; $crear1.=")"; /* realizamos la consulta de creación en ambas bases de datos*/ if($dbSQLite->query($crear1)){ print "La tabla SQLite $tabla ha sido CREADA<br>"; } else{ print "Ha habido un error de PDO: <br />" ; echo "<pre>"; print_r($dbSQLite->errorInfo()); echo "</pre>"; } /* repetimos el mismo proceso ahora con la base de datos MySQL */ if($dbMySQL->query($crear)){ print "La tabla MySQL $tabla ha sido CREADA<br>"; } else{ print "Ha habido un error de PDO: <br />" ; echo "<pre>"; print_r($dbSQLite->errorInfo()); echo "</pre>"; } ?>

Añadir registros a una tabla

La sentencia SQLite que permite añadir registros a una tabla –muy similar a la utilizada por MySQL– es la siguiente:

INSERT INTO tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

dónde tabla es el nombre de la tabla, campo1, campo2, etc. son los nombres de los campos y valor1, valor2, etc. son los valores a incluir en cada uno de los campos.

La única diferencia entre esta sentencia y la equivalente de MySQL es que aquí la palabra INTO tiene carácter obligatorio mientras que allí solo es opcional.

Añadir un registro

Igual que ocurría en el caso de MySQL también aquí puede añadirse un registro incluyendo los valores en la propia sentencia o recogiéndolos de variables PHP previamente definidas.

Page 449: Manual PHP

En este ejemplo, dual como todos los siguientes, se agrega a la tabla demo4 de dos bases de datos distintas –SQLite y MySQL– cuyo nombre es ejemploSQLite un registro cuyos valores son:

DNI Nombre Apellido1 Apellido2 Nacimiento Sexo Hora Fumador Idiomas

1234 Lupicinio Servidor Servido 1954-11-23 M 16:24:52 null 3

Ver código fuente

Presta atención a los siguientes aspectos:

� – En la sentencia no se alude al campo Contador. La razón es que se trata un campo AUTOINCREMENTAL y en ese tipo de campos los valores de los registros se escriben automáticamente cada vez que se añade uno nuevo.

� – El registro no se añadiría si el valor de DNI coincidiera con otra ya existente en la tabla. Recuerda que habíamos definido ese campo como índice primario. Si no hubiéramos incluido el aviso de error no tendríamos ninguna referencia sobre el éxito de la inserción y no detectaríamos el problema de duplicidad. Sencillamente ocurriría que el registro no se añadiría pero no nos enteraríamos de tal circunstancia.

� – En el tratamiendo del campo Fumador definido en MySQL con longitud cero y que solo permite como valores NULL o una cadena vacía es válido todo lo comentado en esta página. Lo transcribimos literalmente un poco más abajo.

El error PDO generado al producirse una coincidencia de valores en un campo único tiene el código 19. En este ejemplo puedes la manera de utilizarlo para depurar el mensaje de advertencia.

Ver código fuente

Añadir un registro a partir de datos contenidos en variables

También es posible añadir registros a partir de valores contenidos en variables PHP. Esta opción es, sin ninguna duda, la más utilizada, ya que lo habitual será escribir el contenido a añadir en un form y después –a través del method (POST o GET)– pasar al script de inserción esos valores como variables PHP.

Aquí tienes el código fuente de un ejemplo con la tabla anterior.

Ver código fuente

Quizá te resulten de alguna utilidad estos comentarios:

� – Observa en el código fuente que al insertar las variables en los VALUES de la sentencia MySQL ponemos cuando se trata de valores tipo cadena '$variable' (el nombre de la variable entre comillas) y cuando se trata de valores númericos sin comillas.

� – Si quieres introducir el valor NULL en un campo tipo VAR(0) define la variable así: $variable="NULL" y si quieres introducir unacadena vacía defínela de este otro modo:$var="''" -comillas dobles (") seguidas de dos comillas sencillas (') y para terminar otras comillas dobles (") y cuando hagas alusión a esa esta variable como un VALUE en la sentencia de inserción no la pongas entre comillas.

Añadir datos mediante formulario

Creación del formulario

El caso más frecuente –casi el único– es que los registros de una tabla se añadan utilizando un formulario y enviando desde él los datos a un script PHP que ejecute la opción de añadir. Si no recuerdas el funcionamiento de este método pulsa aquí

En el ejemplo hemos desarrollado un formulario para añadir registros a la tabla demo4 idéntico al descrito para MySQL. Su código fuente está en este enlace.

Ver código fuente

Page 450: Manual PHP

Añadir nuevo registro con datos del formulario

Tampoco existen grandes diferencias con lo dicho para MySQL en cuanto a la forma de interpretar los datos recibidos a través del formulario. Aquí tienes –comentado– el script que efectúa la inserción de datos en las tablas de ambos tipos.

Añadir registros mediante formulario

Código fuente del script

Tablas para pruebas

Tal como hacíamos cuando estudiábamos MySQL aquí tienes comentado un script que permite agregar aleatoriamente y de formaautomática una serie de registros para pruebas a las tablas demo4 de la base de datos ejemploSQLite tanto en formato SQLite como en MySQL.

Ver código fuente

PDO – Consultas SQLite / MySQL

Algunos ejemplos de consultas

Las consultas de los datos y registros contenidos en una tabla ofrecen un amplísimo abanico de posibilidades a partir de las opciones ya descritas en páginas anteriores para las tablas MySQL. Salvo pequeños detalles que iremos comentando a lo largo de los sucesivos ejemplos la sintaxis y los resultados son muy similares entre las consultas a tablas SQLite y MySQL. Repetiremos las consultas ya comentadas anteriormente para estas nuevas tablas creadas y manejadas mediante objetos PDO.

La consulta más simple

Si utilizamos la sentencia

SELECT * FROM tabla

obtendremos información sobre todos los campos (*) y la salida estará en el mismo orden en el que fueron añadidos los datos. Si visualizas este ejemplo, verás que aparecen ordenados por el valor autonumérico del campo Contador lo cual, como ves, resulta coherente con la afirmación anterior.

La única diferencia entre los resultados de la misma consulta en ambas bases de datos estaría en la presentación del campo autonúmerico. En el caso de MySQL aparece con ceros a la izquierda dado que al definir la tabla establecimos TINYINT(8) UNSIGNED ZEROFILL mientras que en caso de SQLite estuvimos obligados configurar ese campo autoincremental como INTEGER PRIMARY KEY.

Si observas el código fuente, en el caso del campo Idioma hemos tenido que hacer un pequeño arreglo en la consulta SQLite para visualizar los mismos datos que en MySQL. La razón es la no admisión de campos tipo SET ni ENUM por parte de SQLite.

Ejecutar consulta Ver código fuente

Consultando sólo algunos campos

Ahora utilizaremos la sentencia

SELECT campo1,campo2, ... FROM tabla

y tendremos como resultado una lista completa, por el mismo orden que la anterior, pero sólo mostrando los campos indicados.

Ejecutar consulta Ver código fuente

Page 451: Manual PHP

La respuesta es idéntica en ambas bases de datos.

¡Cuidado! En los comentarios contenidos en estos ejemplos puedes ver la forma en la que mysql_fetch_row ymysql_fetch_array tratan los índices escalares de los resultados que producen los SELECT de MySQL. Los valores de los índices se asignan a los contenidos de los campos por el mismo orden en el que estos se escriben en la sentencia SELECT. El campo1 (primero que se escribe) será recogido por el elemento de índice cero del array, el campo2 será recogido con índice uno y así sucesivamente

Consultando sólo algunos campos y limitando la salida a n registros

Ahora utilizaremos la sentencia

SELECT campo1,campo2, ... FROM tabla LIMIT (n, m)

y tendremos como resultado una lista que contendrá m registros a partir del n+1, por el mismo orden que la anterior, y mostrando los campos indicados.

Ejecutar consulta Ver código fuente

La respuesta es idéntica en ambas bases de datos.

Consultando sólo algunos campos y ordenando la salida

Utilizaremos la sentencia MySQL de esta forma

SELECT campo1,campo2, ... FROM tabla ORDER BY campo_n [ASC|DESC], campo_m [ASC|DESC]

y tendremos como resultado una lista ordenada por el primero de los campos indicados en ORDER BY, y en caso de coincidencia de valores en ese campo, utilizaríamos el criterio de ordenación señalado en segundo lugar.

Ejecutar consulta Ver código fuente

La respuesta es idéntica en ambas bases de datos.

Consulta seleccionando registros

Utilizaremos la sentencia MySQL de esta forma

SELECT campo1, ... FROM tabla WHERE condición

que nos devolverá la lista de registros que cumplen la condición indicada. Aquí tienes un ejemplo muy sencillo.

Ejecutar consulta Ver código fuente

La respuesta es idéntica en ambas bases de datos.

Consultas condicionadas

La claúsula WHERE permite un variado abanico de condiciones, que trataremos de resumir aquí. Las opciones señaladas con (*) tiene alguna diferencia de comportamiento. En el propio ejemplo o en llamada a pie de tabla tienes comentadas sus causas.

Operador Tipo Sintaxis Descripción Código Ver

Page 452: Manual PHP

de campo fuente ejemplo

= Numérico WHEREcampo=num Selecciona los registros que contienen en el campo un valor igual a num Ver Probar

= Cadena WHEREcampo="cadena" Selecciona los registros que contienen en el campo una cadena idéntica a cadena (*)

Ver Probar

< Numérico WHEREcampo<num Selecciona los registros que contienen en el campo un valor menor a num Ver Probar

< Cadena WHEREcampo<"cadena" Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmenores que los de la cadena, siendo n el número de caracteres que contiene cadena. (**)

Ver Probar

<= Numérico WHEREcampo<=num Selecciona los registros que contienen en el campo un valor menor O igual a num

Ver Probar

<= Cadena WHEREcampo<="cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmenores que los de la cadena, siendo n el número de caracteres que contiene cadena y añade respecto al caso anterior la opción de que en caso de que ambos valores fueran iguales también los presentaría (**)

Ver Probar

> Numérico WHEREcampo>num Selecciona los registros que contienen en el campo un valor mayor a num Ver Probar

> Cadena WHEREcampo>"cadena" Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmayores que los de la cadena, siendo n el número de caracteres que contiene cadena. (**)

Ver Probar

>= Numérico WHEREcampo>=num Selecciona los registros que contienen en el campo un valor mayor o igual a num Ver Probar

>= Cadena WHEREcampo>="cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres sonmayores que los de la cadena, siendo n el número de caracteres que contiene cadena y añade respecto al caso anterior la opción de que en caso de que ambos valores fueran iguales también los presentaría (**)

Ver Probar

IN Numérico o Cadena

WHERE campoIN (valor1,valor2..)

Selecciona los registros que contienen en el campo valores que coinciden con alguno de los especificados dentro del paréntesis. Cuando se trata de valores no numéricoz han de ir entre comillas

Ver Probar

BETWEEN Numérico o Cadena

WHERE campoBETWEEN valor1 AND valor2

Selecciona los registros en los que los valores contenidos en el campo seleccionado están comprendidos en el intervalo valor1 (mínimo) – valor2 (máximo) incluyendo en la selección ambos extremos. Cuando los contenidos de los campos son cadenas sigue los mismos criterios que se indican para los demás operadores de comparación

Ver Probar

IS NULL Cadena WHERE campo IS NULL Selecciona los registros en los que los valores contenidos en el campo seleccionado son NULOS

Ver Probar

IS NOT NULL

Cadena WHERE campo IS NOT NULL Selecciona los registros en los que los valores contenidos en el campo seleccionado son NO NULOS

Ver Probar

(*) Cuando se trata de cadenas de caracteres, el concepto menor que significa anterior en la ordenación de los caracteres según su código ASCII y mayor quesignifica posterior en esa misma ordenación. (**) La discriminación de Mayúsculas/Minúsculas dependerá del tipo de campo. Recuerda que en MySQL los tipo BLOB hacen esa discriminación, mientras que los de tipo TEXT son insensibles a mayúsculas/Minúsculas y también a vocales con y sin tilde. SQLite siempre toma en consideración esas diferencias.

Cuando se trata de comparar cadenas MySQL dispone de una potente instrucción (LIKE) que permite establecer los criterios de selección a toda o parte de la cadena. Su sintaxis contempla distintas posibilidades utilizando dos comodines>: % (que se comporta de forma similar al (*) en las búsquedas de Windows) y _ (de comportamiento similar a (?) en Windows). Aquí tienes algunas de sus posibilidades:

Sintaxis Descripción Código fuente

Ver ejemplo

WHERE campoLIKE '%cadena%'

Selecciona todos los registros que contengan la cadena en el campo indicado sea cual fuere su posición Ver Probar

WHERE campoLIKE 'cadena%'

Selecciona todos los registros en los que el campo indicado que contengan la cadena exactamente al principio del campo

Ver Probar

WHERE campoLIKE '%cadena'

Selecciona todos los registros en los que el campo indicado que contengan la cadena exactamente al final del campo Ver Probar

WHERE campoLIKE '_cadena%'

Selecciona todos los registros en los que el primer caracter del campo puede ser cualquiera pero los siguientes han de serexactamente los indicados en cadena pudiendo ir seguidos de cualesquiera otros caracteres

Ver Probar

El comodín (_) puede ir tanto al principio como al final y puede repetirse tantas veces como sea necesario. Por tanto sería correctoLIKE '___es%' y también LIKE 'a___es%' así como: LIKE '%a___es'.

Page 453: Manual PHP

La claúsula WHERE aun tiene más opciones. Acepta múltiples condiciones vinculadas por los operadores lógicos AND, OR, NOT o sus sintaxis equivalentes: &&, || y !.

El comportamiento de estos operadores es idéntico al descrito para sus homónimos de PHP en esta página

Utilizando funciones sobre campos

Por medio de la sintaxis

SELECT MAX(campo1), MIN (campo2), ... FROM tabla

obtendríamos UNA SOLA FILA cuyos valores serían los resultados de la aplicación de las funciones a todos los registros del campo indicado. Aquí tienes un ejemplo que determina todos los valores de esos estadísticos aplicados al campo Contador de nuestra tabla demo4.

Ejecutar consulta Ver código fuente

Observa que en el código fuente hemos incluido dos consultas distintas y que la consulta MySQL devuelve un valor más que SQLite. Se debe a que la función STDDEV (desviación típica de la población) no está definida en SQLite.

Aplicando la opción GROUP BY

Tal como señalamos al margen, las funciones anteriores pueden aplicarse a grupos de registros seleccionados mediante un criterioGROUP BY (nombre del campo)

En este ejemplo obtendremos los mismos parámetros estadísticos que en el anterior, pero ahora agrupados por sexo, lo que significaría que obtendremos dos filas de resultados. Aquí tienes el ejemplo

Ejecutar consulta Ver código fuente

Es válido el comentario del caso anterior para STDDEV (desviación típica de la población) que no está definida en SQLite.

Manejo de fechas en las consultas

MySQL dispone de algunas cláusulas de gestión de fechas que pueden tener una gran utilidad a la hora de gestionar consultas. En el caso de SQLite las cosas son algo distintas. No dispone de tales herramientas sin embargo si posee algunas funciones que permiten obtener resultados similares. En los ejemplos utilizamos las siguientes:

substr(campo,posicion_inicial,numero_caracteres)

extrae del contenido del campo de la tabla indicado el numero_caracteres comenzando a partir del que ocupa la posicion_inicial. Siposicion_inicial es un número negativo la determinará contando los caracteres contenidos en el campo de derecha a izquierda.

Formato Descripción Sintaxis Ver código Ver ejemplo

%d Día del mes en formato de dos dígitos DATE_FORMAT(Nacimiento,'%d') Ver Probar

%e Día del mes en formato de uno ó dos dígitos DATE_FORMAT(Nacimiento,'%e') Ver Probar

%m Número del mes en formato de dos dígitos DATE_FORMAT(Nacimiento,'%m') Ver Probar

%c Número del mes en formato de uno o dos dígitos DATE_FORMAT(Nacimiento,'%c') Ver Probar

%y Número del año en formato de dos dígitos DATE_FORMAT(Nacimiento,'%y') Ver Probar

%Y Número del año en formato de cuatro dígitos DATE_FORMAT(Nacimiento,'%Y') Ver Probar

Creación de tablas a partir de la consulta de otra tabla

Reiteramos aquí lo ya comentado cuando estudiábamos MYSQL. Es frecuente -podría decirse que es lo habitual- relacionar tablasmediante campos con idéntico contenido. Supongamos que entre los individuos de nuestra tabla demo4 se pretende establecer un proceso de selección para elegir entre ellos un número determinado de astronautas, pongamos por caso.

Page 454: Manual PHP

Supongamos también, que la selección va a constar de tres pruebas que serán juzgadas y calificadas por tres tribunales distintos.

Una primera opción sería crear tres tablas –una para cada tribunal– e incluir en ellas todos los datos de cada uno de los individuos. Esa opción es factible pero no es ni la más cómoda, ni tampoco es la más rápida ni la que menos espacio de almacenamiento necesita. No debemos olvidar que una tabla puede tener una enorme cantidad de registros.

Una opción alternativa sería crear tres nuevas tablas que sólo contuvieran dos campos cada una. Por ejemplo el campo DNI y el campo Calificación. Como quiera que el campo DNI ha de contener los mismos valores en las cuatro tablas y además es un campo únicopodrían crearse las nuevas tablas y luego copiar en cada una de ellas todos los DNI de la tabla original. Nos garantizaría que no habría errores en los DNI y además nos garantizaría que se incluyeran todos los aspirantes en esas nuevas tablas.

Aquí tienes el código fuente de un script que crea esas tres tablas (a las que hemos llamado prueba1, prueba2 y prueba3.

Ver código fuente

Una consulta conjunta de varias tablas

MySQL permite realizar consultas simultáneas en registros situados en varias tablas.

Para ese menester se usa la siguiente sintaxis:

SELECT tabla1.campo1, tabla2.campo2, ... FROM tabla1, tabla2

en la que, como ves, modificamos ligeramente la sintaxis ya que anteponemos el nombre de la tabla al del campo correspondiente separando ambos nombres por un punto, con lo cual no hay posibilidad de error de identificación del campo incluso cuando campos de distinta tabla tengan el mismo nombre. Otra innovación -respecto a los ejemplos anteriores- es que detrás de la cláusula FROM escribimos los nombres de todas las tablas que está usando SELECT. A partir de ahí se pueden establecer todo tipo de relaciones para las sentencias WHERE, ORDER BY y GROUP BY utilizando para ellocampos de cualquiera de las tablas sin otra particularidad más que poner cuidado al aludir a los campos utilizando siempre la sintaxisnombre_tabla.nombre_campo. A modo de ejemplo –hemos procurado comentarlo línea a línea– aquí tienes un script PHP que hace una consulta conjunta de las tablasdemo4, demodat1, demodat2 y demodat3 y nos presenta una tabla con los datos personales y las puntuaciones de las tres pruebas así como las suma de puntos de las tres y, además, ordena los resultados -de mayor a menor- según la suma de las tres puntuaciones.

Ejecutar consulta Ver código fuente

Declaraciones preparadas (Prepare statement)

La clase PDO dispone de una alternativa al método query que conviene conocer. Requiere realizar el proceso de consulta en dos pasos que utilizan los métodos siguientes:

$actuacion= $objeto->prepare(SENTENCIA)

El método prepare prepara la SENTENCIA para su ejecución devolviendo el resultado como un objeto $actuacion. Este método no ejecuta la sentencia, solo la prepara.

$actuacion-> execute()

El método execute es propio de los objetos resultantes de la $actuacion de una sentencia. Devuelve el objeto resultado de la ejecución de esa sentencia. Este objeto permite extraer y organizar los resultados mediante métodos como estos:

Page 455: Manual PHP

$recuento=$actuacion-> rowCount()

También rowCount es un método propio de los objetos resultantes de una $actuacion . La variable recuento contiene el número de registros afectados por la ejecución de esa sentencia.

Cuando se trata de sentencias que devuelven resultados (caso típico de las consultas) pueden utilizarse algunos métodos que facilitan la organización de la información. Los más útiles pueden ser estos:

$resultado=$actuacion-> fetch(PDO::FETCH_NUM )

aplicando el método fetch al objeto que resulta de execute ($actuacion) se recoge el $resultado en un array escalar cuyo primer índice es cero y que sigue el mismo orden por el que los campos han sido incluidos en la consulta en el registro actual.

$resultado=$actuacion-> fetch(PDO::FETCH_ASSOC )

Cuando se incluye FETCH_ASSOC el resultado será un array asociativo cuyos índices coinciden con los nombres de los campos incluidos en la consulta en el registro actual.

$resultado=$actuacion-> fetch(PDO::FETCH_BOTH )

FETCH_BOTH es el valor por defecto. Devuelve un array asociativo indexado por los nombres de los campos y otro escalar indexado (a partir de cero) por las posiciones de los campos en la sentencia SELECT en el registro actual.

$resultado=$actuacion-> fetchAll(PDO::FETCH_BOTH )

Sentencias que devuelven más de un resultado

Cuando una sentencia produce como resultados varios registros –caso habitual en las consultas– las opciones anteriores no ofrecen los resultados esperados. En esos supuestos es necesaria las siguiente modificación:

� – Hay que modificar el método fetch agregando el siguiente parámetro: $resultado=$actuacion-> fetch(PDO::FETCH_BOTH, PDO::FETCH_ORI_NEXT )

de esta forma ya estaremos en condiciones de construir esa consulta y efectuar la lectura de los resutados por medio de un bucle tal como puedes ver en este ejemplo.

<?php include('conecta.inc.php'); $dbMySQL= conecta('ejemploSQLite','MySQL'); $dbSQLite= conecta('ejemploSQLite','SQLite'); $tabla="demo4"; $query= "SELECT Nombre, Apellido1, Apellido2 FROM $tabla WHERE Nombre='Dorotea'"; /* preparamos la consulta*/ $consulta=$dbSQLite->prepare($query); /* ejecutamos la consulta */ $consulta->execute(); print "<br><br>La consulta SQLite ha producido los siguientes resultados<br><br>"; /* creamos un bucle que irá recorriendo los resultados de la consulta como consecuencia de haber agregado a fecth el valor PDO::FETCH_ORI_NEXT */ while($resultado=$consulta->fetch(PDO::FETCH_ASSOC,PDO::FETCH_ORI_NEXT)){ print $resultado['Apellido1']." ".$resultado['Nombre']."<br />"; } /* repetimos el mismo proceso anterior, esta vez para el objeto MySQL */ $consulta=$dbMySQL->prepare($query); $consulta->execute(); print "<br><br>La consulta MySQL ha producido los siguientes resultados<br><br>"; while($resultado=$consulta->fetch(PDO::FETCH_ASSOC,PDO::FETCH_ORI_NEXT)){ print $resultado['Apellido1']." ".$resultado['Nombre']."<br />";

Page 456: Manual PHP

} ?>

Realizar consultas

Otra posibilidad, alternativa a la anterior, nos la ofrece el siguiente método PDO:

$resultado=$actuacion-> fetchAll()

dónde $resultado es un array bidimensional que contiene el resultado de la ejecución de una sentencia previamente preparada mediante $actuacion=$objeto->prepare(SENTENCIA) y ejecutada por medio de $actuacion->execute().

El primer índice del array es de tipo escalar (contiene el número de orden, empezando en cero, de cada registro leído) El segundo índice ofrece dos posibilidades. Una de carácter escalar (siendo su valor el número de orden, partiendo de cero, del nombre del campo en la sentencia que crea la consulta) y otra asociativa siendo el nombre del índice identico a nombre del campo o campos sobre los que se realiza la consulta.

<?php include('conecta.inc.php'); $dbMySQL= conecta('ejemploSQLite','MySQL'); $dbSQLite= conecta('ejemploSQLite','SQLite'); $tabla="demo4"; $query= "SELECT Nombre, Apellido1, Apellido2 FROM $tabla WHERE Nombre='Dorotea'"; /* preparamos y ejecutamos la consulta */ $consulta=$dbSQLite->prepare($query); $consulta->execute(); print "<br><br>La consulta SQLite ha producido los siguientes resultados<br><br>"; /* mediante fetchall nos devolverá un array bidimensional con todos los resultados*/ $resultado=$consulta->fetchAll(); /* el bucle nos recorrerá los primeros indices y extraeremos los valores de los elementos deseados de cada uno de esos indices */ for ($i=0;$i<sizeof($resultado);$i++){ print $resultado[$i]['Nombre']." ".$resultado[$i]['Apellido1']."<br>"; } /* repetimos la misma consulta ahora con el objeto MYSQL*/ $consulta=$dbMySQL->prepare($query); $consulta->execute(); print "<br><br>La consulta MySQL ha producido los siguientes resultados<br><br>"; $resultado=$consulta->fetchAll(); for ($i=0;$i<sizeof($resultado);$i++){ print $resultado[$i]['Nombre']." ".$resultado[$i]['Apellido1']."<br>"; } ?>

Realizar consultas

La tabla sqlite_master

Auque en algunas situaciones puntuales –las transacciones de las que hablaremos más adelante– será necesario el uso de un fichero temporal llamado rollback journal, la gestión de una base de datos SQLite puede hacerse mediante un único archivo llamado archivo principal de la base de datos.

Ese archivo principal está formado varias por partes iguales, llamadas páginas, de tamaños comprendidos entre 512 y 65536 bytes. El número de páginas máximo puede ser 231 - 2 lo cual significa que un fichero de base de datos SQLite podría llegar a tener (considerando todas las páginas posibles y el tamaño máximo de estas) una dimensión de 140 terabytes.

En la sección de encabezado del archivo principal de base de datos está la tabla sqlite_master, gestionada de forma automática, que contiene la información sobre la ubicación de las restantes tablas (número de página en el que están situadas), índices, view y trigger contenidos en la base de datos. Se crea de forma automática mediante una sentencia similar a esta:

Page 457: Manual PHP

CREATE TABLE sqlite_master( type text, name text, tbl_name text, rootpage integer, sql text )

En ella, el campo type de esta tabla recoge, dependiendo del tipo de objeto utilizado, uno de los siguientes valores: 'table', 'index', 'view'o 'trigger'. El campo name contendrá el nombre del elemento(indice, consulta, trigger o tabla). En tbl_name se recoge el nombre de la tabla a la que está asociado el objeto (si el elemento es un índice sería el nombre de la tabla a la que va asociado). Por último, en rootpage se indica el número de página en la que comienza el objeto y el campo de texto sql suele incluir el texto de la sentencia utilizada para la creación del objeto.

Todas las tablas de una base de datos SQLite, están registradas en sqlite_master.

Una consulta como esta nos permite visualizar su contenido.

<?php include('conecta.inc.php'); $dbSQLite= conecta('ejemploSQLite','SQLite'); /* Escribimos una consulta todos los datos de la tabla correspondiente a registros tipo table */ $llamadaSQLite = "SELECT * FROM sqlite_master WHERE type='table'"; if($resultado=$dbSQLite->query($llamadaSQLite)){ print "La tabla sqlite_master contiene los siguientes registros <br>"; foreach ($resultado as $registro=>$contenido){ print "<b>Tabla del registro n.º: ".$registro."</b><br />"; foreach ($contenido as $campo=>$valor){ if (!is_int($campo)){ print "<i>Campo: </i>".$campo; print "<i> Valor: </i>".$valor."<br>"; } } } } else{ print "Ha habido un error de PDO: <br />" ; echo "<pre>"; print_r($dbSQLite->errorInfo()); echo "</pre>"; } ?>

ejemplo529.php

Si ejecutas el ejemplo anterior tal vez te encuentres con una tabla llamada sqlite_sequence en cuya estructura existen únicamente dos campos: name y seq. Esta tabla de crea de forma automática cuando alguna de las tablas que componen la base de datos incluye un campo autoincremental. Será en esta tabla donde se recogan los últimos valores de estos campos en cada una de las tablas que los requieran.

Modificación de la estructura de una tabla

Las modificaciones de tablas utilizan la sentencia ALTER TABLE complementada con las palabras reservadas ADD cuando se trata de agregar un nuevo campo. Si la modificación conlleva la eliminación de un campo el proceso será un poco más complejo ya que habría que hacerlo según el siguiente proceso:

1.º Crear una nueva tabla cuyo diseño incluya los nombres de campos que pretendemos mantener. 2.º Copiar los datos de la tabla actual a la nueva tabla.

Page 458: Manual PHP

3.º Eliminar la tabla antigua. 4.º Cambiar el nombre a la nueva tabla asignándole el de la recién borrada.

Renombrar una tabla

Se puede cambiar el nombre de una tabla utilizando la sentencia:

ALTER TABLE nombre_de_la_tabla RENAME TO nuevo_nombre

dónde el nombre actual de la tabla es nombre_de_la_tabla y el futuro nuevo_nombre

Borrar una tabla

Las sentencias MySQL que permiten borrar una tabla son las siguientes:

DROP TABLE nombre de la tabla o DROP TABLE IF EXISTS nombre de la tabla

la diferencia entre ambas radica en que usando la segunda no se generaría ningún error en el caso de que tratáramos de borrar una tabla inexistente.

PDO – Modificaciones SQLite / MySQL

Sintaxis SQLite de modificación de registros

Las sentencias SQLite que permiten la modificación de registros en las tablas son prácticamente iguales a las comentadas para MySQL e igual que aquella existen cláusulas obligatorias y opcionales.

El orden en que deben estar indicadas ha de seguir la misma secuencia en la que están descritas aquí.

� UPDATE Tiene carácter obligatorio, debe ser la primera palabra de la sentencia e indica a SQLite que vamos realizar una modificación

� tabla Es obligatoria y contiene el nombre de la tabla que pretendemos modificar.

� SET Tiene carácter obligatorio y debe estar delante de las definiciones de campo y valor.

� campo1 = valor1 Es obligatoria al menos una definición. Indica el nombre del campo a modificar (campo1) y el valor que se asignará a ese campo. Si se pretende modificar más de un campo se repetirá esta definición tantas veces como sea necesario, separando cada una de ellas por una coma.

� WHERE Es un campo opcional y su comportamiento es idéntico al señalado al mencionar el proceso de consultas.

� ORDER BY Tiene idéntica funcionalidad a la descrita al referirnos a consultas

Modificar un campo en todos los registros de una tabla

La sentencia SQLite, que permite modificar uno o varios campos en todos los registros de una tabla, es la siguiente:

UPDATE tabla SET campo1=valor1, campo2=valor2

¡Cuidado!

Page 459: Manual PHP

Hay que tener muy presente que con esta sentencia –en la que no aparece WHERE– se modificarán TODOS LOS REGISTROS DE LA TABLA y por lo tanto los campos modificados tendrán el mismo valor en todos los registros.

Algunas consideraciones sobre la sintaxis

Siempre que manejes PHP y SQLite debes tener muy presente todo lo ya comentado para el caso de MySQL que repetimos aquí para mayor comodidad de lectura.

� – SQLite requiere SIEMPRE que los valores tipo cadena que incluyen campos de fecha vayan entre comillas. Por el contrario, losnuméricos no deben llevar comillas.

� – Presta mucha atención a esto cuando escribas los valores directamente en la sentencia SQLite.

� –Cuando pases valores desde una variable PHP debes tener muy en cuenta las consideraciones anteriores y si el contenido de la variable es una cadena que va a ser tratada como tal por SQLite tienes dos opciones para evitar el error:

� • Definir la variable así: $variable =" 'valor'" (comillas dobles, comilla simple al principio y comilla simple, comilla doble al final) y poner en la sentencia MySQL el nombre de la variable sin entrecomillar ($variable), o

� • Definir la variable PHP así: $variable =" valor" y al escribir el nombre de esa variable en la sentencia MySQL escribirlo entrecomillas sencillas, es decir, así: '$variable'

� – No pienses que es caprichoso el orden que hemos puesto en las comillas. Recuerda que al llamar a la sentencia MySQL, el contenido de la sentencia va entre comillas (que por costumbre son comillas dobles, por esa razón todo entrecomillado que vaya dentro de esa sentencia ha de usar comillas simples para evitar un error seguro). De ahí que al definir una variable PHP en la forma $variable =" 'valor'" las comillas dobles exteriores indican a PHP que se trata de una cadena, por lo que, al pasar la variable a MySQL éste recibirá el contenido de la cadena que es, logicamente: 'valor' y en este caso las comillas forman parte del valor, razón por el que no es necesario escribir -en la sentencia MySQL- el nombre de la variable entrecomillado.

En este primer ejemplo, hemos incluido una actualización de tablas que pondrá 24 puntos de calificación en la primera de las pruebas a todos los aspirantes a astronautas de nuestro ejemplo. Es un caso de actualización sin la condición WHERE y tiene el código comentado.

Ejecutar la modificación Ver código fuente

Tal como puedes ver, en el ejemplo utilizamos una preparación declarada (prepare statement) como procedimiento alternativo para modificar la calificación anterior y pasar a SIETE puntos la nota en todos los registros.

<?php /* incluimos la funcion conecta y creamos objetos de ambas bases de datos */ include('conecta.inc.php'); $dbMySQL= conecta('ejemploSQLite','MySQL'); $dbSQLite= conecta('ejemploSQLite','SQLite'); /* escribimos la consulta que actualizará a 7 el campo Puntos de la tabla prueba1 */ $consulta = "UPDATE prueba1 SET Puntos=7"; /*preparamos la consulta de la tabla SQLite*/ $actualiza = $dbSQLite->prepare($consulta); /* aplicamos el método execute al objeto creado por el método anterior */ $actualiza->execute(); /* leemos el numero de registros afectados por la ejecución de la sentencia */ $numero_resultados = $actualiza->rowCount(); /* visualizamos la información del resultado */ print("Se han actualizado $numero_resultados registros"); /* repetimos exactamente el proceso anterior preparando un objeto MySQL*/ $actualiza = $dbMySQL->prepare($consulta); $actualiza->execute(); $numero_resultados = $actualiza->rowCount(); print("Se han actualizado $numero_resultados registros"); ?>

Page 460: Manual PHP

Ejecutar la modificación Ver código fuente

Selección y modificación de un solo registro

Es una de las opciones más habituales. Es el caso en el que –mediante un formulario– asignamos una condición a WHERE y simultáneamente asignamos los nuevos valor del campo o campos elegidos. Requiere la siguiente sintaxis:

UPDATE tabla SET campo1=valor1, campo2=valor2 WHERE condición

La condición es fundamental en esta opción y normalmente aludirá a un campo índice (clave principal o única), de modo que sea un solo registro el que cumpla la condición. Podría ser el caso, en nuestro ejemplo, del campo DNI que por su unicidad garantizaría que la modificación solamente va a afectar a uno solo de los registros.

El ejemplo siguiente nos permitirá hacer modificaciones de este tipo en la tabla prueba2. Observa el código fuente y verás hacemos una primera consulta utilizando prepare y execute y que la posterior inclusión de la modificación la efectuamos utilizando query.

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Modificación simúltanea de un campo en cualquiera de los registros

Aquí tienes un ejemplo que permite visualizar el valor actual de todas las puntuaciones de la prueba 1 de los astronautas así como sus nombres y apellidos y DNI y en la cual se pueden modificar ninguno, uno, varios o todos los valores y posteriormente actualizarlos todos con los nuevos valores.

En este caso hemos separado los scripts buscando una mejor visualización de los resultados. Los scripts, tal como podrás ver si observas los código fuente, son practicamente idénticos. la única y lógica diferencia es que en el primer caso creamos objetos SQLite y en el segundo MySQL.

Ejecutar la modificacción en SQLite Ver código «formulario» Ver código «script»

Ejecutar la modificacción en MySQL Ver código «formulario» Ver código «script»

Sintaxis SQLite para borrado de registros

La sintaxis SQLite para las sentencia de borrado de registros de una tabla puede contener las siguientes cláusulas que, al igual que ocurría en casos anteriores, pueden tener categoría de obligatorias u opcionales. La secuencia en la que deben estar indicadas en la sentencia es idéntica al orden en que están descritas aquí.

� DELETE Tiene carácter obligatorio. Debe ser la primera palabra de la sentencia e indica a SQLite que tratamos de borrar uno o másregistros.

� tabla Es obligatoria e indica el nombre de la tabla en la que pretendemos efectuar el borrado o eliminación de los registros.

� WHERE Es un campo opcional y su comportamiento es idéntico al señalado en al mencionar el proceso de consultas.

Borrar todos los registros de una tabla La sentencia SQLite que permite borrar todos los registros de una tabla es la siguiente: DELETE FROM tabla

Ten muy presente que con esta sentencia -en la que no aparece WHERE- se BORRARÁN TODOS LOS REGISTROS DE LA TABLA.

Respecto a otras posibles opciones, no difiere en nada de lo indicado en la página anterior. Simplemente habría que sustituir en aquellos script UPDATE por DELETE. Borrar un registro no es otra cosa que un caso particular de modificación.

Page 461: Manual PHP

Integridad referencial tras el borrado de una tabla

¿Recuerdas el ejemplo de las pruebas de selección de astronautas? ¿Recuerdas que las tres tablas de puntuaciones habían sido creadas a partir de la tabla de datos de los aspirantes? ¿Qué ocurriría si borrásemos uno o varios registros de una de ellas? ¿Qué ocurriría se después de crear esas tablas añadiésemos nuevos aspirantes a la lista de candidatos?

Es obvio que si no hacemos algo para evitarlo se perdería la integridad referencial –la relación uno a uno– entre los registros de esas tablas.

Ocurriría que no todos los individuos que están incluidos en una de esas tablas lo estarían en las demás y por tanto, al ejecutar consultas o modificaciones posteriores correríamos el riesgo de que se produjeran errores.

Esa situación es fácilmente evitable modificando ligeramente los scripts con los que se realizan los procesos de altas y bajas. Bastaría con añadirles algunas sentencias que cada vez que se efectúa un alta o baja en el fichero de datos personales efectúen el mismo proceso en todos los demás ficheros relacionados con aquel.

Aquí tienes comentado el código fuente de la modificación añadida al script que registra los nuevos aspirantes en el fichero de altas de la tabla demo4. Con esta modificación se actualizarían automáticamente los ficheros prueba1, prueba2 y prueba3 cada vez que se añadiera un nuevo aspirante.

El formulario no requiere ninguna modificación, Sólo es necesario realizar los cambios en el script que realiza la inserción.

Añadir nuevo aspirante Ver código «formulario» Ver código «script»

Hecho este inciso continuaremos con la referencia al borrado de registros.

En este ejemplo, tienes el código fuente de un script que realiza el borrado de un registo –mediante un formulario en el que se inserta el DNI– tanto en la tabla demo4 como demodat1, demodat2 y demodat3 manteniendo la integridad referencial entre los cuatro ficheros.

Eliminar un aspirante Ver código «formulario» Ver código «script»

Borrar registros seleccionándolos de una lista

En el ejemplo siguiente tienes el código para utilizar la cláusula WHERE en un proceso de borrado de registros que presenta un formulario que contiene una lista con todos los registros actuales y una casilla de verificación por cada uno.

Al marcar las casillas y enviar el formulario el script que recibe los datos procede al borrado de todos los registros marcados en todas la tablas afectadas.

Borrar aspirantes MySQL Ver código «formulario» Ver código «script»

Borrar aspirantes SQLite Ver código «formulario» Ver código «script»

Transacciones en tablas SQLite

Coméntabamos al tratar las tablas InnoDB en los temas relativos a MySQL que uno de los riesgos que se plantean en la gestión de bases de datos es que pueda producirse una interrupción del proceso mientras se está actualizando una o varias tablas.

Decíamos también que las transacciones evitan este tipo de situaciones ya que los datos se registran de manera provisional y no se consolidan hasta que una instrucción confirme que esas anotaciones tienen carácter definitivo.

Sintaxis de las transacciones

Existen tres sentencias muy similares a las de MySQL para gestionar las transacciones. Son las siguientes:

$objeto->query("BEGIN")

Page 462: Manual PHP

Indica a SQLite que en ese punto comienza una transacción. Todas las sentencias que se ejecuten a partir de ella tendrán carácterprovisional y no se concretarán de forma efectiva hasta que encuentre una sentencia que las confirme.

$objeto->query("ROLLBACK")

Mediante esta sentencia advertimos a SQLite que finaliza la transacción pero que no debe hacerse efectiva ninguna de las modificaciones incluidas en ella.

$objeto->query("COMMIT")

Esta sentencia advierte a SQLite que ha finalizado la transacción y que SÍ debe hacer efectivos todos los cambios incluidos en ella.

Ejecutar ejemplo Ver código fuente

Modificación de estructuras de tablas SQLite

Las opciones de modificación de estructuras de tablas SQLite se limitan a los tres tipos de actuaciones siguientes.

Borrar una tabla

Las sentencias MySQL que permiten borrar una tabla son las siguientes:

DROP TABLE nombre de la tabla DROP TABLE IF EXISTS nombre de la tabla

Las diferencia entre ambas radica en que usando la segunda no se generaría ningún error en el caso de que tratáramos de borrar una tabla inexistente.

Renombar una tabla

SQLite permite cambiar el nombre de una tabla haciendo uso de la sentencia siguiente:

ALTER TABLE nombre_actual RENAME TO nuevo_nombre

cambiaría el nombre_actual por el nuevo_nombre.

Agregar un nuevo campo

SQLite permite agregar un nuevo campo a una tabla ya existente mediante la sentencia:

ALTER TABLE tabla ADD COLUMN nuevo_campo

agrega el nuevo_campo a la tabla.

Borrar o modificar un campo

Cuando se pretende borrar un campo de una tabla o modificar su tipo SQLite no dispone de una sentencia específica que permita esas transformaciones. Para hacerlo deberíamos recurrir a un proceso un poco más complejo cuyos diferentes pasos son los siguientes:

� 1.º – Crear una nueva tabla cuyo diseño incluya los nombres de campos que pretendemos mantener.

� 2.º – Copiar los datos de la tabla actual a la nueva tabla.

� 3.º – Eliminar la tabla antigua.

� 4.º – Cambiar el nombre a la nueva tabla asignándole el de la recién borrada.

Page 463: Manual PHP

PDO – Seguridad en SQLite / MySQL

Inyección de código

El hecho de utilizar los recursos PHP Data Objects no excluye los riesgos de seguridad derivados de lo que llamábamos inyección de código cuando tratábamos de MySQL. Todo lo argumentado allí respecto a los riesgos y vulnerabilidades sigue teniendo plena vigencia tal como puedes ver en este ejemplo.

<?php include('conecta.inc.php'); $dbMySQL= conecta('ejemploSQLite','MySQL'); $dbSQLite= conecta('ejemploSQLite','SQLite'); $nombre="' or '34=34"; $tabla="demo4"; /* recogemos la cadena tal como viene en la condición WHERE. Por tratarse de una cadena tenemos que entrecomillar el nombre de la variable en esa cláusula */ $query= "SELECT Nombre, Apellido1, Apellido2 FROM $tabla WHERE Nombre='$nombre'"; /* esta consulta nos dará como resultado una lista de todos los registros de la tabla. Habremos vulnerado las mínimas reglas de seguridad */ /* primero con el objeto SQLite */ print "Esta es la condición de la consulta: ".$query."<br />"; print "<br /><i>Resultado de la consulta SQLite</i><br />"; if($resultado=$dbSQLite->query($query)){ print "Estos son los resultados de la consulta en la base de datos SQLite<br /><br />"; foreach ($resultado as $matriz){ print $matriz[1]." ".$matriz[2].", ".$matriz[0]."<br />"; } } /* Igual proceso el objeto MySQL */ print "<br /><i>Resultado de la consulta MySQL</i><br />"; if($resultado=$dbMySQL->query($query)){ print "Estos son los resultados de la consulta en la base de datos SQLite<br /><br />"; foreach ($resultado as $matriz){ print $matriz[1]." ".$matriz[2].", ".$matriz[0]."<br />"; } } ?>

Ver ejemplo

Como habrás podido observar una consulta con el código adecuado WHERE Nombre='' or '34=34' muestra la vulnerabilidad y devuelve todos los registros contenidos en la tabla por las mismas razones ya comentadas al estudiar MySQL.

PDO dispone de un método que palía en gran medida este tipo de riesgos. Se trata de:

$variable=$objeto->quote(cadena)

que recoge en $variable el contenido de la cadena incluyéndolo dentro de unas comillas simples'. De esa forma una cadena

como ' or '34=34 resultaría transformada en '' or '34=34'

<?php include('conecta.inc.php'); $dbMySQL= conecta('ejemploSQLite','MySQL'); $dbSQLite= conecta('ejemploSQLite','SQLite'); $nombre="' or '34=34";

Page 464: Manual PHP

/* aplicamos el método quote a la cadena con lo cual el nuevo valor de la variable \$nombre se entrecomillará de forma automatica */ $nombre=$dbSQLite->quote($nombre); $tabla="demo4"; /* ya no incluimos la variable \$nombre entre comillas. Ya se las ha asignado el método quote */ $query= "SELECT Nombre, Apellido1, Apellido2 FROM $tabla WHERE Nombre=$nombre"; print "Esta es la condición de la consulta: ".$query."<br />"; /* la consulta ahora ya no listará ninguno de los registros. Habremos mejorado la seguridad */ print "<br /><i>Resultado de la consulta SQLite</i><br />"; if($resultado=$dbSQLite->query($query)){ print "Estos son los resultados de la consulta en la base de datos SQLite<br /><br />"; foreach ($resultado as $matriz){ print $matriz[1]." ".$matriz[2].", ".$matriz[0]."<br />"; } } /* tambien en le caso de MySQL se producirá el resultado esperado */ print "<br /><i>Resultado de la consulta MySQL</i><br />"; if($resultado=$dbMySQL->query($query)){ print "Estos son los resultados de la consulta en la base de datos SQLite<br /><br />"; foreach ($resultado as $matriz){ print $matriz[1]." ".$matriz[2].", ".$matriz[0]."<br />"; } } ?>

Ver ejemplo

En cualquier caso los procedimientos anteriores no son los más aconsejables para la realización de consultas seguras. Lo aconsejable en estos casos es utilizar las declaraciones preparadas agregando algunas opciones a las ya comentadas.

A la sintaxis ya conocida:

$actuacion= $objeto->prepare(SENTENCIA) $actuacion-> execute()

puede hacérsele una modificación incluyendo un nuevo método (bindParam) que tiene como finalidad enlazar el valor de una variable con un identificador incluido en la sentencia SQL. Una sintaxis como esta:

$actuacion= $objeto->prepare('SELECT * FROM tabla WHERE (Nombre=? and Apellido1=?)') $actuacion-> bindParam(posicion, $variable,tipo, longitud); $actuacion-> execute()

en la que hacemos las siguientes modificaciones:

� – En la sentencia SQL sustituimos por ? lo que anteriormente eran nombres de variables (entre comillas por su condición de cadenas alfanuméricas) del tipo '$nombre' y '$apellido'.

� – Agregamos tantas llamadas a métodos bindParam como ? contiene la sentencia SQL incluida por método prepare

� – Las llamadas a los métodos bindParam requieren dos parámetros obligatorios y permiten otros dos opcionales.

� • El primero de ellos posicion indica la posición en la sentencia SQL del ? al que alude. Las posiciones se cuentan a partir de uno y de izquierda a derecha.

� • El señalado como $variable es el nombre de una variable previamente definida que contiene el valor que ha de sustituir al? aludido. Hemos de tener en cuenta que ha de incluirse siempre un nombre de variable y que si tratáramos de sustituirlo por un cadena que contenga su valor se producirá un error.

Page 465: Manual PHP

� • El parámetro tipo tiene carácter opcional e indicaría el tipo de contenido de la variable. Puede tener como valoresPDO::PARAM_STR o PDO::PARAM_INT (sin comilla). En el primer caso indicaría que la variable es de tipo cadena y en el segundo que es de tipo entero.

� • Cuando el tipo es PDO::PARAM_STR puede incluirse longitud que indicaría el número de caracteres de la cadena.

� – El método execute produciría los resultados ya comentados.

Veamos dos ejemplos. El primero de ellos utiliza el método bindParam( mientras que el segundo no lo hace.

Con bindParam Ver código fuente Sin bindParam Ver código fuente

Como habrás podido observar el uso de bindParam impidió la inyección del «código maligno». Desde las páginas oficiales de PHP se recomienda utilizar este método en la situaciones en la que se prevean riesgos de este tipo.

El método bindParam permite algunas modificaciones tales como estas:

$actuacion= $objeto->prepare('SELECT * FROM tabla WHERE (Nombre=:campo_nombre and Apellido1=:campo_apellido) ') $actuacion-> bindParam(':campo_nombre' , $variable,tipo, longitud); $actuacion-> execute()

Hemos sustituido cada ? de la sentencia SQL por una palabra cualquiera precedida de : y también el número de posicion (en la llamada al método bindParam) por una cadena (fíjate que va entre comillas) que incluye exactamente el mismo nombre –con los : obligatorios– incluido en la sentencia SQL. Puedes verlo en el ejemplo.

Ver nuevo ejemplo Ver código fuente

Aún disponemos de otra posibilidad alternativa a la anterior. Sería esta:

$actuacion= $objeto->prepare('SELECT * FROM tabla WHERE (Nombre=:campo_nombre and Apellido1=:campo_apellido) ') $actuacion-> execute(array(':campo_nombre'=>$variable))

En este caso hemos omitido la llamada al método bindParam y cmo alternativa hemos incluido en la llamada al método execute un array asociativo al que asignámos como índices los palabras de la sentencia (siempre precedidas por los dos puntos) y como valor los nombres de la variable que los contiene. Si en la sentencia utilizáramos ? el array habría de ser de tipo escalar. Puedes verlo en los ejemplos.

Ver nuevo ejemplo Ver código fuente Ver nuevo ejemplo Ver código fuente

Como habrás podido observar el «filtro de seguridad» ha funcionado en todos estos ejemplos. Los intentos de visualizar nombre mediante el código ' or '34=34 han fracasado.

La extensión MySQLi

La extensión MySQLI

Aunque no ofrece ninguna novedad respecto a otras interfaces de gestión de bases de datos MySQL mediante PHP, la extensiónMySQLi, o como a veces se le conoce, la extensión de MySQL mejorada, viene incluida en las versiones PHP 5 y posteriores y se desarrolló para aprovechar las nuevas funcionalidades que incluyen las versiones de MySQL posteriores a la 4.1.3. Desde la página oficial de PHP se nos sugiere su uso argumentando que «Esta es la opción recomendada, ya que utilizando el controlador nativo de MySQL resulta en un mejor rendimiento y permite el acceso a funciones que no están disponibles cuando se utiliza la biblioteca de cliente MySQL. Comparada con la extensión MySQL ofrece las siguientes mejoras:

� – Interfaz orientada a objetos

� – Soporte para Declaraciones Preparadas

� – Soporte para Múltiples Declaraciones

� – Soporte para Transacciones

Page 466: Manual PHP

� – Mejoradas las opciones de depuración

� – Soporte para servidor empotrado»

Conocido el manejo de las bases de datos MySQL y el manejo de las mismas mediante PDO o la librería mysql la utilización de esta nueva opción no va a plantearnos demasiadas dificultades. Lo iremos comprobando en los epígrafes siguientes.

Instalación y configuración de MySQLi

El uso del interface MySQLi requiere tener activa la extensión de PHP del mismo nombre.Bajo Windows, hemos de usar la extensiónextension=php_mysqli.dll. Como siempre en estos casos, habremos de editar el fichero php.ini, buscar esa extensión, descomentarlarla (quitar el punto y coma que llevan delante), guardar los cambios y reiniciar el servidor.

Cuando se trata de Ubuntu es preciso realizar la siguiente instalación:

sudo apt-get install php5-mysqli

con lo cual, en cualquiera de los dos casos, una vez reiniciado el servidor la página http://localhost/info.php debería mostrarnos una información similar a esta:

Gestión de bases de datos MySQL mediante MySQLi

Cuando se utiliza esta librería existen dos posibilidades de gestión de las bases de datos MySQL o MariaDB. Por un lado, la gestión que podemos llamar «clásica» que guarda una gran similitud con la ya conocida de la gestión por procesos de MySQL y, como opción alternativa, el estilo «orientado a objetos» que ya hemos manejado al tratar del PDO.

En páginas anteriores hemos tratado ambos formas de proceder. Por ello en este caso intentaremos concretar –en paralelo – ambas formas de proceder. Interaremos ir describiendo ambas formas de sintaxis y los sucesivos epígrafes y trataremos también de ir desarrollando ejemplos, con los mismos resultados, en ambas modalidades de programación.

Creación de bases y/o acceso a bases de datos

Page 467: Manual PHP

MySQLi no es un servidor de bases de datos nuevo o distinto de MySQL. Es únicamente una extensión PHP para acceder a ese tipo de bases de datos. Por tanto, los elementos propios de MySQL: ficheros, tablas, tipos de datos, índices y sentencias son exactamente los ya comentados en las páginas cuyos enlaces hemos incluido en este párrafo.

Desarrollaremos todos los ejemplos atendiendo a la existencia de un fichero al que llamaremos mysqli.inc.php cuyo contenido es el siguiente:

<?php; $cfg_servidor="localhost"; $cfg_usuario="pepe"; $cfg_password="pepa"; $cfg_basephp1="ejemplosMySQLi"; ?>

De esta forma podremos automatizar el proceso de conexión y evitar repeticiones innecesarias de algunas líneas de código.

Conexión/desconexión al servidor de bases de datos MySQL

Estilo por procesos

La sintaxis no difiere demasiado de la ya conocida. Cunado utilizemos la programación mediante procesos la sintaxis será la clásica de MySQL utilizando ahora la función mysqli_connec en vez de mysql_connec. La instrucción sería:

$conexion= mysqli_connect(host,usuario, contraseña )

dónde $conexion es el identificador de la conexión y host, usuario y contraseña los respectivos nombres del servidor, usuario y contraseña que están recogidos en la variables incluidas en el fichero mysqli.inc.php al que hemos hecho alusión en párrafos anteriores.

Cuando se trata de cerrar la conexión es necesaria ejecutar mysqli_close de forma similar a la ya conocida. Ahora deberemos escribir:

mysqli_close($conexion)

Estilo orientado a objetos

$objeto= new mysqli ( servidor, usuario, contraseña )

dónde $objeto es el identificador de un nuevo objeto, y servidor, usuario y contraseña los respectivos nombres del servidor, usuario y contraseña que coinciden con las ya mencionadas variables incluidas en el fichero mysqli.inc.php.

El cierre de la conexión requiere invocar el método close() y por lo tanto escribir algo como:

$objeto->close()

Este es un ejemplo de utilización sucesiva de ambas modalidades de programación.

Ejecutar ejemplo Ver código fuente

Gestión de errores de conexión

Cuando se trata de mysqli la gestión de errores se realiza a dos niveles. Los errores de conexión utilizan recursos de diágnostico distintos de otros errores de utilización de MySQL. El código de error se identifica en el caso de la programación por procesos mediante la función: mysqli_connect_errno() y la descripción de ese error se objetiene mediante mysqli_connect_error().

Page 468: Manual PHP

Si estamos utilizando objetos los eventuales códigos de error de conexión y sus descripciones los obtendríamos de las propiedades$objeto->mysqli_connect_errno y span class="cursiva1">$objeto->mysqli_connect_error. Podrás observar que, a diferencia del caso de programación por procesos, aquí errorno y error no van seguidos de paréntesis.

Mediante procesos Ver código fuente Mediante objetos Ver código fuente

Creación de bases de datos y errores en sentencias

Las sentencias MySQL no difieren en absoluto de las ya comentadas para el caso de utilización de la librería mysql o del PDO. Por tanto son válidas las sentencias ya conocidas:

CREATE DATABASE nombre_de_la_base y CREATE DATABASE IF NOT EXISTS

La ejecución de estas sentencias requiere las siguientes sintaxis:

Estilo por procesos

Requiere haber realizado una conexión previa y su correspondiente identificador de conexión. En esas condiciones la instrucción PHP sería:

$sentencia=CREATE DATABASE nombre_de_la_base mysqli_query($conexion, $sentencia) o también $sentencia=CREATE DATABASE IF NOT EXISTS nombre_de_la_base mysqli_query($conexion, $sentencia)

en este último supuesto evitaríamos la generación de errores como consecuencia de intentar crear ya base de datos preexistente.

¡Cuidado! Fíjate en el orden en el que se incluyen los parámetros. En el caso de mysqli la función mysqli_query lleva como primer parámetro es el indentificador de conexión y la sentencia propiamente dicha se incluye en segundo lugar. Esto es justo lo inverso de lo utilizado cuando se trabaja con el interface mysql y la función mysql_query.

Estilo orientado a objetos

Cuando trabajemos bajo esta modalidad de programación se requerirá la creación previa de un objeto conexión a servidor y la ejecución de la sentencia MySQL a través del método query(). La sintaxis sería:

$sentencia=CREATE DATABASE nombre_de_la_base $objeto->query($sentencia) o también $sentencia=CREATE DATABASE IF NOT EXISTS nombre_de_la_base $objeto->query($sentencia)

Información sobre errores

La gestión de errores en la ejecución de sentencias es distinta a la ya comentada para las conexiones. En este nuevo supuesto los mensajes de error podremos optenerlos de la función mysqli_error($conexion) en el caso de procesos y cuando se trate de programación orientada a objetos usaremos la propiedad $objeto->error.

Page 469: Manual PHP

Cuando se trata de conocer los códigos de error habría que utilizar mysqli_errno($conexion) o $objeto->errno dependiendo del método de programación que estemos utilizando. En estos ejemplos tienes los procesos de creación de una base de datos y la gestión de esos errores.

Mediante procesos Ver código fuente Mediante objetos Ver código fuente

Borrar bases de datos MySQL

Estas sentencias tampoco difieren de las ya comentadas para el caso de utilización de la librería mysql o del PDO. Por tanto son válidas:

DROP DATABASE nombre_de_la_base y DROP DATABASE IFEXISTS

La ejecución de estas sentencias requiere las siguientes sintaxis:

Estilo por procesos

Requiere haber realizado una conexión previa y su correspondiente identificador de conexión. En esas condiciones la instrucción PHP sería:

$sentencia=DROP DATABASE nombre_de_la_base mysqli_query($conexion, $sentencia) o también $sentencia=DROP DATABASE IF EXISTS nombre_de_la_base mysqli_query($conexion, $sentencia)

en este último supuesto evitaríamos la generación de errores como consecuencia de intentar borrar una base de datos inexistente.

Estilo por procesos

Una vez dispongamos un objeto conexión a servidor la ejecución de la sentencia MySQL a través del método query(). La sintaxis sería:

$sentencia=DROP DATABASE nombre_de_la_base $objeto->query($sentencia) o también $sentencia=DROP DATABASE IF EXISTS nombre_de_la_base $objeto->query($sentencia)

Mediante procesos Ver código fuente Mediante objetos Ver código fuente

Base de datos ejemplos

Utilizaremos una nueva base de datos con nombre ejemplosMySQLi nombre coincidente con el que hemos asignado a la variable$cfg_basephp1 en el fichero mysqli.inc.php. Como es lógico resulta indiferente utilizar cualquiera de los métodos de desarrollo del script. Tanto utilzando procesos como por medio de objetos el resultado sería idéntico.

Ver código fuente

Creación de tablas usando MySQLi

Tipos de tablas

Page 470: Manual PHP

La mayoría de los contenidos de esta página son pura transcripción del contenido de esta otra. Hay algunas diferencias de tratamiento que señalaremos para facilitarte la opción de comparación de los diferentes interfaces

MySQL permite usar diferentes tipos de tablas tales como: ISAM, MyISAM o InnoDB.

Las tablas ISAM (Indexed Sequential Access Method) son las de formato más antiguo. Están limitadas a tamaños que no superen los 4 gigas y no permite copiar tablas entre máquinas con distinto sistema operativo.

Las tablas MyISAM son el resultado de la evolución de las anteriores adaptadas por MySQL durante mucho tiempo como el formato por defecto para sus servidores de bases de datos. En las versiones más recientes de MySQL está cambiando es criterio y empiezan a verse versiones que incluyen InnoDB como formato por defecto.

Las tablas del tipo InnoDB (desarrolladas por la compañía finlandesa InnoBase a la que deben su nombre) tienen una estructura distinta a la de MyISAM, ya que utilizan un sólo archivo por tabla en ver de los tres habituales en los tipos anteriores. Incorporan dos de ventajas muy importantes: permiten realizar transacciones y definir reglas de integridad referencial. Hablaremos más adelante de ello.

La definición de uno u otro formato (MyISAM o InnoDB) debe hacerse en el momento de la creación de tabla y solo reguiere agregarType=MyISAM ó Type=InnoDB a la sentencia MySQL encargada de crearla.

Creación de tablas

Las tablas son elementos de las base de datos. Por esa razón nos resultará imposible crear una tabla sin tener creada yseleccionada una base de datos. Es por eso que para la creación de una tabla se necesitan los siguientes requisitos:

� – Tener abierta una conexión con el servidor MySQL.

� – Tener seleccionada una base de datos.

Conexión con el servidor

La hemos comentado en la página anterior. Recuerda que requería la función: $conexion = mysqli_connect ( servidor, usuario, contraseña ) (no confundir el mysqli_connect utilizado aquí con mysql_connect que es el manejado en páginas anteriores)y que esa conexión ha de ser establecida antes de cualquier otra intervención relacionada con accesos a bases de datos y tablas.

Selección de la base de datos

Dado que podemos manejar bases de datos distintas es preciso decir a MySQL con qué base queremos trabajar.

mysqli_select_db(nombre_base_datos, $conexion)

donde nombre_base_datos es el nombre de la base de datos (puede ser una cadena entrecomillada o el nombre de una variable previa que contenga ese nombre). En este último caso, como es habitual, el nombre de la variable no llevaría comillas. El segundo parámetro$conexion es el identificador de conexión. Es decir, la variable creada al establecer la conexión con MySQL. Este valor debe insertarse siempre. La razón es que MySQL permite mantener abiertas varias, de forma simultánea, varias conexiones (podríamos manejar más de un servidor de bases de datos) y en esas condiciones no pueden darse ambigüedades respecto a la base que pretendemos usar.

También es conveniente tener muy presente que en este caso hay que diferenciar también mysqli_select_db de mysql_select_db(observa que se diferencian únicamente en una «i»).

Este proceso puede simplicarse incluyendo el nombre de la base de datos como cuarto parámetro de la función mysqli_connect. Mediante esta opción

$conexion = mysqli_connect ( servidor, usuario, contraseña, nombre_base_datos)

estaremos seleccionando la base de datos nombre_base_datos a la vez que establecemos la conexión. Si hemos optado por la programación orientada a objetos la selección de la base de datos puede incluirse en la llamada al constructor de la clase. La forma de hacerlo es similar a esta:

Page 471: Manual PHP

$objeto = new mysqli ( servidor, usuario, contraseña, nombre_base_datos)

Creación de una tabla

En todas las transacciones PHP – MySQL habremos de utilizar instrucciones de ambos lenguajes. La forma habitual –hay algunas excepciones– en la que PHP se comunica con MySQL. Para utilizar el interface mysqli es necesario utilizar la función genérica:

mysqli_query($conexion, $sentencia );

donde la variable $sentencia contiene las instrucciones propias de MySQL y $conexion sigue siendo la variable que contiene el identificador de conexión.

En el ámbito de la programación orientada a objetos la ejecución de este tipo de sentencias (una vez creado el objeto y establecida satisfactoriamente la conexión) utiliza siempre la misma sintaxis:

$objeto->query( $sentencia)

Las variables aludidas como $sentencia en los párrafos anteriores ha de contener una de estas cadenas:

CREATE TABLE IF NOT EXISTS tabla (campo1, campo2,... ) Type=tipo o CREATE TABLE tabla (campo1, campo2,... )) Type=tipo

donde tabla es una cadena que contiene el nombre de la tabla que pretendemos crear, dónde campo1, campo2, etc. son las definiciones de los campos que pretendemos que contenga la tabla y tipo puede ser MyISAM o InnoDB según el tipo de tabla que queramos crear.

La única diferencia entre ambas opciones es que la segunda daría un error si tratáramos de crear una tabla preexistente (deberíamos recurrir al procedimiento que hemos visto cuando creábamos bases de datos) mientras que la primera no da ese mensaje de error.

Definición de campos en una tabla MySQL

Cada uno de los campos que vayamos a crear en una tabla requiere una definición que debe contener lo siguiente:

– nombre del campo

Es una palabra cualquiera –distinta para campo de la tabla y que normalmente suele elegirse aludiendo al contenido. Por ejemplo,fecha_nacimiento, nombre_del_perro, etcétera. No va entre comillas nunca y MySQL diferencia mayúsculas/minúsculas.

Para utilizar como nombres de campo palabras reservadas del lenguaje MySQL –por ejemplo, create– debemos escribirlas entre ` `. Observa que no son comillas sino acentos graves. Lo más aconsejable es evitar esta situación.

– tipo y dimensiones

Los tipos de campos –los hemos visto en la página anterior– tienen que ajustarse a uno de los soportados por MYSQL. El tipo de campo se escribe a continuación del nombre sin otra separación que un espacio y requieren la sintaxis –estricta– correspondiente a cada tipo.

Cuando se establezca una dimensión como parámetro opcional de un campo deben tenerse en cuenta algunos detalles:

� – Su valor se escribe entre paréntesis y se incluye después del nombre del campo.

� – Si en un campo numérico introdujéramos valores que exceden los límites, su valor no se registraría en la tabla sino el valor dellímite más próximo correspondiente a ese tipo de campo.

Page 472: Manual PHP

� – Supongamos un campo tipo TINYINT que permite valores comprendidos entre -128 y 127. Si asignáramos en uno de sus registros un valor igual a 234 se escribiría en la tabla 127 (el límite superior) y si ponemos -834 escribiría el límite inferior, es decir, -128.

� – En caso de cadenas, si el valor introducido sobrepasara la longitud máxima permitida, la cadena sería recortada y únicamente se registraría el número máximo de caracteres permitidos.

– flags del campo ( son opcionales)

Puede utilizarse cualquiera de los permitidos para cada tipo de campo. Puedes verlos encerrados, entre corchetes, al lado de cada tipo de campo. Cuando sea necesario incluirlo han de escribirse inmediatamente después del nombre del campo o de la dimesión (en los casos en que se incluya).

Ejemplo de creación de una tabla

Este ejemplo con campos idénticos a los de uno incluido en páginas anteriores podemos ver las dos opciones de creación de tablas en una base de datos. Dependiendo del valor pasado como tipo en la llamada al script se ejecutará una u otra de las opciones.

<?php /* crearemos antes una variable que recoja toda la sentencia y será luego cuando la ejecutemos Definiremos una varable llamada $crear e iremos añadiendo cosas */ # la primera parte de la instrucción es esta (espacio final incluido $crear="CREATE TABLE IF NOT EXISTS "; # añadiremos el nombre de la tabla que será ejemplo1 # fijate en el punto (concatenador de cadenas) que permite # ir añadiendo a la cadena anterior $crear .="ejemplo1 "; #ahora pongamos el paréntesis (con un espacio delante) #aunque el espacio también podría detrás de ejemplo1 $crear .="( "; # insertemos el primer campo y llamemoslo num1 # hagamoslo de tipo TINYINT sin otras especificamos # sabiendo que solo permitira valores numéricos # comprendidos entre -128 y 127 $crear .="num1 TINYINT , "; # LOS CAMPOS SE SEPARAN CON COMAS por eso # la hemos incluido al final de la instrucción anterior # ahora num2 del mismo tipo con dimensión 3 y el flag UNSIGNED # Y ZEROFILL que: cambiará los límites de valores # al intervalo 0 - 255, y rellenará con ceros por la izquierda # en el caso de que el número de cifras significativas # sea menor de 3. # Fijate que los flags van separado unicamente por espacios $crear .="num2 TINYINT (3) UNSIGNED ZEROFILL, "; # en num3 identico al anterior añadiremos un valor por defecto # de manera que cuando se añadan registros a la tabla # se escriba automaticamente ese valor 13 en el caso # de que no le asignemos ninguno a ese campo # por ser numérico 13 no va entre comillas $crear .="num3 TINYINT (7) UNSIGNED ZEROFILL DEFAULT 13, "; # ahora un número decimal num4 tipo REAL con 8 digitos en total # de los cuales tres serán decimales y también rellenaremos con ceros # Pondremos como valor por defecto 3.14 $crear .="num4 REAL (8,3) ZEROFILL DEFAULT 3.14, "; # añadamos una fecha $crear .="fecha DATE, "; /* una cadena con un limite de 32 carácter con BINARY para que diferencie Pepe de PEPE */ $crear .="cadena VARCHAR(32) BINARY, "; /* un ultimo campo –opcion– del tipo ENUM que solo admita

Page 473: Manual PHP

como valores SI, NO, QUIZA fijate en las comillas y en el parentesis ¡¡cuidado...!! aqui no ponemos coma al final es el último campo que vamos a insertar y no necesita ser separado. Si la pones dará un ERROR */ $crear .="opcion ENUM('Si','No','Quiza') "; # solo nos falta añadir el paréntesis conteniendo toda la instrucción $crear .=")Type=MyISAM"; /* tenemos completa la sentencia MYSQL solo falta ejecutarla crear la conexión y ejecutarla */ /*incluimos los parámetros de conexión */ include('mysqli.inc.php'); /* incluimos dos opciones de proceso y de programacion orientada a objetos. Podemos ejecutar los ejemplos pasado mediante el metodo GET el tipo elegido. */ if(!empty ($_GET['tipo'])){ $tipo_script=$_GET['tipo']; }else{ $tipo_script="objetos"; } /*En caso de tipo proceso utilizaríamos esto */ if ($tipo_script=="proceso"){ /****** Programación mediante procesos ***********/ #conexion, selección de tabla y verificacion de errores segun ejemplos anteriores $conexion=@mysqli_connect ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); if(!mysqli_connect_errno()==0){ print "<br>No ha podido realizarse la conexión mediante procesos<br>"; print "Error número: ". mysqli_connect_errno()." equivalente a: ". mysqli_connect_error(); exit(); } # gestion de la base de datos. Los parámetros requieren el ordn aquí indicado if(mysqli_query($conexion,$crear)){ print "La tabla ha sido CREADA"; }else{ print "<br>No ha podido crearse la base de datos mediante procesos<br>"; print "Error : ". mysqli_error($conexion); exit(); } mysqli_close($conexion); } /* en caso de programacion orientada a objetos */ if ($tipo_script=="objetos"){ /****** Programación mediante objetos ***********/ #conexion, selección de tabla y verificacion de errores segun ejemplos anteriores $objeto=@new mysqli ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); if(!$objeto->connect_errno==0){ print "<br>No ha podido realizarse la conexión mediante objetos<br>"; print "Error número: ". $objeto->connect_errno." equivalente a: ". $objeto->connect_error; exit(); } # gestion de la base de datos. Los parámetros requieren el ordn aquí indicado if($objeto->query($crear)){ print "La tabla ha sido CREADA";

Page 474: Manual PHP

}else{ print "<br>No ha podido crearse la tabla mediante objetos<br>"; print "Error : ". $objeto->error; exit(); } $objeto->close(); } ?>

Mediante procesos Mediante objetos

Ver y modificar estructuras (usando MySQLi)

Visualizar la estructura de una tabla (estilo por procesos)

Ver la estructura de una tabla utilizando MySQL

La sentencia MySQL que permiten visualizar la estructura de una tabla es la siguiente:

SHOW FIELDS from nombre de la tabla

Lectura de resultados de sentencias MySQL

La sentencia SHOW FIELDS –como prácticamente ocurre con todas las sentencias MySQL– no devuelve los resultados en un formatolegible. Los valores devueltos requieren ser convertidos a un formato que sea interpretable por PHP. Esa traducción se realiza de la siguiente forma:

� – El resultado devuelto por MySQL a través de una llamada mysqli_query() es recogido en una variable, de la forma siguiente:$resultado=mysqli_query($conexion, $sentencia)

� –El resultado recogido en la variable $resultado, está estructurado en líneas. Por medio de la función: $linea =mysqli_fetch_row ($resultado)

se recoge en una variable ($linea) el contenido de la primera línea situándose el puntero interno al comienzo de la línea siguiente. Por esta razón la lectura completa del contenido de la variable $linea requiere llamadas sucesivas a mysqli_fetch_rowhasta que haya sido leída la última de las líneas del resultado de la llamada a MySQL.

La variable $linea tiene estructura de array escalar siendo cero el primero de sus índices. Cuando el puntero interno demysql_fetch_row() alcance el final de la última línea del resultado devolverá como resultado FALSE.

Por esa razón, la visualización de los resultados de una sentencia MySQL suele requerir dos bucles. Este es el esquema de la lectura:

$resultado=mysqli_query($conexion,$sentencia); while($linea=mysqli_fech_row($resultado){ foreach ($linea as $valor){ print $valor; } }

también cabría usar una sintaxis alternativa similar a esta:

$resultado=mysqli_query(sentencia,$conexion); while($linea=mysqli_fech_row($resultado)){ $matriz[]=$linea; }

Page 475: Manual PHP

con lo que estaríamos creando un array bidimensional con el contenido de los resultados de cada línea. En este caso el primer índicedel array $matriz sería un escalar que empezaría en cero y se iría autoincrementando en una unidad en cada uno de los ciclos del buclewhile.

El ejemplo tiene desarrollados ambos procedimientos.

<?php # incluimos una variable con el nombre de la tabla $tabla="ejemplo1"; # incluimos los datos de la conexión y la base de datos para include("mysqli.inc.php"); # establecemos la conexión con el servidor y seleccionamos la base de datos $conexion=mysqli_connect ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $sentencia="SHOW FIELDS from ".$tabla; #ejecutamos mysql_query llamando a la sentencia SHOW FIELDS $resultado=mysql_query($conexion,$sentencia); # ejecutamos los bucles que comentamos mas arriba while($linea=mysqli_fetch_row ($resultado)){ foreach($linea as $valor) { print $valor."<br>"; } } #tenemos que VOLVER a EJECUTAR LA SENTENCIA MySQL porque el puntero está # AL FINAL de la ultima línea de los resultados print("<BR> Los nuevos resultados son <br>"); $resultado=mysql_query($conexion,$sentencia); while($linea=mysqli_fetch_row ($resultado)){ $matriz[]=$linea; } # leemos ahora el array bidimensional foreach ($matriz as $indice=>$mi_linea){ foreach ($mi_linea as $indice2=>$valor){ print "<i>Indice</i>: ".$indice." <i>Indice2</i>: ".$indice2." <i>Valor</i>: ".$valor."<br>"; } } # cerramos la conexion con el servidor mysqli_close($conexion); ?>

ejemplo566.php

El procedimiento anterior nos facilita información sobre la estructura de la tabla pero quizá no lo haga con toda la claridad que fuera de desear. Para mejorar las prestaciones de la anterior existe una función alternativa que es:

$matriz =mysqli_fetch_array($resultado)

idéntica en cuanto a requerimientos pero recogiendo en un array los resultados de la sentencia. Ese array tiene además la peculiaridad de incluye cada uno de los valores en dos índices distintos. Uno de los índices es de tipo escalar. El otro es de tipo asociativo y sus índices son los valores del campo de la tabla del que se han extraido los resultadosativos. En este último caso incorporan como índice el nombre del campo de la tabla del que se han extraído los resultados.

$matriz =mysqli_fetch_array($resultado, MYSQL_NUM)

idéntica a la anterior.La inclusión del parámetro MYSQL_NUM (sin comillas )limita sus resultado a los elementos del array de índice escalar.

Page 476: Manual PHP

$matriz =mysqli_fetch_array($resultado, MYSQL_ASSOC)

También idéntica a las anteriores. La inclusión del parámetro MYSQL_ASSOC (sin comillas )limita ahora los resultado a los elementos del array asociativo.

<?php # incluimos una variable con el nombre de la tabla $tabla="ejemplo1"; # incluimos los datos de la conexión y la base de datos para include("mysqli.inc.php"); # establecemos la conexión con el servidor y seleccionamos la base de datos $conexion=mysqli_connect ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $sentencia="SHOW FIELDS from ".$tabla; #ejecutamos mysql_query llamando a la sentencia SHOW FIELDS $resultado=mysqli_query($conexion,$sentencia); print("<br> Los resultados con mysqli_fech_array<br><br>"); while ($linea=mysqli_fetch_array($resultado)){ # leemos el array de resultados de cada una de las lineas extraidas del resultado foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } /* vamos a separar los elementos escalares de los asociativos comprobando si es numérico el indice de cada array */ print "<br>Los elementos de array con indice numérico son estos<br>"; $resultado=mysqli_query($conexion,$sentencia); while ($linea=mysqli_fetch_array($resultado,MYSQL_NUM)){ /* leemos el array de resultados de cada una de las lineas extraidas del resultado e insertamos un texto que nos ayude a ver lo contenidos de cada linea en la pantalla */ print "<br>Nueva línea<br>"; foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } /* visualizamos los resultados cuando el indice es NO NUMÉRICO */ print "<br>Los elementos de array con indice no numérico son estos<br>"; $resultado=mysqli_query($conexion,$sentencia); while ($linea=mysqli_fetch_array($resultado, MYSQL_ASSOC)){ /* leemos el array de resultados de cada una de las lineas extraidas del resultado e insertamos un texto que nos ayude a ver lo contenidos de cada linea en la pantalla */ print "<br>Nueva línea<br>"; foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } mysqli_close($conexion); ?>

ejemplo567.php

Como habrás podido observar al ejecutar el ejemplo SHOW FIELDS nos permite conocer los propiedades de cada uno de los campos de la tabla: Field (nombre del campo), Type (incluye el tipo, el tamaño y los flags), Null (especificaría si el campo permite

Page 477: Manual PHP

o no permite valores nulos), Key (especificaría si ese campo es o es un índice de la tabla) y Default (que incluiría los eventuales valores asignados por defecto al campo).

Visualizar la estructura de una tabla (estilo orientado a objetos)

Ver la estructura de una tabla utilizando MySQL

La sentencia MySQL que permiten visualizar la estructura de una tabla sigue siendo:

SHOW FIELDS from nombre de la tabla

Lectura de resultados de sentencias MySQL

La sentencia SHOW FIELDS –como prácticamente ocurre con todas las sentencias MySQL– no devuelve los resultados en un formatolegible. Los valores devueltos requieren ser convertidos a un formato que sea interpretable por PHP. Esa traducción se realiza de la siguiente forma:

� – El resultado devuelto por MySQL a través de una llamada al método query() en un objeto mysqli es recogido en una variable, de la forma siguiente: $resultado=$objeto->query($sentencia)

� –El resultado recogido en el nuevo objeto $resultado, está estructurado en líneas. Por medio del método: $linea =$resultado->fetch_row ()

se recoge en una variable ($linea) el contenido de la primera línea situándose el puntero interno al comienzo de la línea siguiente. Por esta razón la lectura completa del contenido de la variable $linea requiere llamadas sucesivas a mysqli_fetch_rowhasta que haya sido leída la última de las líneas del resultado de la llamada a MySQL.

La variable $linea tiene estructura de array escalar siendo cero el primero de sus índices. Cuando el puntero interno demysql_fetch_row() alcance el final de la última línea del resultado devolverá como resultado FALSE.

Por esa razón, la visualización de los resultados de una sentencia MySQL suele requerir dos bucles. Este es el esquema de la lectura:

$resultado=$objeto_mysqli->query($sentencia); while($linea=$resultado->fech_row(){ foreach ($linea as $valor){ print $valor; } }

también cabría usar una sintaxis alternativa similar a esta:

$resultado=$objeto_mysqli->query(sentencia); while($linea=$resultado->fech_row()){ $matriz[]=$linea; }

con lo que estaríamos creando un array bidimensional con el contenido de los resultados de cada línea. En este caso el primer índicedel array $matriz sería un escalar que empezaría en cero y se iría autoincrementando en una unidad en cada uno de los ciclos del buclewhile.

El ejemplo tiene desarrollados ambos procedimientos.

<?php # incluimos una variable con el nombre de la tabla $tabla="ejemplo1"; # incluimos los datos de la conexión y la base de datos para include("mysqli.inc.php"); # creamos el objeto conexión con el servidor y seleccionamos la base de datos

Page 478: Manual PHP

$objeto_mysqli=new mysqli ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $sentencia="SHOW FIELDS from ".$tabla; #ejecutamos mysql_query llamando a la sentencia SHOW FIELDS $resultado=$objeto_mysqli->query($sentencia); # ejecutamos los bucles que comentamos mas arriba while($linea=$resultado->fetch_row ()){ foreach($linea as $valor) { print $valor."<br>"; } } #tenemos que VOLVER a EJECUTAR LA SENTENCIA MySQL porque el puntero está # AL FINAL de la ultima línea de los resultados print("<BR> Los nuevos resultados son <br>"); $resultado=$objeto_mysqli->query($sentencia); while($linea=$resultado->fetch_row ()){ $matriz[]=$linea; } # leemos ahora el array bidimensional foreach ($matriz as $indice=>$mi_linea){ foreach ($mi_linea as $indice2=>$valor){ print "<i>Indice</i>: ".$indice." <i>Indice2</i>: ".$indice2." <i>Valor</i>: ".$valor."<br>"; } } # cerramos la conexion con el servidor $objeto_mysqli->close(); ?>

ejemplo568.php

El procedimiento anterior nos facilita información sobre la estructura de la tabla pero quizá no lo haga con toda la claridad que fuera de desear. Para mejorar las prestaciones de la anterior existe una método alternativa que es:

$matriz =$resultado->fetch_array()

idéntica en cuanto a requerimientos pero recogiendo en un array los resultados de la sentencia. Ese array tiene además la peculiaridad de incluye cada uno de los valores en dos índices distintos. Uno de los índices es de tipo escalar. El otro es de tipo asociativo y sus índices son los valores del campo de la tabla del que se han extraido los resultadosativos. En este último caso incorporan como índice el nombre del campo de la tabla del que se han extraído los resultados.

$matriz =$resultado->fetch_array(MYSQL_NUM)

idéntica a la anterior.La inclusión del parámetro MYSQL_NUM (sin comillas )limita sus resultado a los elementos del array de índice escalar.

$matriz =$matriz =$resultado->fetch_array(MYSQL_ASSOC)

También idéntica a las anteriores. La inclusión del parámetro MYSQL_ASSOC (sin comillas )limita ahora los resultado a los elementos del array asociativo.

<?php # incluimos una variable con el nombre de la tabla $tabla="ejemplo1"; # incluimos los datos de la conexión y la base de datos para include("mysqli.inc.php");

Page 479: Manual PHP

# creamos el nuevo objeto conexión con el servidor y seleccionamos la base de datos $objeto_mysqli=new mysqli ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $sentencia="SHOW FIELDS from ".$tabla; #ejecutamos mysql_query llamando a la sentencia SHOW FIELDS $resultado=$objeto_mysqli->query($sentencia); print("<br> Los resultados con fech_array<br><br>"); while ($linea=$resultado->fetch_array()){ # leemos el array de resultados de cada una de las lineas extraidas del resultado foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } /* vamos a separar los elementos escalares de los asociativos comprobando si es numérico el indice de cada array */ print "<br>Los elementos de array con indice numérico son estos<br>"; $resultado=$objeto_mysqli->query($sentencia); while ($linea=$resultado->fetch_array(MYSQL_NUM)){ /* leemos el array de resultados de cada una de las lineas extraidas del resultado e insertamos un texto que nos ayude a ver lo contenidos de cada linea en la pantalla */ print "<br>Nueva línea<br>"; foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } /* visualizamos los resultados cuando el indice es NO NUMÉRICO */ print "<br>Los elementos de array con indice no numérico son estos<br>"; $resultado=$objeto_mysqli->query($sentencia); while ($linea=$resultado->fetch_array( MYSQL_ASSOC)){ /* leemos el array de resultados de cada una de las lineas extraidas del resultado e insertamos un texto que nos ayude a ver lo contenidos de cada linea en la pantalla */ print "<br>Nueva línea<br>"; foreach($linea as $indice=>$valor) { print ("El indice es: ".$indice." y el valor es: ".$valor."<br>"); } } $objeto_mysqli->close(); ?>

ejemplo569.php

Manipulación de tablas

Borrar tablas

Las sentencias MySQL que permiten borrar una tabla son las siguientes:

DROP TABLE IF EXISTS from nombre_de_la_tabla o DROP TABLE from nombre_de_la_tabla

la diferencia entre ambas radica en que usando la primera no se generaría ningún error en el caso de que tratáramos de borrar una tabla inexistente.

Page 480: Manual PHP

Aquí tienes el código fuente de dos ejemplos que utilizan las distintas opciones de programación.

Ver código fuente mediante procesos

Ver código fuente mediante objetos

Borrar uno de los campos de una tabla

La sentencia MySQL que permite borrar uno de los campos de una tabla es la siguiente:

ALTER TABLE nombre_de_la_tabla DROP nombre_del_campo

Es posible modificar la estructura de una tabla -en este caso borrar un campo- siguiendo un procedimiento similar a los anteriores. La única diferencia estriba en utilizar la sentencia MySQL adecuada. Resulta obvio que el campo debe existir para que pueda ser borrado y si no existiera, es obvio también que se produciría un error.

Aquí tienes el código fuente de dos script que borran uno de los campos de una tabla.

Ver código fuente mediante procesos

Ver código fuente mediante objetos

Añadir un nuevo campo a una tabla

Las sentencia MySQL que permite añadir un nuevo campo a una tabla es la siguiente:

ALTER TABLE nombre_de_la_tabla ADD nombre del campo tipo [flags]

La sintaxis es similar a la de la creación de tablas: el nombre del campo debe ir seguido del tipo sin otra separación que el espacio.

Aquí tienes el código fuente de dos script que añadem uno de los campos de una tabla.

Ver código fuente mediante procesos

Ver código fuente mediante objetos

Añadir registros e índices (usando MySQLi)

Añadir registros a una tabla

La sentencia MySQL que permite añadir registros a una tabla es la siguiente:

INSERT tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

Transcribimos el desarrollo un ejemplo anterior de creación de una tabla e inserción de registros utilizando diversos métodos. La única diferencia entre lo que tratemos aquí y lo que hemos visto aquí será la forma de comunicación entre PHP y MySQL. El proceso es el siguiente:

Creación de la tabla

Empezaremos creando una tabla a la que llamaremos demo4 y que contendrá los siguientes campos:

Page 481: Manual PHP

� Contador, que será de tipo TINYINT(8) (número entero con un máximo de 8 dígitos) que contenga solo valores positivos, que se rellene automáticamente con ceros por la izquierda y que se autoincremente cada vez que añadimos un registro.

� DNI, destinado a recoger valores de números de DNI y que debe poder contener un máximo de ocho caracteres. Lo definiremos de tipo CHAR porque sus valores, pese a ser numéricos, nunca van a requerir ningún tratamiento aritmético.

� Nombre, Apellido1 y Apellido2 serán tres campos tipo VHAR de tamaños máximos respectivos de 20, 15 y 15 caracteres. Su finalidad la evidencian los nombres de campo. Les asignaremos el flag NOT NULL aunque no sea totalmente correcto dado que existen países en los que se utiliza un solo apellido.

� Nacimiento será un campo tipo DATE al que asignaremos como valor por defecto el de 1970-12-21. Recuerda que MySQL trata las fechas en ese orden (año-mes-dia) y que admite como separadores tanto - como / por lo que son válidas entradas con cualquiera de estos formatos: 1970-12-21 ó 1970/12/21, aunque esta segunda es convertida automaticamente al primer formato por MySQL.

� Hora será un campo tipo TIME al que asignaremos como valor por defecto el de 00:00:00. Recuerda que MySQL trata las horas en ese orden (hh:mm:ss) y que sólo admite como separador :. Este campo está destinado a recoger la hora de nacimiento que aunque no tiene demasiado sentido es una buena excusa para introducir este tipo de campo.

� Sexo será un campo tipo ENUM que tendrá dos opciones: M (masculino) y F(femenino) y al que asignaremos M como valor por defecto.

� Fumador será un campo tipo CHAR(0) que por su estructura -cadena de longitud cero- tendrá dos únicas opciones de valor: NULL ó"", que, como veremos, son valores distintos para MySQL.

� Idiomas será un campo tipo SET definido para los valores: Castellano, Francés, Inglés, Alemán, Búlgaro y Chino y que podrá contener, como ocurre con los campos de este tipo, ninguno, uno o varios de los valores de la lista.

� Índice primario será tratado como tal el campo DNI ya que se trata de un valor único para cada persona y que, como tal, no puede tener duplicados.

� Índices auxiliares. Para ejemplificar su tratamiento consideraremos el campo Contador como índice secundario.

El código fuente del fichero que genera esta tabla puedes verlo aquí debajo

Ver código fuente

Añadir un registro

Cuando se añade un registro en una tabla los valores pueden añadirse en la propia sentencia MySQL o ser recogidos de los valores de variables PHP previamente definidas.

En este ejemplo tienes el código fuente del primero de los casos, en el que se añade a la tabla anterior un registro cuyos valores son:

DNI Nombre Apellido1 Apellido2 Nacimiento Sexo Hora Fumador Idiomas

1234 Lupicinio Servidor Servido 1954-11-23 M 16:24:52 NULL 3

Ver código fuente mediante procesos

Ver código fuente mediante objetos

Presta atención a los siguientes aspectos:

� – En la sentencia no se alude al campo Contador. La razón es que se trata un campo AUTOINCREMENTAL y en ese tipo de campos los valores de los registros se escriben automáticamente cada vez que se añade uno nuevo.

� – El registro no se añadiría si el valor de DNI coincidiera con otra ya existente en la tabla. Recuerda que habíamos definido ese campo como índice primario. Si no hubiéramos incluido el aviso de error no tendríamos ninguna referencia sobre el éxito de la inserción y no detectaríamos el problema de duplicidad. Sencillamente ocurriría que el registro no se añadiría pero no nos enteraríamos de tal circunstancia.

� – Si en los valores de nombre y apellidos hubiéramos insertado textos más largos del tamaño establecido para ellos al crear la tabla, las cadenas se recortarían y sólo se añadirían -de izquierda a derecha- los n primeros caracteres de la cadena.

Page 482: Manual PHP

� – Si en la fecha de nacimiento hubiéramos introducido una cadena vacía nos habría puesto el valor por defecto, pero si hubiéramos introducido un valor no válido nos habría escrito 0000-00-00. Serían valores no válidos en este caso:

� – Los que tuvieran como valor de mes alguno no perteneciente al intervalo [1,12].

� – Los que tuvieran como valor de día un valor no válido en concordancia con el mes y el año. Admitiría 29 en un mes defebrero sólo en el caso de que el año fuera bisiesto.

� – Cuando la secuencia no coincidiera con esta AAAA-MM-DD.

� – Cuando los separadores no fueran (-) o (/).

� – En el campo Sexo si hubiéramos introducido una cadena vacía nos habría puesto M (valor por defecto) pero si hubiéramos intentado introducir un valor que no fuera M ni F nos habría insertado una cadena vacía.

� – El campo hora se comportaría de idéntica forma al de fecha salvo que aquí la secuencia es: hh:mm:ss, que la hora debe pertenecer al intervalo [0,23], los minutos y los segundos a [0,59] y que el único separador válido en este caso es (:).

� – El campo Fumador requiere particular atención ya que se trata de un campo CHAR(0) que sólo admite dos valores: NULL ocadena vacía, que como recordarás son distintos para MySQL.

� – Para introducir el valor NULL –utilizando el procedimiento de inserción de este ejemplo– tienes que escribir NULL, sin ponerlo entre comillas, tal como lo he hecho en el ejemplo .

� –Para introducir una cadena vacía en este campo bastaría con que pusieramos '' (¡ojo no es una comilla doble es la comilla sencilla (') repetida dos veces! Date cuenta de que si pusiéramos comillas dobles tendríamos un error ya que hay unas comillas dobles delante delINSERT que se cierran al final de la sentencia MySQL y que no podemos volver a escribirlas entre ambas para evitar unfalso cierre de la cadena que contienen.

� –En este caso el valor del campo Idiomas puede contener valores decimales comprendidos entre 0 y 64 o entre sus equivalentes binarios que son 0 y 111111. El valor 64 lo justifica el hecho de que son seis los elementos que puede contener el campo (hemos definido: Castellano, Francés, Inglés, Alemán, Búlgaro y Chino que son seis y que el caso de insertarlos todos requeriría el número binario 111111, cuyo valor decimal es precisamente 64. El valor 3 significa lo mismo que su equivalente binario (11) o mejor (000011) lo cual quiere decir que, como el primer carácter de la derecha es uno el campo toma el primer elemento de la lista (Castellano), como el segundo (de derecha a izquierda) también esuno tomará también el segundo elemento de la lista (Francés) y por ser cero todos los demás no tomará ningún otro valor de la lista, con lo que la cadena resultante sería en este caso Castellano, Francés.

� –Fíjate también en que el valor 3 no lo hemos puesto entre comillas porque se trata de una expresión decimal. ¿Qué ocurría si hubiera puesto 11? ¿Lo habría interpretado como once (decimal) o como tres (binario)?. La solución es simple: '11' (entre comillas) sería interpretado como binario, sin comillas como decimal.

� – No es necesario introducir valores en todos los campos, es decir, que la lista de campos de la sentencia INSERT puede no contenerlos a todos.

� – No es necesario escribir el nombre de los campos en el mismo orden en el que fueron creados pero si es imprescindible quecampos y valores estén escritos en la sentencia INSERT exactamente en el mismo orden y también que en esa sentencia elnúmero de campos y el número de valores sea el mismo.

� – Recuerda que los values tipo numéricono se incluyen entre comillas, mientras que los no numéricos tienen que estar contenidos entre comillas incluso en el caso de no se inserten directamente sino a través de una variable PHP.

Añadir un registro a partir de datos contenidos en variables

También es posible añadir registros a partir de valores contenidos en variables PHP. Esta opción es, sin ninguna duda, la más utilizada, ya que lo habitual será escribir el contenido a añadir en un form y después -a través del method (POST o GET)- pasar al script de inserción esos valores como variables PHP.

Aquí tienes el código fuente de un ejemplo con la tabla anterior.

Ver código fuente mediante procesos

Ver código fuente mediante objetos

Quizá te resulten de alguna utilidad estos comentarios:

Page 483: Manual PHP

� – Observa en el código fuente que al insertar las variables en los VALUES de la sentencia MySQL ponemos cuando se trata de valores tipo cadena '$variable' (el nombre de la variable entre comillas) y cuando se trata de valores númericos sin comillas.

� – Si quieres introducir el valor NULL en un campo tipo VAR(0) define la variable así: $variable="NULL" y si quieres introducir unacadena vacía defínela de este otro modo:$var="''" -comillas dobles (") seguidas de dos comillas sencillas (') y para terminar otras comillas dobles (") y cuando hagas alusión a esa esta variable como un VALUE en la sentencia de inserción no la pongas entre comillas.

Variantes de la sentencia INSERT

La sentencia INSERT cuya sintaxis se indica más arriba como:

INSERT tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

permite algunos modificadores opciones tales como:

INSERT [LOW_PRIORITY | DELAYED] [IGNORE] tabla (campo1,campo2,..) VALUES (valor1,valor2,..)

de ellos LOW_PRIORITY y DELAYED son incompatibles por lo que solo cabe uno u otro pero no ambos.

Veamos su utilidad. La inserción de registros y la lectura de una tabla son procesos incompatibles, pero cabe la posibilidad de que se intenten ejecutar simultáneamente. No olvides que estamos en Internet y es perfectamente posible que desde dos ordenadores distintos, dos personas distintas estén accediendo a la misma tabla simultáneamente y que uno de los accesos sea de escritura, es decir: añadir,modificar o borrar campos en la tabla.

¿Quién tiene preferencia de paso? ¿Quien tiene que esperar?. Si la opción LOW_PRIORITY está activada, el proceso de escritura esperará a que terminen los procesos de lectura activos, pero si está activa la opción DELAYED el proceso de lectura se interrumpirá automáticamente para ceder el paso al de escritura.

Respecto a la opción IGNORE tiene utilidad cuando se trata de realizar una secuencia de inserciones. Si no está activa en el momento en el que aparezca una clave duplicada se interrumpirá el proceso de inserción, por el contrario, si estuviera activa el proceso de inserción continuará con los siguientes registros de la secuencia, aunque -como es lógico- seguirán sin insertarse los registros con clave duplicada.

Número de resgistros afectados por una sentencia

Cuando se usa el interface de MySQLi es posible cuantificar el número de registros afectados por la ejecución de una sentencia. A tal fin y para el caso de programar mediante procesos disponemos de la función

mysqli_affected_rows($conexion)

que devuelve el número de registros afectados por la ejecución de la última instrucción mysqli_query que utilizó el identificador de conexión $conexion. En el caso de que la programación de nuestros scripts esté orientada a objetos disponemos de la propiedad:

$objeto->affected_rows

cuyo valor es número de registros afectados por la última ejecución del método $objeto->query(). En los ejemplos siguientes hay ejemplos de utilización de este cuantificador.

Tablas para pruebas

Aquí tienes comentado un script que permite agregar aleatoriamente y de forma automática registros a la tabla demo4. Dado que en las páginas siguientes trataremos de consultas, va a resultarnos muy cómodo poder rellenarlas de forma automática.

Ver código fuente Ver código fuente

Page 484: Manual PHP

mediante procesos mediante objetos

Añadir a través de formularios (MySQLi)

Los valores de SELECT MULTIPLE

Los contenidos de esta página son una copia de los que hay aquí adaptados a las peculiaridades requeridas para el uso del interface MySQLi.

La opción SELECT MULTIPLE dentro de un form típico de HTML permite elegir ninguno, uno o varios de los elementos de la lista. Basta con pulsar con el ratón sobre cada uno de los valores elegidos manteniendo pulsada la tecla Ctrl, es decir, puro Windows.

Para recoger los valores de esa opción se define -dentro de la etiqueta SELECT- un name tipo array. Bastaría con escribir:

SELECT MULTIPLE name=variable[ ] SIZE=6>

Como ves, variable es el nombre de la variable (esta vez sin $ delante, recuerda que no estamos en PHP sino en HTML) y va seguidode [ ] precisamente para indicar que es un array.

Lo que respecta a SIZE=6 no es otra cosa que el parámetro que indica cuántos elementos de la lista de opciones queremos que se visualicen simultáneamente en la página.

El elemento más importante son los values de cada option dentro de ese select. Los hemos escrito así:

<option value=1>Castellano <option value=2>Francés <option value=4>Inglés <option value=8>Alemán <option value=16>Búlgaro <option value=32>Chino

Fíjate que hemos mantenido exactamente el mismo orden en el que han sido definidos en el campo SET de la tabla. Observa también los valores: 1, 2, 4, 8, 16 y 32 que son precisamente las potencias de 2: 20, 21, 22, 23, 24, 25, y 26

Al ir seleccionando valores, van añadiéndose al array. Por ejemplo. Si seleccionamos Francés y Búlgaro el array sería este: var[0]=2,var[1]=16

Si sumamos esos valores (2 + 16) el resultado sería 18, y al convertir a binario este valor, resultará: 010010 que es como decirle a MySQL (mirando la cadena de derecha a izquierda, ¿lo recuerdas?) que incluya los valores segundo (Francés) y quinto (Búlgaro) del SELECT MULTIPLE que corresponden a las posiciones en las que la cadena binaria contiene un uno.

Creación del formulario

El caso más frecuente -casi el único- es que los registros de una tabla se añadan utilizando un formulario y enviando desde él los datos a un script PHP que ejecute la opción de añadir. Si no recuerdas el funcionamiento de este método, pulsa aquí

En el ejemplo hemos desarrollado un formulario para añadir registros a la tabla demo4 con las siguientes peculiaridades:

� Para los campos DNI, nombre y apellidos hemos utilizado input tipo texto y hemos recogido mediante la opción name cada uno de los campos en una variable independiente.

� Para los campos Fecha de nacimiento y hora de nacimiento hemos utilizado tres opciones select en cada una de ellas. La finalidad de estas opciones no es otra que impedir la introducción de fechas no válidas (en realidad no lo impedimos totalmente ya que, tal como está confeccionado, podría introducirse 31 de febrero, o 31 de abril). Ese aspecto es mejorable, pero para hacerlo –desde el propio formulario– tendríamos de recurrir a un lenguaje del lado del cliente (JavaScrpt por ejemplo).

Page 485: Manual PHP

� Los valores de esos tres campos (tanto en fecha como en hora) los recogemos en variables que son elementos de dos array escalares.

� El campo sexo la recogemos en input tipo radio y les asignamos valores M ó F que coinciden con los valores del campo ENUM de la tabla.

� Con el campo Fumador -opción Fumador/No fumador - hacemos exactamente lo mismo, pero asignándoles valores 1 o 0 ya que el formulario no permite la opción NULL ó cadena vacia. En el script posterior será cuando modifiquemos los valores de esas variables.

� Para el campo Idiomas utilizamos una opción select de tipo múltiple y para los values un pequeño truco que describimos aquí la izquierda.

Pues bien, aquí tienes, código fuente del formulario que hemos diseñado

Ver código fuente

Añadir nuevo registro con datos del formulario

Como recordarás, cuando se envía el contenido de un formulario mediante el method=POST y se indica como action un fichero PHPlos valores enviados son recogidos en este último fichero en variables de PHP que tienen como nombre $_POST['var'] donde cada una de los índices asociativos de los array (var) coinciden con los name de los diferentes campos del formulario.

A partir de ahí, bastaría con depurar los valores recibos, recoger en variables los valores depurados e incluirlos en la sentencia MySQL INSERT -la hemos visto en la página anterior- para añadirlos a la tabla correspondiente. Aquí tienes –comentados– los scripts en ambas modalidades de programación:

Ver codigo fuente mediante procesos

Ver codigo fuente mediante objetos

En realidad, tal como habrás podido ver en el código fuente, la depuración ha sido la siguiente:

� Hemos creado un valor de fecha y hora en formatos MySQL válidos de la forma que describimos un poco más arriba.

� Hemos sumado todos los valores numéricos recibidos en el array obtenido del SELECT MULTIPLE y hemos asignado el resultado a la variable depurada que recoge el valor a escribir en el campo Idiomas. La justificación de esa suma la tienes al margen.

� La variable Fumador es la que tiene un poquito más de complicación. Veámosla con calma: Los valores que recibimos desde formulario son 1 o 0 y hemos de transformarlos en una cadena vacía o en NULL. Hemos insertado un operador condicional (un if... else) para convertir eso valores en: $var="'\N'" (comilla doble, comilla simple, barra invertida, N, comilla simple y comilla doble) ó $var="''" (comillas dobles, dos comillas simples y unas comillas dobles)

� Asignados los nuevos valores tenemos que recurrir a un pequeño truco. Venimos repitiendo que en la sentencia INSERT losnombres de las variables no numéricas que contienen los values hay que escribirlos dentro de comillas simples, pero en el caso de un campo tipo CHAR(0) hemos de hacer una excepción que sería no poner esas comillas al nombre de la variable. Al hacerlo así, se escribirían como valores –en la sentencia de inserción– uno de estos: ='\N' o =' ' (los valores de la variable) que al contenercomillas ya son interpretados por MySQL como una cadena.

Consultas simples y múltiples (MySQLi)

Sintaxis MySQL de selección de registros

Las sentencias de selección de registros requieren utilizar –entre otras– palabras clave como las que enumeramos a continuación. Observa que las hay dos tipos: obligatorias y opcionales. Observa también que algunas de las palabras clave son alternativas y por lo tanto, incompatibles en una misma sentencia. La inserción ha de hacerse respetando un orden tal y como se enumera aquí debajo. Si alteráramos ese orden (p. ejemplo: colocando GROUP BY antes de WHERE) se produciría un error y dejaría de ejecutarse la sentencia.

Los párrafos siguientes están organizados a dos niveles. Las intrucciones del primero de ellas tienen carácter obligatorio en la sentencia de consulta. Las del segundo, señaladas con letra cursiva, son opcionales.

� SELECT Es la primera palabra de la sentencia de búsqueda y tiene carácter obligatorio.

Page 486: Manual PHP

� SQL_BIG_RESULT Es una cláusula opcional que se usa para indicar al optimizador que el resultado va a tener una gran cantidad de registros. En ese caso, MySQL utilizará tablas temporales cuando sea necesario para optimizar la velocidad de gestión de la información. Esta cláusula también puede ser utilizada dentro de GROUP BY.

� SQL_BUFFER_RESULT Es opcional y su finalidad es la de forzar a MySQL a tratar el resultado en un fichero temporal. Ese tratamiento ayuda a MySQL a liberar recursos más rápidamente y es de gran utilidad (siempre desde el punto de vista de la rapidez) cuando es necesario un largo proceso de cálculo antes de enviar los resultados al cliente.

� HIGH_PRIORITY Esta cláusula, opcional da prioridad al comando SELECT sobre otros comandos que simultáneamente pudieran estar intentando acceder a la tabla para escribir en ella (añadir o modificar registros). Si esta opción está activa, los intentos de escritura que pudieran producirse de forma simultánea deberían esperar al final de este proceso para ejecutarse.

� campo1, campo2, ... Tienen carácter obligatorio y señalan los campos de la tabla que deben incluirse en la consulta. La consulta sólo devolverá información de aquellos campos que estén enumerados aquí. Si se desea incluir a todos campos bastará con excribir * en esta posición. En este caso *tiene la condición de carácter comodín cuyo significado es todos los campos. Los campos numéricos pueden llevar asociadas funciones MySQL que devuelven información estadística. La sintaxis de algunas de esas funciones es la siguientes:

� MAX(campo) Devuelve el mayor de los valores de ese campo de entre todos los registros de la la consulta. Los registros comparados puedes ser todos o una parte de ellos. Si la sentencia incluye la condición GROUP BY, en cuyo caso devolverá el máximo de cada grupo y si incluye la opción WHERE solo serán comparados los registros que cumplan tal condición.

� MIN(campo) Idéntica a la anterior en cuanto a criterios de selección, esta función devuelve el manorde los valores del campo en el ámbito establecido por las evuntuales condiciones que pueda incluir la sentencia.

� AVG(campo) Devuelve el valor promedio de todos los registros numéricos seleccionados con los mismos criterios del caso anterior.

� SUM(campo) Devuelve la suma de todos los valores del campo seleccionado y en el ámbito establecido por los parámetros de la consulta.

� STDDEV(campo) Devuelve la estimación de la desviación típica de la población formada por los valores del campo.

� COUNT(campo) Cuenta los valores no nulos del campo indicado.

� Cuando se aplica una de estas funciones el resultado de la consulta contiene una sola línea, salvo que se incluya opciónGROUP BY, en cuyo caso devolverá tantas líneas como grupos resulten.

� FROM tabla Esta expresión indica a MySQL el nombre de la tabla en la que debe efectuarse la consulta.

� WHERE definicion Esta instrucción tiene carácter opcional y su utilidad es la de filtrar la consulta estableciendo los criterios de selección de losregistros que debe devolver. Si se omite WHERE, la consulta devolverá todos los registros de la tabla. En la tabla de ejemplos tienes información sobre las formas de definir los criterios de selección de esta opción.

� GROUP BY definicion Tiene carácter opcional y su finalidad es la de presentar los resultados de la consulta agrupados según el criterio establecido en su definición. Resulta de gran utilidad cuando se pretende obtener valores estadísticos de los registros quecumplen determinadas condiciones (las condiciones del agrupamiento).

� ORDER BY definicion También tiene carácter opcional y su utilidad es la de presentar la información de la consulta ordenada por los contenidos de uno o varios campos. Siempre tiene como opción complementaria de que en cada campo utilizado para la ordenaciónpuede establecerse uno de estos criterios ASC (ascendente, es el valor por defecto) o DESC (descendente). Si no se establece ningún orden los resultados de la consulta aparecerán en el mismo orden en el que fueron añadidos los registros.

� LIMIT m, n Esta cláusula es opcional y permite establecer cuántos y cuáles registros han de presentarse en la salida de la consulta. Por ejemplo: LIMIT 4, 8 indicaría a MySQL que la consulta debería mostrar OCHO registros contados a partir del quinto (sí, el quinto porque LIMIT considera el primer registro como CERO). El criterio límite se aplica sobre los resultados de la salida, es decir, sobre los resultados seleccionados, ordenados yfiltrados siguiendo los criterios establecidos por las cláusulas anteriores. Si se escribe

Page 487: Manual PHP

como un solo parámetro (LIMIT k), MySQL lo interpretará como que k es el segundo de ellos y que el primero es CERO, es decir: LIMIT 0, k

Algunos ejemplos de consultas simples

Las consultas de los datos y registros contenidos en una tabla ofrecen un amplísimo abanico de posibilidades a partir de las opciones descritas anteriormente. Veamos algunas de las posibilidades.

La consulta más simple

Si utilizamos la sentencia

SELECT * FROM tabla

obtendremos información sobre todos los campos (*) y la salida estará en el mismo orden en el que fueron añadidos los datos. Si visualizas este ejemplo, verás que aparecen ordenados por el valor autonumérico del campo Contador lo cual, como ves, resulta coherente con la afirmación anterior.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Consultando sólo algunos campos

Ahora utilizaremos la sentencia

SELECT campo1,campo2, ... FROM tabla

y tendremos como resultado una lista completa, por el mismo orden que la anterior, pero sólo mostrando los campos indicados.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Consultando sólo algunos campos y limitando la salida a n registros

Ahora utilizaremos la sentencia

SELECT campo1,campo2, ... FROM tabla LIMIT (n, m)

y tendremos como resultado una lista que contendrá m registros a partir del n+1, por el mismo orden que la anterior, y mostrando los campos indicados.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Consultando sólo algunos campos y ordenando la salida

Utilizaremos la sentencia MySQL de esta forma

SELECT campo1,campo2, ... FROM tabla ORDER BY campo_n [ASC|DESC], campo_m [ASC|DESC]

Page 488: Manual PHP

y tendremos como resultado una lista ordenada por el primero de los campos indicados en ORDER BY, y en caso de coincidencia de valores en ese campo, utilizaríamos el criterio de ordenación señalado en segundo lugar.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Consulta seleccionando registros

Utilizaremos la sentencia MySQL de esta forma

SELECT campo1, ... FROM tabla WHERE condición

que nos devolverá la lista de registros que cumplen la condición indicada. Aquí tienes un ejemplo muy sencillo.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Los resultados de la sentencia SELECT

El manejo de los resultados obtenidos mediante mysqli_query o $objeto->query ofrece varias posibilidades. El bloque de información resultante de una consulta y recogido en $resultado está organizado en filas que pueden manejadas de diversas formas.

Una primera información que puede resultar muy útil e interesante es:

$num_resultados=mysqli_num_rows($resultado) o $num_resultados=$resultado->num_rows

En ambos casos la variables $num_resultados recogerá el número de registros (filas) obtenidos como resultado de la consulta. La primera de las opciones se utiliza cuando se gestiona la consulta mediante procesos mientras que es segundo corresponde a consultas mediante objetos.

Los valores contenidos en cada fila de los resultados pueden leerse mediante

$array=mysqli_fetch_row($resultado) o $array=$resultado->fetch_row()

dónde $array es un array escalar que contiene los valores del registro actual (aquel que señala el puntero interno). Los índices de este array van asociados al orden en el que están escritos los nombres de los campos dentro de la sentencia select.

Si no se indica otra cosa el puntero comienza un recorrido por el primer registro del resultado (fila 0) y en las sucedsivas llamadas a método fetch_row se va desplazando a la siguiente fila. Por eso, mediante un bucle while podemos recorriendo los diferentes registros del resultado y convirtiendo cada uno de ellos en un array escalar. Aquí tienes un ejemplo

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Los valores contenidos en cada fila de los resultados también pueden leerse mediante

Page 489: Manual PHP

$array=mysqli_fetch_assoc($resultado) o $array=$resultado->fetch_assoc()

dónde $array es un array asociativo que contiene los valores del registro actual (aquel que señala el puntero interno). Los índices de este array son los nombres de los campos dentro de la sentencia select.

Si no se indica otra cosa el puntero comienza un recorrido por el primer registro del resultado (fila 0) y en las sucedsivas llamadas a método fetch_assoc se va desplazando a la siguiente fila. Por eso, mediante un bucle while podemos recorriendo los diferentes registros del resultado y convirtiendo cada uno de ellos en un array asociativo. Aquí tienes un ejemplo

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Una tercera opción para ir leyendo los valores contenidos en cada fila de los resultados es la que utiliza

$objeto=mysqli_fetch_object($resultado) o $objeto=$resultado->fetch_object()

dónde $objeto es un objeto cuyas propiedades son los nombres de los campos incluidos en la sentencia select y el valor el resultante de la consulta para ese campo en la fila actual (aquella a la que apunta el puntero interno).

Si no se indica otra cosa el puntero comienza un recorrido por el primer registro del resultado (fila 0) y en las sucesivas llamadas a método fetch_object se va desplazando a la siguiente fila. Por eso, mediante un bucle while podemos recorriendo los diferentes registros del resultado y convirtiendo cada uno de ellos en un objeto con las propiedades descritas. Aquí tienes un ejemplo

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

En esta nueva opción se realiza un lectura única del contenido de todas las filas

$array_bidimensional=mysqli_fetch_all($resultado) o $array_bidimensional=$resultado->fetch_all()

dónde $array_bidimensional es un array cuyo primer índice coincide con el número de orden (empezando por cero) de cada una de las filas del resultado y cuyo segundo índice es el número de columna entendiendo como tales cada uno de los campos incluidos en la sentencia SELECT por el orden de inclusión enla misma y contados a partir de cero.

La extracción de los diferentes valores requeriría un doble bucle de lectura. El primero nos iría posicionando en cada una de las filas del resultado (el primer índice) y el segundo bucle recorrería todos de aquella fila. Aquí tienes un ejemplo

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Hay situaciones en las que puede resultarnos de interés conocer únicamente los valores de alguno o algunos de los registros incluidos en el resultado de una consulta. Puede, por ejemplo, que nos interese conocer el únicamente el primer registro (o el último, o el que ocupa la posición 38) tal como aparecen en el resultado de la ejecución de la función mysqli_query o del método query() si se trata de objetos. Tanto la función mysql_fech_row() como el método fetch_row() extreaen aquel registro (fila de datos) al que señala el apuntador. Para posicionar de forma arbitrarie el apuntador de resgistros mysqli dispone las siguientes funciones:

Page 490: Manual PHP

mysqli_data_seek($resultado, posicion)

dónde $resultado es el objeto resultante de una consulta realizada por medio de la función mysqli_query() y posición es un número entero que explicita la posición (la prima posición se identifica mediante cero) a la que pretendemos orientar el apuntador de lectura demysqli_fetch_row. El proceso podría ser similar a este:

$resultado=mysqli_query($identificador,sentencia_MySQL); // realiza la consulta y recoge el resultado en la variable $resultado mysqli_data_seek($resultado,posicion); //Sitúa el apuntador en la fila señalada por posición $fila=mysqli_fetch_row($resultado); //Lee el registro (fila) que señala en apuntador anterior y recogiendo el resultado en el array $fila

Cuando se trata de que la programación esté orientada a objetos el método que debemos utilizar es el siguiente:

$resultado->data_seek(posicion)

dónde $resultado es el objeto resultante de una consulta realizada por medio del método $objeto->query() y posición es un número entero que explicita la posición (la prima posición se identifica mediante cero) a la que pretendemos orientar el apuntador de lectura que usará el posterior métoco fetch_row. El proceso podría ser similar a este:

$resultado=$objeto->query(sentencia_MySQL); // el método query realiza la consulta y recoge el resultado en el nuevo objeto $resultado $resultado->data_seek(posicion); //Sitúa el apuntador en la fila señalada por posición $fila=$resultado->fetch_row(); //Lee el registro (fila) que señala en apuntador anterior y recogiendo el resultado en el array $fila

Como es lógico también pueden usuarse fetch_object ó fetch_assoc en lugar del fetch_row del pseudocódigo anterior.

Consultas condicionadas

La claúsula WHERE permite un variado abanico de condiciones, que trataremos de resumir aquí. Algunos de ellas son los siguientes:

Operador Tipo de campo

Sintaxis Descripción

Progamación por procesos

Programación orientada a objetos

Código fuente

Ver ejemplo

Código fuente

Ver ejemplo

= Numérico WHEREcampo=num Selecciona los registros que contienen en el campo un valor igual a num

Ver Probar Ver Probar

= Cadena WHEREcampo="cadena" Selecciona los registros que contienen en el campo una cadena idéntica a cadena(*)

Ver Probar Ver Probar

< Numérico WHEREcampo<num Selecciona los registros que contienen en el campo un valor menor a num

Ver Probar Ver Probar

< Cadena WHEREcampo<"cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres son menores que los de la cadena, siendo n el número de caracteres que contiene cadena. (**)

Ver Probar Ver Probar

<= Numérico WHEREcampo<=num Selecciona los registros que contienen en el campo un valor menor O igual a num

Ver Probar Ver Probar

<= Cadena WHEREcampo<="cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres son menores que los de la cadena, siendo n el número de caracteres que contiene cadena y añade respecto al caso anterior la opción de que en caso de que ambos valores fueran iguales también los presentaría (**)

Ver Probar Ver Probar

> Numérico WHEREcampo>num Selecciona los registros que contienen en el campo un valor mayor a num

Ver Probar Ver Probar

> Cadena WHEREcampo>"cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres son mayores que los de la cadena, siendo n el número de caracteres que contiene cadena. (**)

Ver Probar Ver Probar

Page 491: Manual PHP

>= Numérico WHEREcampo>=num Selecciona los registros que contienen en el campo un valor mayor o igual a num

Ver Probar Ver Probar

>= Cadena WHEREcampo>="cadena"

Selecciona los registros que contienen en el campo una cadena cuyos n primeros caracteres son mayores que los de la cadena, siendo n el número de caracteres que contiene cadena y añade respecto al caso anterior la opción de que en caso de que ambos valores fueran iguales también los presentaría (**)

Ver Probar Ver Probar

IN Numérico o Cadena

WHERE campoIN (valor1,valor2..)

Selecciona los registros que contienen en el campo valores que coinciden con alguno de los especificados dentro del paréntesis. Cuando se trata de valores no numéricoz han de ir entre comillas

Ver Probar Ver Probar

BETWEEN Numérico o Cadena

WHERE campoBETWEEN valor1 AND valor2

Selecciona los registros en los que los valores contenidos en el camposeleccionado están comprendidos en el intervalo valor1 (mínimo) – valor2(máximo) incluyendo en la selección ambos extremos. Cuando los contenidos de los campos son cadenas sigue los mismos criterios que se indican para los demás operadores de comparación

Ver Probar Ver Probar

IS NULL Cadena WHERE campo IS NULL Selecciona los registros en los que los valores contenidos en el camposeleccionado son NULOS

Ver Probar Ver Probar

IS NOT NULL

Cadena WHERE campo IS NOT NULL

Selecciona los registros en los que los valores contenidos en el camposeleccionado son NO NULOS

Ver Probar Ver Probar

(*) Cuando se trata de cadenas de caracteres, el concepto menor que significa anterior en la ordenación de los caracteres según su código ASCII y mayor quesignifica posterior en esa misma ordenación. (**) La discriminación de Mayúsculas/Minúsculas dependerá del tipo de campo. Recuerda que los tipo BLOB hacen esa discriminación, mientras que los de tipo TEXT son insensibles a Mayúsculas/Minúsculas.

Cuando se trata de comparar cadenas MySQL dispone de una potente instrucción (LIKE) que permite establecer los criterios de selección a toda o parte de la cadena. Su sintaxis contempla distintas posibilidades utilizando dos comodines>: % (que se comporta de forma similar al (*) en las búsquedas de Windows) y _ (de comportamiento similar a (?) en Windows). Aquí tienes algunas de sus posibilidades:

Sintaxis Descripción

Progamación por procesos

Programación orientada a objetos

Código fuente

Ver ejemplo

Código fuente

Ver ejemplo

WHERE campoLIKE '%cadena%'

Selecciona todos los registros que contengan la cadena en el campo indicado sea cual fuere su posición

Ver Probar Ver Probar

WHERE campoLIKE 'cadena%'

Selecciona todos los registros en los que el campo indicado que contengan la cadena exactamente alprincipio del campo

Ver Probar Ver Probar

WHERE campoLIKE '%cadena'

Selecciona todos los registros en los que el campo indicado que contengan la cadena exactamente al final del campo

Ver Probar Ver Probar

WHERE campoLIKE '_cadena%'

Selecciona todos los registros en los que el primer caracter del campo puede ser cualquiera pero los siguientes han de ser exactamente los indicados en cadena pudiendo ir seguidos de cualesquiera otros caracteres

Ver Probar Ver Probar

El comodín (_) puede ir tanto al principio como al final y puede repetirse tantas veces como sea necesario. Por tanto sería correctoLIKE '___es%' y también LIKE 'a___es%' así como: LIKE '%a___es'.

La claúsula WHERE aun tiene más opciones. Acepta múltiples condiciones vinculadas por los operadores lógicos AND, OR, NOT o sus sintaxis equivalentes: &&, || y !.

El comportamiento de estos operadores es idéntico al descrito para sus homónimos de PHP en esta página

Utilizando funciones sobre campos

Por medio de la sintaxis

Page 492: Manual PHP

SELECT MAX(campo1), MIN (campo2), ... FROM tabla

obtendríamos UNA SOLA FILA cuyos valores serían los resultados de la aplicación de las funciones a todos los registros del campo indicado. Aquí tienes un ejemplo que determina todos los valores de esos estadísticos aplicados al campo Contador de nuestra tabla demo4.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Aplicando la opción GROUP BY

Tal como señalamos al margen, las funciones anteriores pueden aplicarse a grupos de registros seleccionados mediante un criterioGROUP BY (nombre del campo)

En este ejemplo obtendremos los mismos parámetros estadísticos que en el anterior, pero ahora agrupados por sexo, lo que significaría que obtendremos dos filas de resultados. Aquí tienes el ejemplo

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

<

Manejo de fechas en las consultas

MySQL dispone de algunas cláusulas de gestión de fechas que pueden tener una gran utilidad a la hora de gestionar consultas. Son las siguientes:

DATE_FORMAT( campo,formato)

Las diferentes opciones de formato las tienes en la tabla. Es importante tener en cuenta que la sintaxis correcta es %Y (sin espacio) ya que si hubiera un espacio % Y interpretaría la letra Y como un texto a incluir.

CURDATE()

Dentro de DATE_FORMAT se puede incluir -en vez del nombre del campo- una cadena en la que se indique una fecha en formatoYYYY-MM-DD hh:mm:ss. Puedes verlo en los ejemplos. De igual modo es posible sustituir el nombre del campo -o la cadena- por la funciónCURDATE() que recoge la fecha actual del sistema (únicamente día, mes y año). A efectos de horas, minutos y segundos CURDATE() va a tomar el mediodía de la fecha actual.

CURTIME()

Se comporta de forma similar a CURDATE(). Devuelve la hora actual del sistema que alberga el servidor MySQL en formato hh:mm:ss

CURRENT_TIMESTAMP()

Se comporta de forma similar a CURDATE(). Devuelve la fecha y hora actual del sistema en formato YYYY-MM-DD hh:mm:ss

NOW()

Es un alias de CURRENT_TIMESTAMP().

Page 493: Manual PHP

Formato Descripción Sentencia MySQL

Progamación por procesos

Programación orientada a objetos

Código fuente

Ver ejemplo

Código fuente

Ver ejemplo

%d Día del mes en formato de dos dígitos DATE_FORMAT(Nacimiento,'%d') Ver Probar Ver Probar

%e Día del mes en formato de uno ó dos dígitos DATE_FORMAT(Nacimiento,'%e') Ver Probar Ver Probar

%D Número de día seguido del sufijo en inglés DATE_FORMAT(Nacimiento,'%D') Ver Probar Ver Probar

%m Número del mes en formato de dos dígitos DATE_FORMAT(Nacimiento,'%m') Ver Probar Ver Probar

%c Número del mes en formato de uno o dos dígitos DATE_FORMAT(Nacimiento,'%c') Ver Probar Ver Probar

%M Nombre del mes (en inglés) DATE_FORMAT(Nacimiento,'%M') Ver Probar Ver Probar

%b Nombre del mes abreviado (en inglés) DATE_FORMAT(Nacimiento,'%b') Ver Probar Ver Probar

%y Número del año en formato de dos dígitos DATE_FORMAT(Nacimiento,'%y') Ver Probar Ver Probar

%Y Número del año en formato de cuatro dígitos DATE_FORMAT(Nacimiento,'%Y') Ver Probar Ver Probar

%w Número de día de la semana 0=Domingo ... 6=Sábado DATE_FORMAT(Nacimiento,'%w') Ver Probar Ver Probar

%W Nombre del día de la semana (en inglés) DATE_FORMAT(Nacimiento,'%W') Ver Probar Ver Probar

%W Nombre abreviado del día de la semana (en inglés) DATE_FORMAT(Nacimiento,'%W') Ver Probar Ver Probar

%j Número de día del año en formato de 3 dígitos DATE_FORMAT(Nacimiento,'%j') Ver Probar Ver Probar

%U Número de semana del año considerando el DOMINGO como primer día de la semana (dos dígitos)

DATE_FORMAT(Nacimiento,'%U') Ver Probar Ver Probar

%u Número de semana del año considerando el LUNES como primer día de la semana (dos dígitos)

DATE_FORMAT(Nacimiento,'%u') Ver Probar Ver Probar

La fecha para los ejemplos siguientes la extraemos de una variable del tipo: $fecha="2005-10-12 14:23:42" ya que la tabla no contiene campos de fecha que incluyan horas, minutos y segundos

%H Hora con dos dígitos (formato 0 a 24 horas) DATE_FORMAT($fecha,'%H')

Ver Probar Ver Probar

%k Hora con uno ó dos dígitos (formato 0 a 24 horas) DATE_FORMAT($fecha,'%k')

%h Hora con dos dígitos (formato 0 a 12 horas) DATE_FORMAT($fecha,'%h')

%I Hora con uno ó dos dígitos (formato 0 a 12 horas) DATE_FORMAT($fecha,'%I')

%i Minutos con dos dígitos DATE_FORMAT($fecha,'%i')

%s Segundos con dos dígitos DATE_FORMAT($fecha,'%s')

%r Hora completa (HH:mm:ss) en formato de 12 horas indicando AM ó PM DATE_FORMAT($fecha,'%r')

%T Hora completa (HH:mm:ss) en formato de 24 horas DATE_FORMAT($fecha,'%T')

% texto Incluye el texto que se indica detrás del % DATE_FORMAT($fecha,'% texto')

%p Añade AM ó PM dependiendo de la Hora DATE_FORMAT($fecha,'%p')

Se pueden combinar a voluntad varias opciones utilizando una sintaxis de este tipo: '% Hoy es: %d - %m - %Y % es %W % estamos en el mes de %M % <br>y van transcurridos %j % dias de este año.<br>Son las %r'

Setencias múltiples

Cuando usamos mysqli podemos ejecutar múltiples sentencias en una sola instrucción. Para ello debemos empezar encadenando las sucesivas sentencias incluyéndolas una a continuación de otra y separándolas mediante un punto y coma (;).

SELECT * FROM tabla1 ; SELECT campo1 FROM tabla2 ; DROP TABLE IF EXISTS tabla3

La ejecución de este tipo de sentencias requiere la utilización de la función:

mysqli_multi_query($conexion,sentencia_multiple)

Page 494: Manual PHP

dónde, en el caso de programación mediante procesos, $conexion es el identificador de la conexión y sentencia_multiple el una sentencia múltiple en el formato comentado en el párrafo anterior. Devuelve un valor booleano (true o false).

Cuando se trata de programación orientada a objetos es preciso recurrir al método multi_query que mediante la sintaxis:

$objeto->multi_query(sentencia_multiple)

en la que sentencia_multiple es una sentencia múltiple en el formato comentado anteriormente. Devuelve un valor booleano (true o false).

¡Cuidado! Los mensajes de error sólo se visualizan en le caso de producirse este en la ejecución de la primera de las sentencias

Sentencias que no devuelven valores

Cuando se trata de crear, borrar o modificar tablas, lo habitual es que se realice el proceso sin necesidad de visualizar ningún resultado. Esos casos, aparentemente los más simples, podrían tratarse mediante de la forma que puedes ver en el código fuente de estos dos ejemplos de creación de tres tablas.

Código fuente mediante procesos

Código fuente mediante objetos

Pese a incluir un condicional para advertirnos de eventuales errores solo se activaría el mensaje de error en el caso de que este se produjese en la primera de las sentencias.

Sentencias que devuelven valores

Cuando se trata de sentencias que devuelven valores como ocurre con las consultas es necesario utilizar algunas de las funciones o métodos que comentamos a continuación

$resultado=mysqli_store_result($conexion) o $resultado=$objeto->store_result()

Transfiere a $resultado el conjunto de resultados de la última consulta en la conexión de base de datos. En el primer caso (usando procesos) se requiere el parámetro $conexion. En el segundo supuesto (orientado a objetos) el método no requiere ningún parámetro.

$resultado=mysqli_use_result($conexion) o $resultado=$objeto->use_result()

Se utiliza para iniciar la recuperación de un conjunto de resultados de la última consulta ejecutada. Aunque a primera vista pueda parecer muy similar a lo anterior hay una importantísima diferencia dado que no se transfiere el conjunto completo de resultados y por lo tanto no se pueden utilizar funciones como mysqli_data_seek () para pasar a un registro en particular dentro del conjunto.

Cuando cualquiera de las opciones (store o use) se ejecuta inmediatamente después de la ejecución de un multi_query recogerá los resultado de la primera de las sentencias que contiene.

mysqli_free_result($resultado) o $resultado->free()

Page 495: Manual PHP

Liberan la memoria ocupada por los resultados. Como en casos anteriores la primera sentencia se utiliza cuando se programa por procesos y la segunda cuando se hace por objetos. No es estrictamente necesario utilizarlos dado que toda la memoria utilizada por los resultados asociados se libera automáticamente al finalizar la ejecución del script. Sólo tiene utilidad práctica en aquellos casos en los que las consultas devuelven una cantidad de datos muy grande.

mysqli_more_results($conexion) o $objeto->more_results()

Comprueba si hay algún resultado más de una consulta múltiple. Devuelve un valor booleano

mysqli_next_result($conexion) o $objeto->next_result()

Prepara siguiente conjunto de resultados de una llamada previa a mysqli_multi_query (), que pueden ser recuperados por mysqli_store_result () o mysqli_use_result ().

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Creación de tablas a partir de la consulta de otra tabla

Es frecuente -podría decirse que es lo habitual- relacionar tablas mediante campos con idéntico contenido. Supongamos que entre los individuos de nuestra tabla demo4 se pretende establecer un proceso de selección para elegir entre ellos un número determinado deastronautas, pongamos por caso.

Supongamos también, que la selección va a constar de tres pruebas que serán juzgadas y calificadas por tres tribunales distintos.

Una primera opción sería crear tres tablas –una para cada tribunal– e incluir en ellas todos los datos de cada uno de los individuos. Esa opción es factible pero no es ni la más cómoda, ni tampoco es la más rápida ni la que menos espacio de almacenamiento necesita. No debemos olvidar que una tabla puede tener una enorme cantidad de registros.

Una opción alternativa sería crear tres nuevas tablas que sólo contuvieran dos campos cada una. Por ejemplo el campo DNI y el campo Calificación. Como quiera que el campo DNI ha de contener los mismos valores en las cuatro tablas y además es un campo únicopodrían crearse las nuevas tablas y luego copiar en cada una de ellas todos los DNI de la tabla original. Nos garantizaría que no habría errores en los DNI y además nos garantizaría que se incluyeran todos los aspirantes en esas nuevas tablas.

Aquí tienes el código fuente de un script que crea esas tres tablas (a las que hemos llamado demodat1, demodat2 y demodat3). Como podrás observar hemos utilizado una sentencia múltiple para crear las tablas y posteriormente hemos hecho las inserciones de datos en cada una de ellas.

Código fuente mediante procesos

Código fuente mediante objetos

Una consulta conjunta de varias tablas

MySQL permite realizar consultas simultáneas en registros situados en varias tablas.

Para ese menester se usa la siguiente sintaxis:

SELECT tabla1.campo1, tabla2.campo2, ... FROM tabla1, tabla2

Page 496: Manual PHP

en la que, como ves, modificamos ligeramente la sintaxis ya que anteponemos el nombre de la tabla al del campo correspondiente separando ambos nombres por un punto, con lo cual no hay posibilidad de error de identificación del campo incluso cuando campos de distinta tabla tengan el mismo nombre. Otra innovación -respecto a los ejemplos anteriores- es que detrás de la cláusula FROM escribimos los nombres de todas las tablas que está usando SELECT. A partir de ahí se pueden establecer todo tipo de relaciones para las sentencias WHERE, ORDER BY y GROUP BY utilizando para ellocampos de cualquiera de las tablas sin otra particularidad más que poner cuidado al aludir a los campos utilizando siempre la sintaxisnombre_tabla.nombre_campo. A modo de ejemplo –hemos procurado comentarlo línea a línea– aquí tienes un script PHP que hace una consulta conjunta de las tablasdemo4, demodat1, demodat2 y demodat3 y nos presenta una tabla con los datos personales y las puntuaciones de las tres pruebas así como las suma de puntos de las tres y, además, ordena los resultados -de mayor a menor- según la suma de las tres puntuaciones.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Consultas preparadas (MySQLi)

Declaraciones preparadas (Prepare statement)

Mediante MySQLi disponemos de una nueva alternativa de consultas que conviene conocer. Requiere realizar el proceso de consulta en los dos pasos siguientes:

$actuacion=mysqli_prepare($conexion,SENTENCIA) o $actuacion= $objeto->prepare(SENTENCIA)

El método prepare prepara la SENTENCIA para su ejecución devolviendo el resultado como un objeto $actuacion. Este método no ejecuta la sentencia, solo la prepara.

mysqli_stmt_execute($actuacion) o $actuacion-> execute()

El método execute es propio de los objetos resultantes –$actuacion – de la preparación una sentencia. Devuelve el objeto resultado de la ejecución de esa sentencia. Este objeto permite extraer los resultados mediante:

$resultado=mysqli_stmt_get_result($actuacion) o $resultado=$actuacion->get_result()

El método get_result() cuando se trata de objetos o su equivalente mysqli_stmt_get_result producen un bloque de información idéntico al que se podría obtener mediante el método query o su equivalente mysqli_query. Por tanto es susceptible de ser leido mediante cualquiera de los procedimientos comentados en el epígrafe Los resultados de la sentencia SELECT de la página anterior. Aquí tienes un ejemplo en ambas versiones.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Page 497: Manual PHP

Existe una opción alternativa de lectura del objeto resultante de la ejecución del método execute. Se trata de utilizar una de las siguientes funciones:

mysqli_stmt_bind_result($actuacion, $var1, $var2, $var3, ...) o $actuacion->bind_result($var1, $var2, $var3, ...)

dónde $actuacion es el nombre del objeto resultante del método execute (o de mysqli_execute) $var1, $var2, $var3,.. son nombres válidos cualesquiera de variables PHP a través de las que se vincularán los campos de la consulta. Han de crearse –obligatoriamente– tantas variables como campos intervengan en la consulta.

Las funciones anteriores organizan los resultados de la consulta por registros (filas) que pueden ser leidas secuencialmente de una en una y a través de llamadas reiterativas por medio de una de estas funciones:

mysqli_stmt_fetch($actuacion) o $actuacion->fetch()

que irán asignando a las variables $var1, $var2, $var3,.. los valores de los campos correspondientes al registro contenido en la fila actual. La lectura comienza por el primer registro resultante de la consulta y continúa secuencialmente (la lectura completa requiere la realización de un bucle tal como puedes ver en los ejemplos) en las sucesivas llamadas a estas funciones. Como es obvio, en cada iteración se modificarán los valores de las variables $var1, $var2, $var3,...

Las páginas oficiales de PHP justifican la utilización de este procedimiento cuando se trate de ahorrar recursos de memoria (el resultado de la consulta no es almacenado en ningún buffer) y, a la vez, admiten una mayor lentitud en el procesamiento de la información

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

A la sintaxis ya conocida:

$actuacion= $objeto->prepare(SENTENCIA) $actuacion-> execute()

puede hacérsele una modificación incluyendo entre ambas un nuevo método (bind_param) que tiene como finalidad enlazar el valor de una variable con un identificador incluido en la sentencia SQL. Una sintaxis como esta:

$actuacion=mysqli_prepare($conexion,'SELECT * FROM tabla WHERE (Nombre=? and Apellido1=? and edad=?)') mysqli_stmt_bind_param($actuacion,'ssi', $var1, $var2, $var3); $var1='Jose'; $var2='Alonso'; $var3=25; mysqli_stmt_execute($actuacion) o $actuacion= $objeto->prepare('SELECT * FROM tabla WHERE (Nombre=? and Apellido1=? and edad=?)') $actuacion-> bind_param('ssi', $var1, $var2, $var3); $var1='Jose'; $var2='Alonso'; $var3=25; $actuacion-> execute()

en la que hacemos las siguientes modificaciones:

� – En la sentencia SQL sustituimos por ? lo que anteriormente eran cadenas o nombres de variables (entre comillas por su condición de cadenas alfanuméricas) del tipo '$nombre' y '$apellido' y números o variables del tipo $edad (sin comillas por su condición de valor numérico).

� – Agregamos una llamada al método bind_param con los siguientes parámetros obligatorios:

Page 498: Manual PHP

� • El primero de ellos es una cadena formada por identificadores de tipo de los valores que habrán de reemplazar a los signos de interrogación. Los identificadores de tipo (la cadena debe incluir tantos como signos de interrogación contenga la sentencia MySQL) son los siguientes:

� • i – La variable es de tipo entero

� • d – La variable es numérica double

� • s – La variable es de tipo cadena

� • b – La variable es de tipo blob

� • Los señalados como $var1, $var2, $var3, etcétera son los nombres de las variables que habrán de estar definidas antes de invocar el método execute y que contienen los valores que ha de sustituir a los sucesivos ? de la sentencia. Hemos de tener en cuenta que han de incluirse siempre tantos nombres de variable como interrogantes existan en la sentencia.

� – El método execute produciría los resultados de la ejecución de la sentencia después de haber reemplazado los ? por los valores de la variable.

Veamos un primer ejemplo en el que las variables son de tipo numérico

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

En este otro ejemplo ya hemos incluido variables tanto numéricas como cadena y dónde conjugamos el uso de bind_param con el de bind_result.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Modificar registros (MySQLi)

Sintaxis MySQL de modificación de registros

Las sentencias MySQL que permiten la modificación de registros en las tablas son idénticas a ls ya comentadas con anterioridad y tal como decíamos entonces pueden incluir algunas de las siguientes cláusulas que, al igual que ocurría en casos anteriores, pueden tener categoría de obligatorias u opcionales.

El orden en que deben estar indicadas ha de seguir la misma secuencia en la que están descritas aquí.

� UPDATE Tiene carácter obligatorio, debe ser la primera palabra de la sentencia e indica a MySQL que vamos realizar una modificación.

� LOW_PRIORITY Es opcional e indica a MySQL espere a que se terminen de hacer las consultas que en ese momento pudiera haber en proceso antes realizar la actualización.

� IGNORE Es opcional. Cuando se incluye en una sentencia el proceso de actualización no se interrumpe si aparece un conflicto de clave duplicada en uno de los registros en proceso. Simplemente ignora ese registro y continúa con los siguientes Si no se incluye, el proceso de modificación se interrumpe en el momento en que encuentre un conflicto de clave duplicada. Tanto con ignore como sin esa cláusula, en el caso de duplicidad de clave NUNCA se efectúan las modificaciones.

� tabla Es obligatoria y contiene el nombre de la tabla que pretendemos modificar.

� SET Tiene carácter obligatorio y debe estar delante de las definiciones de campo y valor.

� campo1 = valor1 Es obligatoria al menos una definición. Indica el nombre del campo a modificar (campo1) y el valor que se asignará a ese campo. Si se pretende modificar más de un campo se repetirá esta definición tantas veces como sea necesario, separando cada una de ellas por una coma.

� WHERE Es un campo opcional y su comportamiento es idéntico al señalado al mencionar el proceso de consultas.

Page 499: Manual PHP

� ORDER BY Tiene idéntica funcionalidad a la descrita al referirnos a consultas

Modificar un campo en todos los registros de una tabla

La sentencia MySQL, que permite modificar uno o varios campos en todos los registros de una tabla, es la siguiente:

UPDATE tabla SET campo1=valor1, campo2=valor2 ¡Cuidado! Hay que tener muy presente que con esta sentencia –en la que no aparece WHERE– se modificarán TODOS LOS REGISTROS DE LA TABLA y por lo tanto los campos modificados tendrán el mismo valor en todos los registros.

Algunas consideraciones sobre la sintaxis

Siempre que manejes PHP y MySQL debes tener muy presente lo siguiente:

� – MySQL requiere SIEMPRE que los valores tipo cadena que incluyen campos de fecha vayan entre comillas. Por el contrario, losnuméricos no deben llevar comillas.

� – Presta mucha atención a esto cuando escribas los valores directamente en la sentencia MySQL.

� –Cuando pases valores desde una variable PHP debes tener muy en cuenta las consideraciones anteriores y si el contenido de la variable es una cadena que va a ser tratada como tal por MySQL tienes dos opciones para evitar el error:

� • Definir la variable así: $variable ="'valor'" (comillas dobles, comilla simple al principio y comilla simple, comilla doble al final) y poner en la sentencia MySQL el nombre de la variable sin entrecomillar, o

� • Definir la variable PHP así: $variable ="valor" y al escribir el nombre de esa variable en la sentencia MySQL escribirlo entre comillas sencillas, es decir, así: '$variable'

� – No pienses que es caprichoso el orden que hemos puesto en las comillas. Recuerda que al llamar a la sentencia MySQL, el contenido de la sentencia va entre comillas (que por costumbre son comillas dobles, por esa razón todo entrecomillado que vaya dentro de esa sentencia ha de usar comillas simples para evitar un error seguro). De ahí que al definir una variable PHP en la forma $variable ="'valor'" las comillas dobles exteriores indican a PHP que se trata de una cadena, por lo que, al pasar la variable a MySQL éste recibirá el contenido de la cadena que es, logicamente: 'valor' y en este caso las comillas forman parte del valor, razón por el que no es necesario escribir -en la sentencia MySQL- el nombre de la variable entrecomillado.

En este primer ejemplo, hemos incluido una actualización de tablas que pondrá puntuación 7 en la primera de las pruebas a todos losaspirantes a astronautas de nuestro ejemplo. Es un caso de actualización sin la condición WHERE y tiene el código comentado.

Consulta mediante procesos

Ver código fuente

Consulta mediante objetos

Ver código fuente

Recuento de resultados

El recuento de los resultados de la ejecución de una sentencia tiene dos opciones que dependen del tipo de esta. Cuando se trata de sentencias que no producen modificación en los contenidos (caso de las consultas) el recuento de resultados utiliza las funciones y sintaxis ya conocidas de

$num_resultados=mysqli_num_rows($resultado) o $num_resultados=$resultado->num_rows

En ambos casos la variables $num_resultados recogerá el número de registros (filas) obtenidos como resultado de la consulta. La primera de las opciones se utiliza cuando se gestiona la consulta mediante procesos mientras que es segundo corresponde a consultas mediante objetos.

Page 500: Manual PHP

Cuando se trata de sentencias que sí producen modificación en los contenidos (altas, modificaciones o borrado de registros) el recuento de resultados requiere de esta sintaxis:

$cuenta_resultados=mysqli_affected_rows($conexion) o $cuenta_resultados=$objetoMySQLi->affected_rows

En el primer caso –programación mediante procesos– se utiliza la función mysqli_affected_rows con el parámetro identificador de conexión: $conexion

En el segundo supuesto –programación orientada a objetos– se utiliza la propiedad affected_rows que es la que contiene el valor de ese resultado.

Selección y modificación de un solo registro

Es una de las opciones más habituales. Es el caso en el que –mediante un formulario– asignamos una condición a WHERE y simultáneamente asignamos los nuevos valor del campo o campos elegidos. Requiere la siguiente sintaxis:

UPDATE tabla SET campo1=valor1, campo2=valor2 WHERE condición

La condición es fundamental en esta opción y normalmente aludirá a un campo índice (clave principal o única), de modo que sea un solo registro el que cumpla la condición. Podría ser el caso, en nuestro ejemplo, del campo DNI que por su unicidad garantizaría que la modificación solamente va a afectar a uno solo de los registros.

El ejemplo siguiente nos permitirá hacer modificaciones de este tipo en la tabla deomodat2. Observa el código fuente y verás que mediante un simple recurso JavaScript, el script que realiza la modificación nos reenvía al formulario con un mensaje de confirmación de la modificación. En este primer caso utilizaremos las instrucciones de modificación mediante procesos

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Aquí tienes el mismo ejemplo del supuesto anterior modificando a programación mediante objetos el tratamientos de las instrucciones de gestión de la base de datos

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Modificación simúltanea de un campo en cualquiera de los registros Aquí tienes un ejemplo que permite visualizar el valor actual de todas las puntuaciones de la prueba 2 de los astronautas así como sus nombres y apellidos y DNI y en la cual se pueden modificar ninguno, uno, varios o todos los valores y posteriormente actualizarlos todos con los nuevos valores. Igual que en el caso anterior, en este primer ejemplo efectuaremos las modificaciones tratando mysqli mediante procesos mientra que en el segundo supuesto lo hacemos mediante programación orientada a objetos.

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Ejecutar la modificacción Ver código «formulario» Ver código «script»

Imágenes en tablas (MySQLi)

Imágenes en tablas

Peculiaridades de las tablas

Las tablas que han de contener imágenes deben tener campos del tipo BLOB, MEDIUMBLOB o LONGBLOB, (recuerda sus tipos) pudiendo elegir aquel de ellos que más se adecue al tamaño, en bytes, de las imágenes que se desean guardar en la tabla.

Page 501: Manual PHP

En el ejemplo hemos incluido un campo BLOB para incluir la imagen, insertando también campos para recoger su nombre, su tamaño (en bytes), su formato (el tipo de fichero transferido) así como un campo autoincremental. No es otra cosa que el ejemplo incluido en esta página adaptado la nueva extensión de comunicación entre PHP y MySQL.

<?php #definimos una variable con el NOMBRE QUE QUEREMOS DAR A LA TABLA $tabla="fotos"; /* incluimos los datos de conexión al servidor MySQL */ include("mysqli.inc.php"); /****** Programación por procesos ***********/ #conexion y seleccion de base de datos $conexion=@mysqli_connect($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); #escribimos la sentencia que crea la tabla $crear="CREATE TABLE IF NOT EXISTS $tabla ("; $crear.="num_ident INT(10) unsigned NOT NULL AUTO_INCREMENT,"; $crear.="imagen BLOB NOT NULL, "; $crear.="nombre VARCHAR(255) NOT NULL DEFAULT '',"; $crear.="tamano VARCHAR(15) NOT NULL DEFAULT '',"; $crear.="formato VARCHAR(10) NOT NULL DEFAULT '',"; $crear.="PRIMARY KEY (num_ident))type=MyISAM"; #Creamos la cadena, comprobamos si esa instrucción devuelve # VERDADERO o FALSO # y dependiendo de ellos insertamos el mensaje de exito o fracaso if(mysqli_query ($conexion,$crear)) { echo "<h2> Tabla $tabla creada con EXITO </h2><br>"; }else{ echo "<h2> La tabla $tabla NO HA PODIDO CREARSE</h2><br>"; }; # cerramos la conexión... y listo... mysqli_close($conexion); ?>

Aquí tiene el código fuente que te permitiría crear la tabla fotos e insertar automáticamente algunas imágenes de ejemplo.

Mediante procesos Mediante objetos

Transferencia de la imagen

El formulario para realizar la transferencia de la imagen no tiene particularidades. Es un formulario habitual para transferencia de ficheros. Lo único reseñable sería incluir un campo oculto en el que pudiera especificarse una restricción en cuanto al tamaño máximo permitido para cada imagen y que debe estar acorde con el tipo de campo utilizado en la tabla.

<FORM ENCTYPE="multipart/form-data" ACTION="ejemplo707.php" METHOD="post"> <INPUT type="hidden" name="lim_tamano" value="65000"> <p><b>Selecciona la imagen a transferir<b><br> <INPUT type="file" name="foto"><br> <p><b>Título la imagen<b><br> <INPUT type="text" name="titulo"><br></p> <p><INPUT type="submit" name="enviar" value="Aceptar"></p> </FORM>

Ejemplo de transferencia de imagen

Comprobación del tipo de imagen

Page 502: Manual PHP

Al transferir imágenes jpg ó png el type MIME que recibía el servidor es distinto según el navegador que se utilice para hacer la transferencia.

Aquí debajo, en el código fuente del script que actualiza la base de datos, tienes los nombres de esos tipos asociados a los navegadores más usuales.

<?php $foto_name= $_FILES['foto']['name']; $foto_size= $_FILES['foto']['size']; $foto_type= $_FILES['foto']['type']; $foto_temporal= $_FILES['foto']['tmp_name']; $lim_tamano= $_POST['lim_tamano']; $foto_titulo= $_POST['titulo']; /* limitamos los formatos de imagen admitidos a: png que segun del navegador que ulicemos puede ser: en IE image/x-png en Firefox y Mozilla image/png jpg que puede tener como tipo en IE image/pjpeg en Firefox y Mozilla image/jpeg gif que tiene como tipo image/gif en todos los navegadores Mira los comentarios al margen sobre la variable $extensión */ if ($foto_type=="image/x-png" OR $foto_type=="image/png"){ $extension="image/png"; } if ($foto_type=="image/pjpeg" OR $foto_type=="image/jpeg"){ $extension="image/jpeg"; } if ($foto_type=="image/gif" OR $foto_type=="image/gif"){ $extension="image/gif"; } # condicionamos la inserción a que la foto tenga nombre, # un tamaño distinto de cero y menor de límite establecido # en el formulario y que la variable extensión sea no nula if ($foto_name != "" AND $foto_size != 0 AND $foto_titulo !='' AND $foto_size<=$lim_tamano AND $extension !=''){ /*reconversion de la imagen para meter en la tabla abrimos el fichero temporal en modo lectura "r" binaria"b"*/ $f1= fopen($foto_temporal,"rb"); #leemos el fichero completo limitando # la lectura al tamaño de fichero $foto_reconvertida = fread($f1, $foto_size); #anteponemos \ a las comillas que pudiera contener el fichero # para evitar que sean interpretadas como final de cadena $foto_reconvertida=addslashes($foto_reconvertida); # abrimos la base de datos y escribimos las intrucciones de inserción # en el campo BLOB insertaremos la foto_reconvertida $tabla="fotos"; include("mysqli.inc.php"); $conexion=@mysqli_connect($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $meter="INSERT INTO ".$tabla; $meter .=" (num_ident, imagen, nombre, tamano, formato) "; $meter .=" VALUES('','$foto_reconvertida','$foto_titulo',"; $meter .= "$foto_size, '$extension')"; if (@mysqli_query($conexion,$meter)){ print "Foto guardada en la tabla"; }else{ print "Ha habido un error al guardar la foto"; } }else{ echo "<h2>No ha podido transferirse el fichero</h2>";

Page 503: Manual PHP

} mysqli_close($conexion); ?>

Hay otro aspecto a tener en cuenta. Esa discriminación de tipos se plantea únicamente cuando Apache recibe una transferencia. Cuando se visualiza un contenido las cabeceras tipo de contenido (header("content-type: xx")) pueden ser las mismas para todos los navegadores. Esa es la razón por la que a la hora de incluir el formato en la tabla utilizamos image/jpg, image/gif o image/png.

¿Cómo guardamos la imagen?

La información recibida a través del formulario requiere un ligero retoque antes de incluirla en le campo BLOB de la tabla. Esa reconversión requiere abrir la imagen en modo binario (rb) -parece que solo en el caso de Windows– leer el fichero completo y añadirle \ antes de las comillas mediante addslashes.

Una vez hecho el retoque ya puede guardarse sin más problema.

PNG con transparencias en Internet Explorer

Internet Explorer no permite visualizar de forma automática las transparencias de las imágenes con formato png. Existen en la redalgunos recursos que permiten solventar ese problema. Hemos elegido uno de ellos –pngfix.js- que puedes ver en este enlace.

Se trata de un fichero JavaScript que basta incluir en la cabecera HMTL de la página de la forma que ves en el ejemplo. Cuando un navegador IE es detectado se ejecuta una función contenida en ese fichero que analiza la página, busca imágenes con extensión png y les aplica la transparencia adecuada.

Por esa razón, es probable que inicialmente (al cargar la página) se visualice la imagen opaca y que, posteriormente, adquiera la transparencia.

Visualización de imágenes

La lectura de una imagen utiliza solo dos instrucciones. Incluir la cabecera Header en el que se indica el tipo de contenido (el famoso nombre MIME de la imagen) y luego imprimir el contenido del campo. Pero (por aquello de que header debe ir incluida en el script antes que cualquier otra salida) si pretendemos incluir en una página algo más que una imagen tendremos que invocar esas dos funciones, de forma independiente, para cada una de ellas.

Por esa razón al desarrollar el ejemplo que permite visualizar todas las imágenes de la tabla hemos tenido que incluir un script que va leyendo la tabla que contiene las imágenes para extraer los campos informativos y a la hora de ver la imagen hemos de recurrir a la misma técnica que se utilizaba para ver las imágenes dinámicas. Es decir, poner una etiqueta de imagen de las de HTML pero -en vez de escribir el nombre de la imagen- poniendo incluyendo como nombre el del script que las visualiza y pasándole el número (valor del campo autoincremental) de la imagen que pretendí visualizar.

<?php $numero=$_REQUEST['n']; $tabla="fotos1"; include("mysqli.inc.php"); $conexion=@mysqli_connect($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $sacar = "SELECT * FROM ".$tabla." WHERE (num_ident=$numero)" ; $resultado = mysqli_query($conexion,$sacar); while ($registro = mysqli_fetch_assoc($resultado)){ $tipo_foto=$registro['formato']; header("Content-type: $tipo_foto"); echo $registro['imagen']; } mysqli_close($conexion); ?>

Ver imágenes guardadas

El problema de los PNG en IE

Page 504: Manual PHP

El JavaScript que asigna la transparencia a las imágenes en formato png las identifica buscando la coincidencia de los tres últimos caracteres del nombre de la imagen con la extensión png.

Cuando se trata de imágenes dinámicas el nombre de la imagen coinciden con el nombre de la llamada al script que se utiliza para su visualización. Por eso, para advertir a JavaScript de que se trata de una imagen png hemos incluido el condicional que puedes ver en el ejemplo. De esa forma, cuando se trata de una imagen en este formato incluimos en la petición una variable con el valor png de forma que pueda ser reconocida por pngfix.js y aplicada la transparencia requerida. Script para leer la base de datos <html> <head> <!-- al margen te comentamos la razón por la que --> <!-- se incluyen estas líneas en rojo --> <!--[if IE ]> <script type="text/javascript" src="pngfix.js"></script> <![endif]--> </head> <body> <? $base="ejemplos"; $tabla="fotos1"; include("mysqli.inc.php"); $conexion=mysqli_connect($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); $sacar = "SELECT * FROM ".$tabla; $resultado = mysqli_query($conexion,$sacar); while ($registro = mysqli_fetch_assoc($resultado)){ print "<center>Titulo de la imagen: ".$registro['nombre']."<br>"; /* la inclusión de este condicional obedece a los problemas que plantea la visualización de las transparencias de las imágenes png en Internet Explorer. Al margen justificamos las razones de su inclusión */ if($registro['formato']=="image/png"){ print "<img src='ver_foto.php?n=".$registro['num_ident']."&v=png'><br>"; }else{ print "<img src='ver_foto.php?n=".$registro['num_ident']."'><br>"; } print "Tamaño de la imagen: ".$registro['tamano']." bytes </center>"; } mysql_close(); ?> </body> </html>

Ver imágenes guardadas

Tablas InnoDB (MySQLi)

Las tablas InnoDB

Creación de una tabla InnoDB

La creación de tablas de este tipo no presenta ninguna dificultad añadida. El proceso es idéntico a las tablas habituales sin más que añadir Type=InnoDB después de cerrar el paréntesis de la sentencia de creación de la tabla.

Este script crea una tabla InnoDB con idénticos campos a los utilizados en el caso de la tabla MyISAM que hemos visto en páginas anteriores. La sintaxis, muy similar a la utilizada allí es esta:

<?php #definimos (en minusculas) el nombre de la nueva tabla $tabla="demoinno";

Page 505: Manual PHP

# escribimos la cadena que contiene la sentencia de creación de la nueva tabla $crear="CREATE TABLE $tabla ("; $crear.="Contador TINYINT(8) UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT,"; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, "; $crear.="Apellido2 VARCHAR (15) not null, "; $crear.="Nacimiento DATE DEFAULT '1970-12-21', "; $crear.="Hora TIME DEFAULT '00:00:00', "; $crear.="Sexo Enum('M','F') DEFAULT 'M' not null, "; $crear.="Fumador CHAR(0) , "; $crear.="Idiomas SET(' Castellano',' Francés','Inglés', ' Alemán',' Búlgaro',' Chino'), "; $crear.=" PRIMARY KEY(DNI), "; $crear.=" UNIQUE auto (Contador)"; $crear.=")"; # esta es la única diferencia con el proceso de # creación de tablas MyISAM $crear.=" Type=InnoDB"; /* tenemos completa la sentencia MYSQL solo falta ejecutarla crear la conexión y ejecutarla */ /*incluimos los parámetros de conexión */ include('mysqli.inc.php'); /* incluimos dos opciones de proceso y de programacion orientada a objetos. Podemos ejecutar los ejemplos pasado mediante el metodo GET el tipo elegido. */ if(!empty ($_GET['tipo'])){ $tipo_script=$_GET['tipo']; }else{ $tipo_script="objetos"; } /*En caso de tipo proceso utilizaríamos esto */ if ($tipo_script=="proceso"){ /****** Programación mediante procesos ***********/ #conexion, selección de base de datos y verificacion de errores segun ejemplos anteriores $conexion=@mysqli_connect ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); if(!mysqli_connect_errno()==0){ print "<Mbr>No ha podido realizarse la conexión mediante procesos<br>"; print "Error número: ". mysqli_connect_errno()." equivalente a: ". mysqli_connect_error(); exit(); } # Creación. Los parámetros requieren el orden aquí indicado al revés del caso mysql if(mysqli_query($conexion,$crear)){ print "La tabla ha sido CREADA"; }else{ print "<br>No ha podido crearse la base de datos mediante procesos<br>"; print "Error : ". mysqli_error($conexion); exit(); } mysqli_close($conexion); } /* en caso de programacion orientada a objetos */

Page 506: Manual PHP

if ($tipo_script=="objetos"){ #conexion, selección de base de datos y verificacion de errores segun ejemplos anteriores $objeto=@new mysqli ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); if(!$objeto->connect_errno==0){ print " No ha podido realizarse la conexión mediante objetos<br>"; print "Error número: ". $objeto->connect_errno." equivalente a: ". $objeto->connect_error; exit(); } if($objeto->query($crear)){ print "La tabla ha sido CREADA"; }else{ print "<br>No ha podido crearse la tabla mediante objetos<br>"; print "Error : ". $objeto->error; exit(); } $objeto->close(); } ?>

¡Cuidado! Bajo Windows, al crear una base de datos o tabla InnoDB el nombre de la misma aparecerá en minúsculas independientemente de la sintaxis que hayamos utilizado en su creación. Para prever posibles conflictos al ejecutar los scripts en diferentes plataformas utilizaremos minúsculas para los nombres de tablas.

Una vez creadas, las tablas InnoDB se comportan –a efectos de uso– exactamente igual que las que hemos venido utilizando en las páginas anteriores. No es preciso hacer ningún tipo de modificación en la sintaxis. Por tanto, es totalmente válido todo lo ya comentado respecto a: altas, modificaciones, consultas y bajas.

Las transacciones

Uno de los riesgos que se plantean en la gestión de bases de datos es que pueda producirse una interrupción del proceso mientras se está actualizando una o varias tablas. Pongamos como ejemplo el cobro de nuestra nómina. Son necesarias dos anotaciones simultáneas: El cargo en la cuenta del organismo pagador y el abono en nuestra cuenta bancaria. Si se interrumpiera fortuitamente el proceso en el intermedio de las dos operaciones podría darse la circunstancia de que apareciera registrado el pago sin que se llegaran a anotar los haberes en nuestra cuenta.

Las transacciones evitan este tipo de situaciones ya que los datos se registran de manera provisional y no se consolidan hasta que una instrucción confirme que esas anotaciones tienen carácter definitivo. Hemos de tener muy presente que para utilizar este tipo de opciones es imprescindible que las tablas sean del tipo InnoDB.

Sintaxis de las transacciones

Hay dos palabras que son claves para este tipo acciones. Commit, cuya traducción puede ser ejecutar y rollback que significa algo como no ejecutar. Cuando se trata de la extensión mysqli hay que tomar en consideración una tercera palabra, también clave, que esautocommit cuyo significado podría ser algo parecido a ejecutar de forma autómatica y que es configuración por defecto de la extensión mysqli.

Una buena opción es conocer el estado actual de la opción autocommit. Si su valor es TRUE significa que cualquier llamada amysql_query, $obejeto->query, mysqli_stmt_execute o $actuacion-> execute() se ejecutaría de forma inmediata e irreversible. Por el contrario, si su valor fuera FALSE las instrucciones anteriores se ejecutarían de forma provisional pero no serían confirmadas hasta que se incluyera una orden commit o fueran descartadas por medio de una orden rollback.

Para conocer el estado actual de autocommit hemos de ejecutar la sentencia MySQL siguiente:

Page 507: Manual PHP

SELECT @@autocommit

qué siguiendo el tratamiento clásico de las consultas nos devolverá el valor TRUE (1) o FALSE (0) de su estado actual. Su valor por defecto es TRUE y las eventuales modificaciones que podamos hacer en nuestros script solo persisten hasta la finalización de la ejecución del mismo. La modificación de su estado requiere de una de estas funciones:

mysqli_autocommit($conexion,BOOLEANO ) o $objeto->autocommit(BOOLEANO )

Siendo $conexion el identificador de conexión (programación mediante procesos) y $objeto el identificador del objeto mysqli (programación orientada a objetos. El valor BOOLEANO puede ser TRUE o FALSE. Como es lógico para poder utilizar las transacciones es necesario que ese valor sea FALSE.

Una vez configurado autocommit como false las sucesivas sentencias, sean simples, múltiples como preparadas, no se registrarán en la tabla hasta que no se incluya una orden explícita de hacerlo. Esa orden requiere el uso de una de estás dos funciones:

mysqli_commit($conexion ) o $objeto->commit()

sentencias que advierte a MySQL (en modo procesos y objetos respectivamente) que ha finalizado la transacción y que SÍ debe hacer efectivos todos los cambios incluidos en ella. Si se optara por NO hacer efectivos los cambios habría que utilizar:

mysqli_rollback($conexion ) o $objeto->rollback()

Precauciones a tener en cuenta

Cuando se utilizan campos autoincrementales en tablas InnoDB los contadores se van incrementando al añadir registros (incluso de forma provisional) con lo cual si se aborta la inclusión con un ROLLBACK ese contador mantiene el incremento y en inserciones posteriores partirá de ese valor acumulado.

Por ejemplo. Si partimos de una tabla vacía y hacemos una transacción de dos registros (número 1 y número 2 en el campo autoincremental) y la finalizamos con ROLLBACK, no se insertarán pero en una inserción posterior el contador autoincremental comenzará a partir del valor 2.

¡Cuidado! Cuando una instrucción modifica la condición de autocommit ese valor no recupera su valor por defecto (true) hasta que no se incluya una modificación explícita de su valor o hasta que finalice la ejecución del script actual.

Aquí tienes unos ejemplos de uso de transacciones

Ejecutar Modo procesos

Ver código fuente Modo procesos

Ejecutar Orientado a objetos

Ver código fuente Orientado a objetos

La integridad referencial

Elementos necesarios para la integridad referencial

Page 508: Manual PHP

La integridad referencial ha de establecerse siempre entre dos tablas. Una de ellas ha de comportarse como tabla principal (suele llamarse tabla padre y la otra sería la tabla vinculada ó tabla hijo. Es imprescindible:

� Que la tabla principal tenga un índice primario (PRIMARY KEY)

� Que la tabla vinculada tenga un índice (no es necesario que sea ni único ni primario) asociado a campos de tipo idéntico a los que se usen para índice de la tabla principal.

Si observas el código fuente del ejemplo que tienes aquí debajo podrás observar que utilizamos el número del DNI (único para alumno) como elemento de vinculación de la tabla de datos personales con la que incluye las direcciones.

Integridad referencial en tablas InnoDB

Cuando se trabaja con varias tablas que tienen algún tipo de vínculo resulta interesante disponer de mecanismos que protejan o impidan acciones no deseadas. Supongamos, como veremos en los ejemplos posteriores que pretendemos utilizar una tabla con datos de alumnos y otra tabla distinta para las calificaciones de esos alumnos. Si no tomamos ninguna precaución (bien sea mediante los script o mediante el diseño de las tablas) podría darse la circunstancia de que incluyéramos calificaciones a alumnos inexistentes, en materias de las que no están matriculados, etcétera. También podría darse la circunstancia de que diéramos de baja a un alumno pero que se mantuvieran las calificaciones vinculadas a él. Todas estas circunstancias suelen producir efectos indeseados y las tablas InnoDB pueden ser diseñadas para prever este tipo de situaciones.

Sintaxis para la vinculación de tablas

Los vínculos entre tablas suelen establecer en el momento de la creación de la tabla vinculada.

CREATE TABLE tabla (campo1, campo2,... KEY nombre (campo de vinculacion ), FOREIGN KEY (campo de vinculacion) REFERENCES nombre_de la tabla principal (Indice primario de la tabla principal) ) Type=InnoDB

donde el campo de vinculacion ha de ser un índice (no es necesario que sea PRIMARY KEY ni UNIQUE) y donde Indice primario de la tabla principal ha de ser un índice primario (PRIMARY KEY) de la tabla principal. Debe haber plena coincidencia (tanto en tipos como contenidos) entre ambos índices.

<?php $tabla1="principal"; $tabla2="vinculada"; /*incluimos los parámetros de conexión */ include('mysqli.inc.php'); /****** Programación mediante procesos ***********/ #conexion, selección de base de datos y verificacion de errores segun ejemplos anteriores $conexion=@mysqli_connect ($cfg_servidor,$cfg_usuario,$cfg_password,$cfg_basephp1); # creación de la tabla principal type InnoDB $crear="CREATE TABLE IF NOT EXISTS $tabla1 ("; $crear.="DNI CHAR(8) NOT NULL, "; $crear.="Nombre VARCHAR (20) NOT NULL, "; $crear.="Apellido1 VARCHAR (15) not null, "; $crear.="Apellido2 VARCHAR (15) not null, "; # el indice primario es imprescindible. Recuerda que debe # estar definido sobre campos NO NULOS $crear.=" PRIMARY KEY(DNI) "; $crear.=")"; $crear.=" Type=InnoDB"; # creamos la tabla principal comprobando el resultado if(@mysqli_query ($conexion,$crear)){ print "La tabla ".$tabla1." ha sido creada<br>"; }else{ print "No se ha creado ".$tabla1." ha habido un error<br>";

Page 509: Manual PHP

} # crearemos la tabla vinculada $crear1="CREATE TABLE IF NOT EXISTS $tabla2 ("; $crear1.="IDENTIDAD CHAR(8) NOT NULL, "; $crear1.="calle VARCHAR (20), "; $crear1.="poblacion VARCHAR (20), "; $crear1.="distrito VARCHAR(5), "; # creamos el índice (lo llamamos asociador) para la vinculación # en este caso no será ni primario ni único # Observa que el campo IDENTIDAD de esta tabla CHAR(8) # es idéntico al campo DNI de la tabla principal $crear1.=" KEY asociador(IDENTIDAD), "; #establecemos la vinculación de ambos índices $crear1.=" FOREIGN KEY (IDENTIDAD) REFERENCES $tabla1(DNI) "; $crear1.=") TYPE = INNODB"; # creamos (y comprobamos la creación) la tabla vinculada if(@mysqli_query ($conexion ,$crear1)){ print "La tabla ".$tabla2." ha sido creada<br>"; }else{ print "No se ha creado ".$tabla2." ha habido un error<br>"; } mysqli_close($conexion); ?>

Aquí tienes el ejemplo en ambas modalidades

Ejecutar Modo procesos

Ver código fuente Modo procesos

Ejecutar Orientado a objetos

Ver código fuente Orientado a objetos

Modificación de estructuras

La modificación de estructuras en tablas vinculadas puede hacerse de forma idéntica a la estudiada para los casos generales de MySQL siempre que esas modificaciones no afecten a los campos mediante los que se establecen las vinculaciones entre tablas.

Aquí tienes un ejemplo en se borran y añaden campos en ambas tablas. Como puedes ver la sintaxis es exactamente la misma que utilizamos en temas anteriores.

Ver código fuente Modo procesos

Ver código fuente Orientado a objetos

Modificación o borrado de campos vinculados

Las sentencias MySQL que deban modificar o eliminar campos utilizados para establecer vínculos entre tablas requieren de un parámetro especial (CONSTRAINT) –puede ser distinto en cada una de las tablas– que es necesario conocer previamente.

La forma de visualizarlo es ejecutar la sentencia: SHOW CREATE TABLE nombre tabla que devuelve como resultado un array asociativo con dos índices. Uno de ellos -llamado Table- que contiene el nombre de la tabla y el otro -Create Table- que contiene la estructura con la que ha sido creada la tabla, pero incluyendo el parámetro CONSTRAINT seguido de su valor. Ese valor es precisamente el que necesitamos para hacer modificaciones en los campos asociados de las tablas vinculadas.

En estos podrás ver el código fuente y ejecutar los scripts que permiten visualizar el valor de de CONSTRAINT.

Ejecutar Modo procesos

Ver código fuente Modo procesos

Ejecutar Orientado a objetos

Ver código fuente Orientado a objetos

Page 510: Manual PHP

Conocido el valor de parámetro anterior el proceso de borrado del vínculo actual requiere la siguiente sintaxis:

ALTER TABLE nombre de la tabla DROP FOREIGN KEY parametro

Cuando se trata de añadir un nuevo vínculo con una tabla principal habremos de utilizar la siguiente sentencia:

ALTER TABLE nombre de la tabla ADD [CONSTRAINT parametro] FOREIGN KEY parametro REFERENCES tabla principal(clave primaria)

El parámetro CONSTRAIT (encerrado en corchetes en el párrafo anterior) es OPCIONAL y solo habría de utilzarse en el caso de que existiera ya una vinculación previa de esa tabla.

En estos ejemploS determinaremos el valor de CONSTRAINT borraremos un campo asociado de una tabla vinculada y posteriormente crearemos un nuevo campo idéntico al eliminado.

Ejecutar Modo procesos

Ver código fuente Modo procesos

Ejecutar Orientado a objetos

Ver código fuente Orientado a objetos

Añadir registros a la tabla vinculada

Agregar nuevos registros a una tabla vinculada como la de los ejemplos anteriores no requiere ningún tratamiento especial. El proceso es ya comentado en páginas anteriores. El único problema que podría plantearse sería cuando se intentará añadir un registro y se produjera un error nº 1452. Cannot add or update a child row: a foreign key constraint fails (`ejemplos`.`vinculada`, CONSTRAINT `vinculada_ibfk_1` FOREIGN KEY (`IDENTIDAD`) REFERENCES `principal` (`DNI`)). Esto ocurrirá si intentamos añadir un registro a la tabla vinculada sin que en el campo DNI de la tabla principal exista otro registro con un valor igual al que pretendemos introducir en la tabla vinculada.

Hemos de añadir, y por este orden, un registro a la tabla principal con el DNI requerido y posteriormente ya podremos ejecutar -sin errores- el script que inserta datos en la tabla vinculada. Y podremos ejecutarlo tantas veces como queramos ya que -el campo IDENTIDAD está definido como KEY y por tanto permite duplicados- no hemos establecido la condición de índice PRIMARIO ó UNICO.

Borrar o modificar registros en la tabla principal

El borrado de registros también requiere ejecutarse en el orden adecuado. En este caso hemos de empezar borrando los registros de la tabla vinculada para, posteriormente, hacerlo con la tabla principal. Sin intentamos borrar un registro de la tabla principal antes de eliminar todos los vinculados a él se producirá un error nº 1451 que corresponde a: Cannot delete or update a parent row: a foreign key constraint fails (`ejemplos`.`vinculada`, CONSTRAINT `vinculada_ibfk_1` FOREIGN KEY (`IDENTIDAD`) REFERENCES `principal` (`DNI`)) advirtiéndonos de que no se realiza el borrado porque existen registros en la tabla vinculada con valores asociados al índice del campo que pretendemos borrar y, de permitir hacerlo, se rompería la integridad referencial ya que quedarían registros huérfanos en la tabla vinculada.

Lo mismo ocurirá sin tratamos de modificar un registro de la tabla principal y la modificación afecta al índice que realiza la asociación con la tabla (o tablas) vinculadas se produciría -por las mismas razones y en las mismas circunstancias- un error 1451 que corresponde a: Cannot delete or update a parent row: a foreign key constraint fails (`ejemplos`.`vinculada`, CONSTRAINT `vinculada_ibfk_1` FOREIGN KEY (`IDENTIDAD`) REFERENCES `principal` (`DNI`)) que impediría la modificación.

El borrado o modificación de un registro de la tabla principal requiere siempre que no existieran en la tabla (o tablas) vinculada registros asociados con él.

Borrado de tablas vinculadas

Si pretendemos eliminar una tabla principal sin haberlo hecho previamente con sus tablas vinculadas recibiremos un mensaje de error y no se producirá el borrado.

Page 511: Manual PHP

Las tablas vinculadas sí permiten el borrado y una vez que éstas ya han sido eliminadas (o quitada la vinculación) ya podrán borrarse sin problemas las tablas principales. El orden de borrado (si ambas pretenden borrarse desde el mismo script)es importante. Primero se borra la vinculada y luego la principal.

Opciones adicionales de FOREIGN KEY

La claúsula FOREIGN KEY permite añadirle –detrás de la definición ya comentada y sin poner coma separándola de ella– los parámetros ON DELETE y ON UPDATE en las que se permite especificar una de las siguientes opciones:

ON DELETE RESTRICT

Esta condición (es la condición por defecto de MySQL y no es preciso escribirla) indica a MySQL que interrumpa el proceso de borrado y dé un mensaje de error cuando se intente borrar un registro de la tabla principal cuando en la tabla vinculada existan registros asociados al valor que se pretende borrar.

ON DELETE NO ACTION

Es un sinónimo de la anterior.

ON DELETE CASCADE

Cuando se especifica esta opción, al borrar un registro de la tabla principal se borrarán de forma automática todos los de la tabla vinculada que estuvieran asociados al valor de la clave foránea que se trata de borrar. Con ello se conseguiría una actualización automática de la segunda tabla y se mantendría la identidad referencial.

ON DELETE SET NULL

Con esta opción, al borrar el registro de la tabla principal no se borrarían los que tuviera asociados la tabla secundaria pero tomarían valor NULL todos los índices de ella coincidentes con la clave primaria de la tabla principal.

Para el caso de ON UPDATE las opciones son estas:

ON UPDATE RESTRICT ON UPDATE CASCADE ON UPDATE SET NULL

Su comportamiento es idéntico a sus homónimas del caso anterior.

¡Cuidado! El uso de la opción SET NULL requiere que el campo indicado en FOREIGN KEY permita valores nulos. Si está definido con flag NOT NULL (como ocurre en los ejemplos que tienes aquí) daría un mensaje de error. Al incluir ON DELETE y ON UPTADE (si se incluyen ambas) han de hacerse por este mismo orden. Si se cambiara este orden daría un mensaje de error y no se ejecutarían.

Automatización de procesos

En estos ejemplos haremos un proceso partiendo de cero. Borraremos las eventuales tablas preexistentes, crearemos dos tablas vinculadas, insertaremos algunos datos y posteriorme realizaremos una actualización en cascada.

Ejecutar Ver código fuente Ejecutar Ver código fuente

Page 512: Manual PHP

Modo procesos Modo procesos Orientado a objetos Orientado a objetos

Seguridad con MySQLi

Inyección de código

El hecho de utilizar los recursos de MySQLi no excluye los riesgos de seguridad derivados de lo que llamábamos inyección de códigocuando tratábamos de MySQL. Todo lo argumentado allí respecto a los riesgos y vulnerabilidades sigue teniendo plena vigencia tal como puedes ver en estos ejemplos.

Ejecutar consulta Mediante procesos

Ver código fuente Mediante procesos

Ejecutar consulta Mediante objetos

Ver código fuente Mediante objetos

Como habrás podido observar una consulta con el código adecuado WHERE Nombre='' or '34=34' muestra la vulnerabilidad y devuelve todos los registros contenidos en la tabla por las mismas razones ya comentadas al estudiar MySQL.

Para evitar la posibilidad de ese efecto indeseado (inyección de código) cuando utilizamos MySQLi, PHP dispone de las función:

mysqli_real_escape_string($conexion,cadena) o $objeto->real_escape_string(cadena)

que hace una llamada a la librería MySQL del mismo nombre y para que «escape» los caracteres especiales contenidos en lacadena de forma que sea mucho más seguro su uso a través de mysql_query(). Los caracteres que son «escapados» son los siguientes:\x00, \n, \r, \, ', " y \x1a. Se convertirían en: \\x00, \\n, \\r, \\\, \', \" y \\x1a con lo cual la cadena ' or '34=34 se convertiría en \' or \'34=34 y la claúsula anteriormente comentada se convertiría en WHERE (Nombre='\' or \'34=34') con lo cual ya no se produciría el efecto indeseado y se evitaría el riesgo de uso inadecuado.

Ejecutar consulta Mediante procesos

Ver código fuente Mediante procesos

Ejecutar consulta Mediante objetos

Ver código fuente Mediante objetos

¡Cuidado! El uso de la función mysqli_real_escape_string() requiere como parámetro un identificador de conexión. Por lo tanto es imprescindible que antes de utilizarla se haya establecido una conexión con el servidor MySQL. De igual manera, cuando se trata de programación orientada a objetos, real_escape_string() es un método y en consecuencia es requisito inprescindible la preexistencia del objeto antes de su utilización.

Aunque los desarrolladores de PHP anuncian su próxima desaparición, en la actualidad hay en el fichero php.ini una directiva llamadamagic_quotes_gpc cuyo valor por defecto es Off. Si cambiáramos esa directiva a On (o si usáramos un servicio externo que la tuviera configurada de esta forma) todos los datos recibidos por medio de los métodos GET o POST serían tratados por la función:

addslashes(cadena)

que devuelve la cadena después de haber «escapado» los carácteres ',", \ y NUL (el byte NULL). La acción de addslashes puede deshacerse utilizando la función

stripslashes(cadena_escapada)

que quita los carácteres de «escape» añadidos por addslashes devolviendo la cadena original.

Para comprobar como está configurada la directiva magic_quotes_gpc disponemos de la función:

get_magic_quotes_gpc()

que devuelve un valor booleano. True si está configurada como On y False en caso de estarlo como Off.

Page 513: Manual PHP

Filtrado de código

Mediante un proceso como el que puedes ver en el código fuente que se incluye a continuación puede realizarse un filtrado de los eventuales intentos de inyección de código. Su funcionamiento es el siguiente:

� – Leemos toda la información que se recibe en las variables superglobales $_GET y $_POST. Recorremos los arrays que recogen esos resultados sustituyendo sus valores por los obtenidos de la aplicación de una función que realiza el filtrado.

� – La función que realiza el filtrado comprueba el estado de magic_quotes. Caso de estar configuradas con On revierte los caracteres «escapados» para impedir que se dupliquen los carácteres de escape.

� – Comprueba si es numérico el valor recibido y en caso de serlo lo devuelve sin ninguna modificación.

� – Si el valor recibido no es numérico aplica la función mysqli_real_escape_string y devuelve el resultado dentro de unas comillas simples. De esta forma podríamos despreocuparnos de poner comillas al valor de la variable en la claúsula WHERE. Si se trata de un número no las requiere y si se trata de una cadena el valor recibido ya las incluye.

<?php if(is_array($_GET)){ foreach ($_GET as $_GET_indice=>$_GET_valor){ $_GET[$_GET_indice]=filtro_seguridad($_GET_valor); } } if(is_array($_POST) ){ foreach ($_POST as $_POST_indice=>$_POST_valor){ $_POST[$_POST_indice]= filtro_seguridad($_POST_valor); } } function filtro_seguridad($valor){ /* comprueba si están activas las magic_quotes y, en caso de estarlo revierte su acción mediante stripslashes */ if (get_magic_quotes_gpc()) $valor = stripslashes($valor); /* Abrimos una conexión con MySQL requerida para aplicar mysqli_real_escape_string */ $conexion=mysqli_connect("localhost","pepe","pepa"); /* con la certeza de que estamos ante la cadena original aplicamos la función mysql_real_escape_string a todas las cadenas no numéricas */ if (!is_numeric($valor)) $valor = "'" .mysqli_real_escape_string($conexion,$valor) . "'"; /* cerramos la conexion y devolvemos el resultado*/ mysqli_close($conexion); return $valor; } ?>

Este es un ejemplo de utilización de procedimiento anterior

Ejecutar consulta Mediante procesos

Ver código fuente Mediante procesos

En el caso de optar por la programación orientada a objetos el script anterior debería tener esta sintaxis:

<?php if(is_array($_GET)){ foreach ($_GET as $_GET_indice=>$_GET_valor){ $_GET[$_GET_indice]=filtro_seguridad($_GET_valor); } } if(is_array($_POST) ){ foreach ($_POST as $_POST_indice=>$_POST_valor){ $_POST[$_POST_indice]= filtro_seguridad($_POST_valor); } }

Page 514: Manual PHP

function filtro_seguridad($valor){ /* comprueba si están activas las magic_quotes y, en caso de estarlo revierte su acción mediante stripslashes */ if (get_magic_quotes_gpc()) $valor = stripslashes($valor); /* Abrimos una conexión con MySQL requerida para aplicar mysqli_real_escape_string */ $objeto_filtro=new mysqli("localhost","pepe","pepa"); /* con la certeza de que estamos ante la cadena original aplicamos la función mysql_real_escape_string a todas las cadenas no numéricas */ if (!is_numeric($valor)) $valor = "'" .$objeto_filtro->real_escape_string($valor) . "'"; /* cerramos la conexion y devolvemos el resultado*/ $objeto_filtro->close(); return $valor; } ?>

Este es un ejemplo de utilización de procedimiento anterior

Ejecutar consulta Mediante objetos

Ver código fuente Mediante objetos

Cambios de aspecto de una página

Modificación de los contenidos de una página

Cuando se trata de modificar parcialmente los contenidos de una página tenemos dos posibilidades. La más «cutre» seguramente sería cargar nuevamente toda la página después de haber realizado las modificaciones requeridas en el servidor. En este supuesto seguiríamos moviéndonos en el entorno de los lenguajes del lado del servidor, es decir, en el entorno de PHP.

La solución más óptima, pensando siempre en modificaciones parciales, sería mantener todo el contenido no modificble y realizar únicamente los cambios en los elementos de que los requieran. De esa forma, además de lograr una continuidad gráfica en la pantalla estaríamos reduciendo el tiempo de ejecución de eso cambios. En este segundo supuesto será necesario el concurso de un lenguaje del lado del cliente que permita ejecutar aquellas modificaciones que sean susceptibles de hacerse en modo local y/o recabar del servidor datos concretos que pueden ser requeridos. Trataremos el asunto en el entorno de JavaScript complementándolo cuando sea necesario con AJAX (Asynchronous JavaScript And XML) y accediendo al los datos del servidor mediante el objeto XMLHttpRequest soportado e incluido en la mayoría de los navegadores actuales.

Elementos con nombre

Sin meterme en grandes profundidades del JavaScript ni de los llamados DOM (Document Object Model) y BOM (Browser Object Model) vamos a intentar encontrar algunas recetas prácticas que nos permitan identificar indubitativamente los elementos de un documento. Consideremos el documento con una familia numerosa en la que conviven una cantidad inusual de hijos. En el ámbito familiar la solución es fácil. Para cada hijo se elige un nombre del agrado de sus progenitores con las restricciones establecidas en el artículo 54 de la Ley del Registro Civil (español) que establece que «No puede imponerse al nacido el mismo nombre que ostente uno de sus hermanos...».

Con un criterio similar podremos «bautizar» los diferentes elementos de un documento. Para ello bastaría con incluir en las etiquetas del lenguaje HTML algo como esto: id="nombre_unico". De esta forma podremos ya podremos llamar a cada cosa por su nombre, es decir, por su id aunque algunos elementos, body por ejemplo, al ser únicos no necesitan de ningún id.

En los ejemplos vamos a probar con varias de las etiquetas habituales en HTML tales como: <div>, <tr>, <td>, <li> o <img> y también con Los <OBJECT> de los tipos: img/gif, text/html o application/x-shockwave-flash así como con los diferentes elementos utilizados por los formularios. De esa forma podríamos crear una página como esta que puedes ver en el ejemplo

Ver página Ver código fuente

Page 515: Manual PHP

Como podrás observar hemos intentado incluir una pequeña miscelánea de elementos HTML utilizando estilos CSS en algunos casos. Las únicas novedades (si es que lo son para tí) son las nuevas formas de utilización la etiqueta <object> que parece tener una gran proyección futura ya que este tipo de elemento permite utilizar una sintaxis similar para los diferentes elementos.

Compatible con las mayoría de los navegadores modernos (la estandarización en términos absolutos me temo que es una gran utopía) y utilizando <object data="ruta_nombre_y_extension" type="tipo_mime">

De esta forma, <object id="foto_objeto" data="./images/bocadillo.gif" type="image/gif"></object>> nos permite incluir una imagen de forma alternativa a la clásica de: <img id="foto_objeto" src="./images/bocadillo.gif" />. De igual forma y tan sólo modificando el tipo de objeto hemos incluido una animación flash usando <object type="application/x-shockwave-flash" data="./videos/trubia.swf"> y también un sonido por medio de <object type="audio/x-wav" data="data/test.wav"></object> e incluso hemos podido utilizar, como opción alternativa al clásicoIFRAME el objeto <object id="web" data="http://www.rinconastur.com" type="text/html"></object> ahora con tipo text/html.

Como quiera que existen una gran variedad de tipos MIME en este enlace tiene una «chuleta» en la que se incluyen una buena parte de las extensiones de ficheros existentes y los tipos MIME asociados.

¡Cuidado! He vuelto atrás para corregir el ejemplo anterior y a la vez advertirte de algo de relativa importancia. Parece que la estadarización es algo absolutamente imposible. Los quebraderos de cabeza son continuos y muchas veces nos obligan a recurrir a algunas chapuzas. Una de ellas sería tener la precaución de incluir dentro de una etiqueta div losobject destinados a contener páginas web. Es decir, en vez de escribir algo como: <object id="web" data="http://www.rinconastur.com" type="text/html"></object> procura escribir esto: <div id="chapuza_para_IE"> <object id="web" data="http://www.rinconastur.com" type="text/html"></object> </div> Puede parecer (o ser) ridículo pero no encontré una opción mejor para resolver un problema que se me planteó un poco más adelante. ¡Misterios de la informática! Me hizo recordar un «pseudodespido» del que fuí víctima hace ya unos cuantos años. ¡Tardé más de un mes en darme cuenta que en una opción de QuarkXpress no era lo mismo 90º que la suma algebraica (-90º+180º)!¡Misterios ...!

¿Cómo identifica JavaScript cada uno de los elementos?

Lo primero que tenemos que considerar es la forma en la que JavaScript identifica cada una de las partes de nuestra página web. Para JavaScript todo son objetos. Algo bastante parecido, al menos en el concepto, a lo que ocurría con los objetos PHP. Y como ocurría allí cada objeto pueden contener otros objetos y disponer de métodos y también de propiedades.

Cuandos se trata de documentos HMTL o XML tenemos que recurrir al DOM (Document Model Objects) –algo así como Objetos para la representación de Documentos– que puede sonar algo raro pero verás que, al menos en lo que vamos a necesitar, no resulta nada complicado una vez que le hayamos «cogido el tranquillo».

Hemos aprendido que cada vez que «abrimos el navegador» lo que estamos haciendo es «abrir una ventana» y como ventana en «raro» se escribe window pues ya tenemos un objeto Javascript, el objeto window que por decirlo de alguna manera es como el gran contenedor de todo lo demás. Dentro de window está todo lo demás. Los objetos que contiene, sus métodos y sus propiedades. En PHP la sintaxis era $objeto->metodo() o $objeto->propiedad. En el caso de JavaScript -> es sustituido por el caracter punto (.)

En este enlace tiene una página en la que por medio de un script de JavaScript puedes ver la información correspondiente al objeto window y a algunos de los objetos que contiene.

¡Cuidado! La normalización o estandarización parece ser una utopía en el mundo de los navegadores y el JavaScript. Si abres la página del párrafo anterior –aquí está el enlace– con distintos navegadores podrás observar que los resultados de pulsar sobre los enlaces que contiene son distintos en cada uno de ellas. Podrás ver, por ejemplo, que en Internet Explorer no van a aparecerte alusiones a los métodos que incluyen los objetos. También podrás observar que las propiedades, en incluso los objetos contenidos en cada uno de los objetos, no son exactamente los mismos. La

Page 516: Manual PHP

compatibilidad entre los diferentes navegadores sigue requiriendo «auténtico encaje de bolillos». Quizá algún día... se normalizarán los navegadores.

Para nuestros fines uno de los objetos más importantes es window.document.body como todos los objetos JavaScript que vayamos manejando incluye una serie de propiedades y métodos que puedes ver enumeradas en el enlace de igual nombre que hay en este documento.

El objeto window.document.body incluye otro objeto llamado window.document.body.style cuyas propiedades son las diferentes opciones de estilos aplicables al body. ¿Cuales son esas propiedades? La lista es larga y bastante similar a la utilizada en las hojas de estilo pero ¡cuidado! que la sintaxis no es la misma. Por ejemplo lo que en CSS (las hojas de estilo en cascada) se define como background-color se corresponde con una propiedad del objeto style de JavaScript llamada backgroundColor. Para facilitarte las cosas en este enlacetienes la lista de las diferentes propiedades relacionadas con los estilos.

Veamos ahora un poco de código JavaScript relacionado con este asunto. En PHP el código lo delimitamos mediante <?php y ?>. En JavaScript es bastante similar aunque aquí se delimita por medio de las etiquetas <script type="text/javascript"> y </script>

El tratamiento de las propiedades de los objetos es similar al que usamos en PHP con la diferencia que las -> son reemplazadas aquí. y que, a diferencia de PHP hay una serie de objetos (body entre ellos) que no hace falta instanciar porque se crean de forma automática. Por tanto para modificar una propiedad bastaría con escribir algo así: window.document.body.style.backgroundColor="nuevo_color". Eso es lo que puedes ver en estos ejemplos. Pero fíjate en los dos primeros. También aquí el navegador hace una lectura secuencial del documento y si instanciamos un objeto que aún no ha sido creado (es lo que ocurre en el primer ejemplo) no habrá manera de que se modifique la pretendida propiedad.

<html> <head> <title>Pruebas del body</title> <script type="text/javascript"> window.document.body.style.backgroundColor="#ffff00"; </script> </head> <body> Te has precipidado. Me mandaste cambiar el color del body antes de que el body existiera. </body> </html>

Ver ejemplo

<html> <head> <title>Pruebas del body</title> </head> <body> <script type="text/javascript"> window.document.body.style.backgroundColor="#ffff00"; </script> Ahora si me pondré amarillo. Las cosas en su sitio. Los javascript también. </body> </html>

Ver ejemplo

Vamos un poco a «salto de mata» pero como todos los lenguajes tienen una gran similitud pienso que no resulta difícil de entender este nuevo ejemplo. También existen funciones en JavaScript, también la sintaxis es:

function nombre_de_la_funcion(){ .... ... instrucciones .... .... }

Page 517: Manual PHP

Como ocurre en PHP y en todos los demás lenguajes las funciones no se ejecutan hasta que no son invocadas (eso permite incluirlas en cualquier parte del documento) y también aquí se invocan escribiendo nombre_de_la_funcion(). Hay una diferencia (puedes verla en el ejemplo) y es que en el caso de JavaScript la ejecución de la función puede asociarse a un evento. Un evento no es otra cosa según la RAE que un acaecimiento, una cosa que sucede. Y ese es su significado. Cuando acaece que se carga el body de la página (onLoad) es cuando se ejecuta automáticamente la función.

<html> <head> <title>Pruebas del body</title> <script type="text/javascript"> function cambia_fondo(){ window.document.body.style.backgroundColor="#ffff00"; } </script> </head> <body onLoad="cambia_fondo()"> Ahora me es dicho: cuando cambies cargues el body (onLoad) ejecuta la función cambia_fondo. Soy obediente y lo hago </body> </html>

Ver ejemplo

Los ejemplos anteriores aluden al objeto style contenido en body. Pero body tiene, además sus propiedades (algunas modificables otras no) que también se pueden acceder de forma fácil. Aquí tienes un ejemplo en el que hemos incluido una novedad. Ahora la función se ejecuta pulsando un enlace clásico en el que hemos sustituido el nombre de la nueva página (lo habitual en los enlaces) por esto otro:javascript:nombre_de_la_funcion(). Ahora, cuando pulsemos en el enlace se ejecutará la función.

Podrás ver como la función alert() abre una ventanita informativa sobradamente conocida en la que puede leerse el valor de lo contenido en su paréntesis.

<html> <head> <title>Pruebas del body</title> <script type="text/javascript"> function informame(){ alert(window.document.body.clientWidth); } </script> </head> <body> Si <a href="javascript:informame()">pulsas aquí</a> podrás conocer el ancho de esta ventana. Si modificas su tamaño y vuelves a pulsar verás que la medida cambia. </body> </html>

Ver ejemplo

La identificación de cada uno de los elementos de una página podría complicarse a medida que la página va incluyendo contenidos. JavaScript ha ido evolucionando a lo largo del tiempo. Los primeros navegadores con JavaScript permitían acceder sólo a ciertos tipos de objetos del contenido del documento que son conocidos como colecciones y que se identifican como document.anchors (anclas),document.forms (formularios), document.images (imágenes), document.links (enlaces), document.applets (applets) y document.embeds(objetos incrustados), estos dos últimos ya han caido en absoluto desuso.

Como quiera que en un mismo documento puede haber varios objetos de la misma colección (varias imágenes o varios formularios por poner un ejemplo) la forma de referirse a ellos era (y sigue siendo ya que los navegadores actuales mantienen esta forma de acceso a estos tipos de objetos) utilizar lo que en PHP llamaríamos un índice escalar de forma que sintaxis del tipo document.images[0] odocument.images[4] harían alusión a la primera (empieza a numerar desde cero) y la quinta (por la misma razón) de las imágenes contenidas en el documento contadas, como se decía en lo créditos de las películas, por «orden de aparición».

Page 518: Manual PHP

También es factible aludir a los diferentes elementos por medio de id de modo que si en la etiqueta de una imagen incluimos algo como: <img id="la_foto" src="...." /> javaScript la identificará cuando escribamos document.images['la_foto'] (fíjate en las comillas que van dentro del corchete).

En este ejemplo tienes algunas muestras de como hacer estas llamadas.

Ver ejemplo Ver código fuente

Afortunadamente, nuestro ya famoso id pude suplir con creces las limitaciones de las colecciones. Será la referencia utilizaremos en adelante.

Bastará con que escribamos en JavaScript algo así como window.document.getElementById('identificador') para referirnos a el elemento HMTL cuya etiqueta de apertura incluya id="identificador". Para que JavaScript no tenga dudas bastará, tal como hemos comentado ya al principio de esta página, con que no repitamos los nombres de los identificadores a lo largo nuestro documento.

Igual que ocurría con body, cada uno de los objetos window.document.getElementById('identificador') contiene un objeto style al que nos podremos referir utilizando la sintaxis: window.document.getElementById('identificador').style

Si preferimos utilizar una sintaxis un poco menos farragosa podremos recurrir a objetos intermedios con denominaciones más fáciles. Por ejemplo:

var variable1= window.document.getElementById('identificador'); y var variable2=variable1.style podríamos hacer alusión mediante la variable variable1 al objeto cuyo id es identificador y a sus estilos mediante variable2. Probablemente resultaría más cómodo y más corto.

Cambios de aspecto de los elementos de una página

Al comienzo de este documento hemos incluido un ejemplo en que utilizábamos una muestra de los diferentes elementos HTML asignándoles un nombre identificador creanod con ellos una página (hasta el momento estática).

A partir de ese documento con sus elementos identificados por medio de sus respectivos id ya estaremos en condiciones de recurrir a JavaScript para hacer cambios de forma autómatica. Pienso que una forma de hacerlo con determinado método sería clasificar los cambios en dos tipos: aspecto y contenido.

Empezamos por el aspecto lo primero de todo es decidir dónde hacer el cambio y, luego, ver que cambio pretendemos hacer. Al primero de los ejemplos de esta página le agregaremos una función JavaScript como esta:

<script type="text/javascript"> function cambia_aspecto(elemento, propiedad, valor){ if (elemento=="body"){ comando="document."+elemento+".style."+propiedad+"='"+valor+"'"; eval(comando); alert("Se ha ejecutado:\n\r"+comando); }else{ comando="document.getElementById('"+elemento+"').style."+propiedad+"='"+valor+"'"; eval(comando); alert("Se ha ejecutado:\n\r"+comando); } } </script>

Veamos su comportamiento. La llamada a la función incluye tres valores: elemento, propiedad y valor. Como elemento incluiremos el nombre del id cuya apariencia queremos modificar. Como propiedad pondremos el nombre de una de las propiedades de los objetos styleque tenemos enumeradas en este enlace y como valor incluiremos una cadena con el nuevo valor que pretendamos asignar a la propiedad.

Page 519: Manual PHP

En cualquiera de las opciones del condicional if (fíjate que la sintaxis es idéntica a la de PHP con la diferencia de que en JavaScript los nombres de variable no empiezan por $) hay una variable llamada comando que concatena textos entrecomillados con valores de variables. Podrás observar que en este caso el concatenador no es el . como ocurría en PHP sino el signo +.

Con esa sintaxis la variable comando recogería cadenas tales como las que vas a poder leer al ejecutar el ejemplo. Una vez creada la variable ejemplo es necesario aplicarle la función JavaScript eval() que es quien realmente ejecuta la intrucción JavaScript contenida en la cadena comando.

Hemos incluido una llamada a la función alert para poder visualizar el comando ejecutado por javascript. Es evidente que resulta innecesario desde un punto de vista práctico. Por último quizá no esté de más aludir al condicional if. Cuando el objeto cuyo estilo que deseamos cambiar es body el objeto que recoge su estilo es: window.document.body.style cuando se trata de otros elementos que pueden no pertener a colecciones el objeto se identificaría de forma distinta ya que habría que recurrir a:window.document.getElementById('nombre_del_elemento').style. La variable elemento nos permitirá elegir uno u otro camino dependiendo del valor que contenga.

¡Cuidado! Cuando se trata de una misma ventana es indiferente utilizar la sintaxis window.document ó document. Habrá otras situaciones en las que será necesario tomar en consideración ese asunto.

Una vez incluida la función en el documento solo nos faltaría poner algún elemento que permita ejecutar esa función. En el ejemplo lo hemos hecho mediante los enlaces comentados anteriormente y cuya sintaxis podría ser similar a esta:

<a href="javascript:cambia_aspecto('capa1','width','300px');">Cambiar ancho de la capa</a>

De esta forma ya estaremos en condiciones de modificar el estilo de cualquier elemento de una página web tal como puedes comprobar en este ejemplo.

Ver ejemplo Ver código fuente

¡Cuidado! La compatibilidad entre navegadores parece ser algo así como la cuadratura del círculo. Si ejecutas el ejemplo anterior en Internet Explorer verás que si cambia el color de fondo del radio. En otros navegadores no ocurrirá lo mismo. Es cierto que la mayoría de las opciones de estilo son compatibles entre navegadores pero... siempre hay algún pero en algún lugar que rompe la racha de compatibilidad.

La sintaxis de la función anterior tiene una alternativa mucho más cómoda y simple que puedes visualizar aquí debajo:

function cambia_aspecto(elemento, propiedad, valor){ if(elemento=='body'){ window.document.body.style[propiedad]=valor; }else{ window.document.getElementById(elemento).style[propiedad]=valor; } }

Como verás se trata evitar la construcción de la cadena de texto (un poco liosa por aquello de concatenar textos y variables) y utilizar directamente la llamada al objeto escribiendo la variable identificadora dentro del paréntesis y colocando un corchete pegado a style para incluir la variable propiedad.

¡Cuidado! Fíjate que los nombres de las variables se incluyen sin comillas tanto en el paréntesis como en el corchete. Si prentendemos incluir valores directamente dentro de esos elementos (paréntesis y corchete) deberemos ponerlos entre comillas.

Como es lógico, el uso de una u otra función no altera para nada el resultado final. Aquí tienes la prueba.

Page 520: Manual PHP

Ver ejemplo Ver código fuente

Una alternativa más

Los objetos window.document.body.style y window.document.getElementById(nombre_del_elemento) disponen de otros métodos que también pueden utilizarse para realizar los cambios de diseño comentados anteriormente. Se trata de:

objeto.style.getAttribute('atributo') y de: objeto.style.setAttribute('atributo','valor')

el primero de los métodos nos devuelve una cadena conteniendo el valor actual de atributo mientras que el segundo nos permite efectuar modificar un atributo concreto

. <script type="text/javascript"> /* funcion para comprobar los valores actuales de una propiedad */ function comprueba_aspecto(elemento, propiedad){ if(elemento=='body'){ alert(window.document.body.style.getAttribute(propiedad)); }else{ alert(window.document.getElementById(elemento).style.getAttribute(propiedad); } } /* funcion para cambiar los valores actuales de una propiedad */ function cambia_aspecto(elemento, propiedad, valor){ if(elemento=='body'){ window.document.body.style.setAttribute(propiedad,valor); }else{ window.document.getElementById(elemento).style.setAttribute(propiedad,valor); } } </script>

Ver ejemplo Ver código fuente

Algunas consideraciones respecto al ejemplo anterior

Al ejecutar el ejemplo anterior podrás observar un par de cosas interesantes que conviene tener muy en cuenta.

� – Si pulsas el enlace Comprueba colores de fondo inmediatamente después de cargar la página verás que no aparece nada en la ventana del alert. Aunque el fondo por defecto es blanco JavaScript no lo identificará si previamente no ha sido establecido un valor bien por medio de JavaScript o a través de una definición de estilo por medio de la sintaxis de CSS.

� – Si observas el código fuente del ejemplo verás que los valores que pasamos como atributos de ancho y alto tienen el formato200px (llevan la unidad inmediatamente después del valor numérico de la medida) lo que significa que tales valores van a ser siempre una cadena alfanumérica y no un valor numérico.

� – Si pulsas los enlaces Comprueba el ancho o Comprueba el alto observarás que detrás del valor numérico aparecen indicadas las unidades (170px, 200px, etcétera). Eso significa que, igual que comentamos en el párrafo anterior, el valor devuelto es una cadena que no es susceptible de ser tratada mediante funciones aritméticas.

� – Si se pretende tratar aritméticamente el valor de una propiedad hemos de recurrir al método parseInt('cadena') que extrae el valor numérico entero de la cadena de forma similar a lo que ocurría en PHP con (int)$variable tal como puedes recordar en este enlace. Por tanto el manejo aritmético de una de estas propiedades requeriría un proceso similar a este

� � • Extraer el valor numérico del valor de la propiedad por medio de variable=parseInt(propiedad)

� • Operar aritméticamente ese valor (por ejemplo sumándole 10 unidades) variable=variable+10

Page 521: Manual PHP

� • Modificar el valor de la propiedad agregando al resultado anterior la cadena que especifica el el tipo de unidades. Tendría una sintaxis similar a esta:propiedad=variable+'px'

Moficaciones masivas de aspecto

En todos los ejemplos comentados hasta ahora hemos aludido a modificaciones del aspecto de un elemento específico identificado por su window.document.ElementById('identificador'). No es la única opción. Pueden plantearse situaciones que requieran la modificación en bloque un conjunto de elementos. Una primera posibilidad podría ser la necesidad de todos los elementos de un tipo determinado (por ejemplo todos los elementos etiquetados como <td>, <li> o <div>) contenidos en una página.

Para este fin los objetos JavaScript disponen de un método muy interesante. Se trata de getElementsByTagName(etiqueta) que nos devuelve un objeto que contiene todos los elementos cuya etiqueta coincide con el parámetro etiqueta. El número de esos elementos puede conocerse por medio de la propiedad length de tal forma que una expresión del tipo getElementsByTagName(etiqueta).length nos devolvería el número de elementos definidos por medio de etiqueta

Para referirnos a un elemento concreto deberemos utilizar la sintaxis getElementsByTagName(etiqueta)[i] dónde el índice i es el número de orden de cada etiqueta, contado a partir de cero y recorriendo secuencialmente los contenidos de la página.

Cuando el nombre de la etiqueta se sustituye por un * el asterisco se interpreta como carácter comodín y en consecuencia se efectuará un recuento de todos los elementos o nodos de la página independientemente de cual sea su tag o etiqueta. Aquí tienes un ejemplo de una función JavaScript que realiza esos recuentos

<script type="text/javascript"> function recuenta(etiqueta){ /* en caso de que etiqueta sea nulo le asigna el valor del comodin * */ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; /* el objeto elementos recogerá todos elementos cuyo nombre coincide con la variable etiqueta*/ var elementos = document.getElementsByTagName(etiqueta); /* la variable cadena recogerá un literal y el número de elementos especificado por la propiedad elementos.length Los carácteres '\n\r' son saltos de línea */ var cadena="Tengo "+elementos.length+'\n\r'; /* por medio de un bucle vamos añadiendo a la variable cadena los nombres de cada uno de los elementos contenidos en el objeto elementos e identificados por el subíndice que indica su orden de creación */ for (i=0;i<elementos.length;i++){ /* la propiedad tagName contiene el nombre del elemento */ cadena+=elementos[i].tagName+'\n\r'; } /* visualizamos la cadena resultante de la consulta */ alert(cadena); } </script>

Si ejecutas este ejemplo y pulsas el enlace Recuenta todos los elementos podrás ver la enumeración ordenada de los nombres de cada uno de los elementos que contiene la página de ejemplo. Presta atención a las diferentes etiquetas que visualiza y observa que, por ejemplo, contempla TBODY pese a no haber sido especificado expresamente dentro de la etiquet TABLE.

Ver ejemplo Ver código fuente

La función anterior tiene su utilidad reducida a la enumeración de los diferentes elementos de una página pero puede ser el soporte de estas otras funciones que si permiten llevar a cabo las pretendidas modificaciones masivas de los elementos de una página.

<script type="text/javascript">

Page 522: Manual PHP

/* la funcion recibe tres valores. El nombre de la etiqueta que pretendemos modificar el atributo que queremos cambiar o agregar a esas etiquetas y el valor que se pretende asignar al atributo */ function modifica(etiqueta,atributo,valor){ /* el condicional sustituye los valores nulos o vacios por el comodín */ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; /* por comodidad creamos el objeto elementos que incluirá todos todos los objetos cuya etiqueta coincida con ese parámetro*/ var elementos = document.getElementsByTagName(etiqueta); /* por medio del bucle recorreremos secuencialmente cada uno de los elementos y mediante el método setAttribute le aplicamos el nuevo valor al atributo indicado */ for (i=0;i<elementos.length;i++){ elementos[i].style.setAttribute(atributo,valor); } } /* esta otra funcion es la alternativa ya comentada de modificación de una propiedad sin utilizar el método setAttribute. Cualquiera de las dos funciones producirá el mismo resultado */ function modifica1(etiqueta,atributo,valor){ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; var elementos = document.getElementsByTagName(etiqueta); for (i=0;i<elementos.length;i++){ elementos[i].style[atributo]=valor; } } </script>

Ver ejemplo Ver código fuente

La utilización de hojas de estilo en cascada (CSS) lleva implícita la definición de diferentes clases de estilos que son aplicados a los diferentes elementos de las páginas incluyendo dentro de sus etiquetas el atributo class="nombre_de_la_clase".

Si en el ejemplo anterior hacíamos un modificación masiva de todas las etiquetas de un determinado tipo es posible que en momentos determinados tratemos de restringir esa modificación a las etiquetas de una clase determinada. Esta posibilidad es propiciada por la propiedad objeto.className que nos devolverá el nombre de la clase (si ha sido establecida) correspondiente a un objeto (etiqueta html) determinado. En caso de no haberse establecido esa propiedad tendrá valor null o cadena vacía.

Contemplar esta nueva posibilidad implicará readecuar las funciones del ejemplo anterior a este nuevo escenario.

<script type="text/javascript"> /* agregamos el nuevo parámetro clase a la función anterior */ function modifica(etiqueta,clase,atributo,valor){ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; var elementos = document.getElementsByTagName(etiqueta); for (i=0;i<elementos.length;i++){ /* establecemos la condición de que para modificar el atributo coincida la clase con el valor recibido en ese parámetro o que la función haya sido invocada asignándole a clase un valor nulo o una cadena vacia en cuyo caso afectaría a todos los elementos que no tuvieran una clase establecida*/ if(elementos[i].className==clase || clase==null || clase==''){ elementos[i].style[atributo]=valor; }

Page 523: Manual PHP

} } /* igual que en el ejemplo precedente esta función es una alternativa a la anterior */ function modifica1(etiqueta,clase,atributo,valor){ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; var elementos = document.getElementsByTagName(etiqueta); for (i=0;i<elementos.length;i++){ if (elementos[i].className==clase || clase==null || clase==''){ elementos[i].style[atributo]=valor; } } } </script>

Ver ejemplo Ver código fuente

Aun puede darse un poco más de versatilidad a la opción de llevar a cabo modificaciones masivas de estilos. En los ejemplos anteriores la búsqueda de etiquetas y/o clases la realizabamos en document, es decir, a lo largo de toda la página visualizada en la ventana actual. Es posible que en determinadas situaciones nos interese realizar la búsqueda y modificación sólo en una parte del documento. Esa posibilidad nos obligaría a llevar a cabo una nueva ampliación en la funciones modificadoras que hemos venido manejando en los ejemplos anteriores. Aquí tienes el código fuente.

<script type="text/javascript"> /* la funcion recibe ahora un quinto parámetro que hemos llamado contenedor*/ function modifica(contenedor,etiqueta,clase,atributo,valor){ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; /* si el contenedor es null o vacio entendemos que nos referimos a todo el documento */ if (contenedor==null || contenedor==''){ var elementos = document.getElementsByTagName(etiqueta); }else{ /*cuando el contenedor no es vacio entendemos que nos referimos al elemento de documento cuyo nombre identificador unico (id) es contenedor. La búsqueda de los elementos modificable la realizaremos dentro de esa parte del del documento (objeto cuya es etiqueta es etiqueta y contenidos dentro de un elemento cuyo id es el valor de la variable contenedor) */ var elementos=document.getElementById(contenedor).getElementsByTagName(etiqueta); } for (i=0;i<elementos.length;i++){ if(elementos[i].className==clase || clase==null || clase==''){ elementos[i].style[atributo]=valor; } } } /* las modificaciones son idénticas a las descritas para la función anterior que, como siempre, es una opción alternativa a esta */ function modifica1(contenedor,etiqueta,clase,atributo,valor){ if ( etiqueta == null || etiqueta == ''){etiqueta = '*'}; if (contenedor==null || contenedor==''){ var elementos = document.getElementsByTagName(etiqueta); }else{ var elementos=document.getElementById(contenedor).getElementsByTagName(etiqueta); } for (i=0;i<elementos.length;i++){ if (elementos[i].className==clase || clase==null || clase==''){ elementos[i].style[atributo]=valor;

Page 524: Manual PHP

} } } </script>

Ver ejemplo Ver código fuente

Eventos en JavaScript

¿Cómo activar las modificaciones?

En la página anterior hemos visto como modificar los aspectos de los elementos de una página por medio de javascript. En todos los ejemplos allí manejados la ejecución de las funciones requería pulsar sobre un enlace clásico con su sintaxis habitual de:

<a href="javascript:nombre_de_la_funcion()">texto del enlace </a>

Esta no es la única forma posible de hacerlo. En javascript existen una serie de eventos (acciones llevadas a cabo por el usuario) que pueden ser utilizados para «disparar» la ejecución de una función. Veamos los nombres de algunos, sus significados y su forma de utilización sobre los textos de un enlace. Su sintaxis más recomendada es: <a href="javascript:void(0)" manejador="nombre_de_la_funcion(parametros)">texto del enlace</a> cuyo ejemplo de utilización requiere una sintaxis como esta <a href="javascript:void(0)" onClick="alert('Has hecho clic')"></a> y cuyo funcionamiento puedes comprobarlo al hacer «clic» sobre el enlace.

Existen otras opciones tales como <a href="#" manejador="nombre_de_la_funcion(parametros)">texto del enlace</a> y cuyo ejemplo de uso puedes ver en este código y en este enlace <a href="#" onClick="alert('Has hecho clic')"></a>

La diferencia entre ambas opciones es que en la primera se invoca la ejecución de la función javascript void(0) (daría lo mismo si en vez de cero llevara cualquier otro valor numérico) que devuelve un valor nulo para href con lo cual no se recarga la página. Si escribiéramos algo como: <a href="javascript:void()" onClick="alert('Has hecho clic')"></a> javascript nos daría un error de sintaxis (void requiere que incluyamos un valor dentro del paréntesis) y si intentáramos algo como <a href="" onClick="alert('Has hecho clic')"></a> podrás observar que si bien se produce el mensaje de alerta posteriormente intenta cargarse una página sin nombre y eso nos conducirá a un error de página no encontrada o a la eventual página de error que por defecto hayamos podido establecer en .htaccess.

El uso del formato <a href="#" onClick="alert('Has hecho clic')"></a> está bastante extendido aunque en puridad suele aconsejarse la opción void(0). Cuando se usa esta alternativa lo que ocurre es que al pulsar se intentar recargar la página redirigiéndola a un ancla de la misma (anchor) sin nombre. Podremos observar que en la barra de direcciones aparece la dirección de la página seguida de #

Eventos del ratón sobre textos de hiperenlaces

Además del ya comentado existen los siguientes manejadores para eventos del ratón:

� onDblClick

� – El evento se dispara al hacer doble clic con el ratón sobre el texto del enlace

� • Ejemplo: <a href="javascript:void(0)" onDblClick="alert('Has hecho doble clic')"></a>

� onMouseOver

� – El evento se dispara al situar el puntero del ratón encima del texto del enlace

� • Ejemplo: <a href="javascript:void(0)" onMouseOver="alert('Has puesto el ratón encima')"></a>

� onMouseOut

� – El evento se dispara al quitar el puntero del ratón de encima del texto del enlace

� • Ejemplo: <a href="javascript:void(0)" onMouseOut="alert('Has quitado el ratón de encima')"></a>

� onMouseDown

� – El evento se dispara en el momento en que se pulsa cualquier botón del ratón encima del texto del enlace

� • Ejemplo: <a href="javascript:void(0)" onMouseDown="alert('Me disparo cuando pulsas el ratón sobre el texto')"></a>

Page 525: Manual PHP

� onMouseUp

� – El evento se dispara en el momento en que se suelta cualquier botón del ratón encima del encima del texto del enlace

� • Ejemplo: <a href="javascript:void(0)" onMouseUp="alert('Me disparo cuando sueltas botón el ratón sobre el texto')"></a>

Eventos del ratón sobre imágenes y botones

De igual forma que hemos hecho utilizando textos con áreas sensibles a los eventos también podríamos haber utilizado imágenes y/o elementos de formularios

Eventos del ratón y el teclado sobre campos de formulario tipo TEXT

Veamos que eventos se pueden manejar en los distintos elementos de un formulario. Empezaremos por los de tipo="text". Además de manejar los eventos comentados anteriormente tal como puedes ver en estos ejemplos

. haz click aq

haz doble click a

pon el raton enc

quita el raton

pulsa aqui dentr

suelta el raton

tanbién maneja los siguientes:

� onChange

� – El evento se dispara al salir de campo de texto depués de haber modificado su contenido

� • Ejemplo: escribe aqui

� onFocus

� – El evento se dispara al tomar el foco que equivale a situar el cursor dentro del campo de texto bien mediante el ratón o desplazándose entre campos por medio de la tecla de tabulación

� • Ejemplo:

� onBlur

� – El evento se dispara al perder el foco que equivale a sacar el cursor del campo de texto bien mediante el ratón o desplazándose entre campos por medio de la tecla de tabulación

� • Ejemplo:

� onSelect

� – El evento se dispara al seleccionar texto dentro del campo

� • Ejemplo: selecciona una parte

� onKeyPress

� – El evento se dispara al mantener pulsada una tecla cuando el cursor está dentro del campo

� • Ejemplo: mantén pulsada

� onKeyUp

� – El evento se dispara al soltar una tecla pulsada cuando el cursor está dentro del campo

� • Ejemplo: pulsa y suelta

Page 526: Manual PHP

� onKeyDown

� – El evento se dispara al pulsar una tecla cuando el cursor está dentro del campo

� • Ejemplo: pulsa una tecla

� onKeyPress (leyendo el valor de la tecla pulsada)

� – El evento se dispara al mantener pulsada una tecla cuando el cursor está dentro del campo y el alert muestra el resultado de ejecutar una función javascript que captura el valor de la tecla pulsada. Esa funcion tiene la forma siguiente:

� <script type="text/javascript"> � function captura_teclado(e){ � var numero_de_tecla_pulsada; � var caracter_de_la_tecla_pulsada; � if(window.event){ //cosas de IE � var numero_de_tecla_pulsada = e.keyCode //recoge en IE el valor ASCII de la tecla � } � else if(e.which){ // Cosas Netscape/Firefox/Opera � var numero_de_tecla_pulsada = e.which //recoge valor ASCII en otros navegadores � } � /* obtiene el caracter correspondiente al codigo ASCII recogido */ � caracter_de_la_tecla_pulsada = String.fromCharCode(numero_de_tecla_pulsada); � /* devuelve el valor del caracter correpondiente a la tecla pulsada */ � return caracter_de_la_tecla_pulsada; � }

</script>

� • Ejemplo (teclea dentro del campo):

Eventos del ratón sobre input type="radio"

� onChange

� – El evento se dispara al producirse un cambio en el elemento. Se comprueba el cambio cuando el elemento pierde el foco.

� • Ejemplo: Si No

� onClick

� – El evento se dispara al hacer clic sobre la opción

� • Ejemplo: Si No

� onFocus

� – El evento se dispara al tomar el foco.

� • Ejemplo: Si No

� onBlur

� – El evento se dispara al perder el foco.

� • Ejemplo: Si No

Eventos del ratón sobre input type="checkbox"

� onChange

� – El evento se dispara al producirse un cambio en el elemento. Se comprueba el cambio cuando el elemento pierde el foco.

� • Ejemplo: Si No

� onClick

Page 527: Manual PHP

� – El evento se dispara al hacer clic sobre la opción

� • Ejemplo: Si No

� onFocus

� – El evento se dispara al tomar el foco.

� • Ejemplo: Si No

� onBlur

� – El evento se dispara al perder el foco.

� • Ejemplo: Si No

Eventos del ratón sobre select

� onChange

� – El evento se dispara al producirse un cambio en el elemento. Se comprueba el cambio cuando el elemento pierde el foco.

� • Ejemplo:

� onFocus

� – El evento se dispara al tomar el foco.

� • Ejemplo:

� onBlur

� – El evento se dispara al perder el foco.

� • Ejemplo:

Eventos del ratón y el teclado sobre campos de formulario tipo TEXTAREA

Maneja exactamente los mismos eventos que el input type=text tal como puedes ver en estos ejemplos

.

tanbién maneja los siguientes:

� onChange

� – El evento se dispara al salir de campo de texto depués de haber modificado su contenido

� • Ejemplo:

� onFocus

� – El evento se dispara al tomar el foco que equivale a situar el cursor dentro del campo de texto bien mediante el ratón o desplazándose entre campos por medio de la tecla de tabulación

Page 528: Manual PHP

� • Ejemplo:

� onBlur

� – El evento se dispara al perder el foco que equivale a sacar el cursor del campo de texto bien mediante el ratón o desplazándose entre campos por medio de la tecla de tabulación

� • Ejemplo:

� onSelect

� – El evento se dispara al seleccionar texto dentro del campo

� • Ejemplo:

� onKeyPress

� – El evento se dispara al mantener pulsada una tecla cuando el cursor está dentro del campo

� • Ejemplo:

� onKeyUp

� – El evento se dispara al soltar una tecla pulsada cuando el cursor está dentro del campo

� • Ejemplo:

� onKeyDown

� – El evento se dispara al pulsar una tecla cuando el cursor está dentro del campo

� • Ejemplo:

� onKeyPress (leyendo el valor de la tecla pulsada)

� – El evento se dispara al mantener pulsada una tecla cuando el cursor está dentro del campo y el alert muestra el resultado de ejecutar una función javascript que captura el valor de la tecla pulsada. Esa funcion tiene la forma siguiente:

� <script type="text/javascript"> � function captura_teclado(e){ � var numero_de_tecla_pulsada; � var caracter_de_la_tecla_pulsada;

Page 529: Manual PHP

� if(window.event){ //cosas de IE � var numero_de_tecla_pulsada = e.keyCode //recoge en IE el valor ASCII de la tecla � } � else if(e.which){ // Cosas Netscape/Firefox/Opera � var numero_de_tecla_pulsada = e.which //recoge valor ASCII en otros navegadores � } � /* obtiene el caracter correspondiente al codigo ASCII recogido */ � caracter_de_la_tecla_pulsada = String.fromCharCode(numero_de_tecla_pulsada); � /* devuelve el valor del caracter correpondiente a la tecla pulsada */ � return caracter_de_la_tecla_pulsada; � }

</script>

� • Ejemplo (teclea dentro del campo):

El botón submit

No presenta mayores complicaciones. Es un input type button (también podría ser una imagen que lleva asociado el evento onClick que lleva implícita la función submit() (enviar) el formulario que se indica.

Haz clic

Haz doble clic

Quita el ratón

Pulsa cualquier botón

Suelta cualquier boton

Un ejemplo de uso de eventos

En este ejemplo ya contemplado en la página anterior hemos modificado únicamente la forma de modificar los diferentes valores de los elementos. Ahora usaremos algunos de los eventos aquí descritos y comprobaremos que se les puede asociar más de una instrucción javascript (observa el código fuente de los que requieren pulsar una tecla determinada) y, observa también, que un mismo elemento puede tener asociadas acciones distintas para dos eventos distintos. Tal es el caso de la opción que reduce la imagen al colocar el ratón encima y recupera el tamaño al quitarlo.

Ejecutar ejemplo Ver código fuente

Cambios en los contenidos de una página

Cambios de contenido de los elementos de una página

Los contenidos de los elementos de una página web (vistos como objetos JavaScript) son recogidos como valores en una de sus propiedades. El problema es que dependiendo del tipo objeto la propiedad será distina y por tanto habrá que tomar en consideración este aspecto.

A modo de ejemplo, para conocer el contenido de un elemento del tipo div tendríamos que utilizar la propiedad innerHTML de modo que:

document.getElementById('identificador_del_div').innerHTML

contendría el valor de ese elemento. Igual que hacíamos en con las propiedades de los objetos en PHP la lectura de ese valor podría hacerse por medio de:

document.getElementById('identificador_del_div').innerHTML=nuevo_contenido

Ver ejemplo Ver código fuente

Page 530: Manual PHP

Intentaremos incluir en las líneas siguientes una lista de los diferentes elementos de una página y de las propiedades que permiten modificar sus contenidos.

� – Objetos con las etiquetas: <div>, <p>, <td>, <ul>, <li>, <tr>*, <table>* (Las señaladas con * parecen no funcionar en Internet

Explorer)

� • La lectura y/o modificación de los contenidos de los objetos de estos tipos puede hacerse por medio de la propiedadinnerHTML. Si un objeto contiene otros objetos el valor de la propiedad innerHTML incluirá a estos. Puedes verlo en este ejemplo.

Ver ejemplo Ver código fuente

� � • Para esas lecturas y/o modificaciones pueden utilizarse funciones similares a estas:

� <script type="text/javascript"> � function lee (identificador){ � alert(document.getElementById(identificador).innerHTML); � } � function cambia (identificador,nuevo_valor){ � document.getElementById(identificador).innerHTML=nuevo_valor; � alert(document.getElementById(identificador).innerHTML); � }

</script>

� • Aquí puedes ver un ejemplo de utilización en los diferentes elementos.

Ver ejemplo Ver código fuente

� � – Objetos con las etiquetas: <img> o <iframe>(Las señaladas con * parecen no funcionar en Internet Explorer)

� • La lectura y/o modificación de los contenidos de los objetos de estos tipos puede hacerse por medio de la propiedad src. Puedes verlo en este ejemplo.

Ver ejemplo Ver código fuente

� � • Para esas lecturas y/o modificaciones pueden utilizarse funciones similares a las contempladas en el párrafo anterior. Una ligera

modificación en su estructura, introduciendo un nuevo parámetro (tipo) y un operador condicional podrá permitirnos utilizar la misma función en ambas modalidades:

� <script type="text/javascript"> � function lee (identificador,tipo){ � if(tipo=='imagen' || tipo=='iframe'){//condicion particular � alert(document.getElementById(identificador).src); � }else if(tipo=='' || tipo==null){// condicion general � alert(document.getElementById(identificador).innerHTML); � } � } � function cambia (identificador,nuevo_valor,tipo){ � if(tipo=='imagen' || tipo=='iframe'){// condicion particular � document.getElementById(identificador).src=nuevo_valor; � alert(document.getElementById(identificador).src); � }else if(tipo=='' || tipo==null){// condicion general � document.getElementById(identificador).innerHTML=nuevo_valor; � alert(document.getElementById(identificador).innerHTML); � } � }

</script>

� • Aquí puedes ver un ejemplo de utilización de estas nuevas funciones con los diferentes elementos.

Ver ejemplo Ver código fuente

� � – Objetos con la etiqueta: <object> que permite incluir como tal: imágenes, vídeos, sonidos o, de forma similar a iframe, páginas web.

Page 531: Manual PHP

� • El uso de objetos parece que va ser la opción de futuro. Sin embargo no es fácil superar los obstáculos que actualmente se plantean cuando se trata de utilizar código compatible entre los navegadores más usados. Parafraseando a don Quijote diremos aquello de «Con la iglesia hemos dado, Sancho» aunque este caso no se trate de los muros de la parroquial de El Toboso sino con las diferentes maneras de interpretar los objetos por parte de los distintos navegadores. Intentaremos analizar cada uno de los tipos (tipos MIME) de contenidos de los objetos.

� • Imágenes

� Este tipo de objetos se incluyen en los documentos mediante la sintaxis: <object data="ruta y nombre del fichero" class="(opcional)" style="(opcional)" id="(opcional)"></object> Como podrás observar el único atributo de la etiqueta object que tiene condición de imprescindible es data. Loa restantes class, style (hacen alusión a los estilos aplicables) e id son opcionales aunque para los fines aquí propuestos (modificación de contenidos) el atributo id es imprescindible. El caso de las imágenes es el que ofrece mayor compatibilidad entre navegadores. Para leer datos de la imagen actual bastaría con leer la propiedad data. Para modificarlos sería suficiente modificar el valor de esa propiedad tal como puedes ver en este ejemplo. Como podrás observar no hemos incluido el tipo MIME ya que, al parecer, los navegadores habituales (Firefox, Opera, Chrome, IE y Safari) son capaces de gestionar imágenes en los formatos más frecuentes (jpg, png, gif) sin necesidad de especificar su tipo MIME concreto.

Ver ejemplo Ver código fuente

� • Vídeos

� La proliferación de formatos de vídeo y las especificaciones poco estandarizadas de los navegadores hacen que la gestión de este tipo de archivo resulte un auténtico rompecabezas. Ante esta situación hemos optado por «la calle del medio» que no es otra cosa que tratar de insertar los vídeos en un formato único (el de extensión .swf por ser uno de los más populares) y sugerir la transformación del formato cuando nuestros originales procedan de fuentes en otro formato. Existen aplicaciones gratuitas que permiten efectuar ese tipo de transformaciones sin dificultad alguna. Considerando ese formato la inclusión de un fichero de vídeo requeriría el uso de una sintaxis como esta:

� <object data="nombre del video" type="application/x-shockwave-flash" width="ancho" height="alto" id="(opc)"> � <param name="movie" value="nombre del video" /> � <param name="quality" value="best" />

<param name="bgcolor" value="#cccccc" /> <param name="scale" value="noScale" /> <param name="play" value="false" /> <param name="loop" value="0" />

� </object> � Los valores marcados en rojo son obligatorios y los incluidos en cursiva opcionales. Podrás observar una información

aparentemente redundante. El nombre del vídeo se incluye como valor de parámetro movie (por compatibilidad con Internet Explorer) y como data (por compatibilidad con los restantes navegadores habituales). Los parámetros play pueden establecer con un valor booleano (cuando se configura como true la reproducción de vídeo comenzará inmediatamente después de cargarse la página, si se hace false será necesario que el usuario la active de forma manual). Cuando se trata de loop si se le asigna el valor false la reproducción se detendrá en el momento en que se haya llegado al final del mismo. Si se configura como true al llegar al final de vídeo empezará una nueva reproducción del mismo. En cuanto al parámetro scale admite como valores: default (permite visualizar sin distorsión el vídeo completo ajustándolo a las dimensiones especificadas en el style del mismo. Las zonas no ocupadas por la imagen aparecerán del color especificado en el parámetro bgcolor), noborder (ajusta el tamaño del vídeo para que ocupe todo el área especificada sin distorsionar la imagen. Para evitar la distorsión recortaría la imagen si fuera preciso),exactfit (rellena todo el áres especificada distorsionando la imagen si fuera necesario) o noscale (impide la ampliación de la imagen para ajustarse al area especificada. ) El parámetro quality admite como valores: low (antepone la velocidad de reproducción al aspecto y nunca suaviza las imágenes), autolow (prioriza la velocidad de reproducción pero intenta mejorar el aspecto y suavizar las imágenes siempre que las características del hardware lo permitan), autohigh (establece igual prioridad inicial a velocidad y calidad. Si la visualización se ralentiza mejora la velocidad de reproducción sacrificando el suavizado de la imagen), medium (es una calidad intermedia entre la baja -Low- y la alta -high-), high (prioriza el aspecto a la velocidad de reproducción) o best (proporciona la mejor calidad de visualización sin tener en cuenta la velocidad de reproducción).

� Las modificaciones, cuando se trata de compatibilizar navegadores, sí que abren una ventana a la demencia. Cuando se trata de modificar los parámetros width ó heigth no surge ningún problema aparente. Basta con

Page 532: Manual PHP

utilizardocument.getElementById('identificador').setAttribute('width', nuevo valor). Si intentamos, mediante el mismo procedimiento modificar otros parámetros veremos que esa opción solo funciona en Internet Explorer.

� Pero, sin duda, lo más esperpéntico es cambiar el vídeo que se visualiza. Ahí si que hay una modalidad para cada una de las opciones. Cuando se trata de Internet Explorer se logra mediante:document.getElementById(identificador).movie=nuevo_valor y cuando una piensa que para otros navegadores la solución podría ser del tipo document.getElementById(identificador).data=nuevo_valor la ilusión se desvanece cuando uno intenta probar esa opción en Chrome. Allí no funciona. Una solución (más bien debería decir una «chapuza») puede ser esta:

� – Incluimos el objeto dentro de una div

� – Cuando el navegador sea Internet Explores modificamos el parámetro movie del objeto flash

� – Cuando el navegador no sea Internet Explorer reescribimos la totalidad del contenido del contenedor divmodificando en ese proceso el valor del parámetro data.

� – Una de las múltiples formas (seguro que no la más eficiente) podría ser la utilización de una función JavaScript similar a esta:

� /* la funcion recibe cinco parámetros: � contenedor: Recogería el nombre de la div creada para � contener el objeto vídeo. � identificador: es el identificador del objeto video � tipo: es un parámetro para diferenciar distintos tipos de objetos � atributo: recoge el nombre del atributo a modificar (data en este caso) � nuevo_valor: será el nombre, extensión y ruta del nuevo video */ � � function cambia_video (contenedor,identificador,tipo,atributo,nuevo_valor){ � if(tipo=='flash'){ � /* Comprobado que el tipo es flash, � comprobamos la propiedad appName del objeto navigator � si su valor es Microsoft Internet Explorer � será suficiente modificar el valor del parámetro movie. */ � if(navigator.appName=="Microsoft Internet Explorer"){ � document.getElementById(identificador).movie=nuevo_valor; � }else{ � /* Para navegadores diferentes a IE podemos unificar le proceso � leyendo el contenido de la div contenedora del objeto y recogiendo � su contenido en una variable que aquí llamamos anterior */ � var anterior=document.getElementById(contenedor).innerHTML; � /* Lo que debemos cambiar es el valor del atributo data � (lo hemos recogido del parámetro atributo) � asignándole por medio del signo = el nuevo valor que incluimos � entre comillas. */ � nuevo_valor=atributo+"=\""+nuevo_valor+"\""; � /* Tenemos que sustitur el data anterior por el nuevo_valor. � Creamos un nuevo objeto (new RegExp) expresión regular que � estará formada por el atributo (data en nuestro � caso) seguido de un signo igual. Detrás estaré el � el nombre actual (na serie de caracteres que identificaremos � por medio de los metacaracteres (.*) y acabados en unas comillas. � Invertimos la codicia (para delimitar en las primeras comillas � que encuentre) por medio del metacaracter ? que sigue al * � Como segundo parámetro incluimos el modificador "i" que hace � la expresión insensible a mayúsculas/minúsculas*/ � valor_a_reemplazar=new RegExp(atributo+'=.*?"',"i"); � /* aplicamos el método replace a la cadena que contiene los datos � actuales pasándole dos parámetros: valor a reemplazar y nuevo valor. � El resultado será la cadena ya actualizada */ � var renovado=anterior.replace(valor_a_reemplazar,nuevo_valor); � /* ya podemos modificar el elemento contenor insertándole � sus nuevos valores */ � document.getElementById(contenedor).innerHTML=renovado; � }

Page 533: Manual PHP

� } � }

� No podemos olvidarnos de que los vídeos incluidos por este procedimiento no disponen de elementos de control. Afortunadamente la gestión de esos controles es compatible con los diferentes navegadores por lo cual bastaría con ejecutar alguno de estoy métodos: objeto_flash.Play() (iniciaría la reproducción del vídeo),objeto_flash.Stop()(detendría la reproducción) y objeto_flash.Rewind() (rebobinaría el vídeo y lo devolvería a la posición inicial)

� Considerando todas estas variables podríamos llegar a algo como lo que puedes ver en este ejemplo.

Ver ejemplo Ver código fuente

� • Páginas web

� La inclusión de páginas web en en objetos no presenta grandes complicaciones sintácticas. Sería suficiente una sintaxis similar a esta: <object type="text/html" data="direccion" width="250" height="250" id="identificador"> </object>

� Ahora el tipo MIME sería text/html (adecuado a su contenido) y el parámetro data nos permitiría especificar la dirección URL. Parecería que la modificación de los contenidos pasaría por la modificación del parámetro data. Y es cierto, solo que «a medias». Hay navegadores que no responden a esa modificación y la solución (al menos una de las soluciones, seguramente ni la única ni la mejor) es hacer algo similar a lo comentado para el caso del vídeo. Incluyendo el objeto dentro de una div contenedora y reescribiendo el contenido de esa div parece ser que se logra la ansiada compatibilidad entre navegadores. La función que permitiera el cambio de contenidos podría ser el resultado de una modificación de la comentada anteriormente. Ahora puede aplicarse un procedimiento único razón por la cual ya no es preciso diferenciar por nombre del navegador.

� function cambia (contenedor,identificador,tipo,atributo,nuevo_valor){ � if(tipo=='flash'){ � if(navigator.appName=="Microsoft Internet Explorer"){ � document.getElementById(identificador).movie=nuevo_valor � }else{ � var anterior=document.getElementById(contenedor).innerHTML; � nuevo_valor=atributo+"=\""+nuevo_valor+"\""; � valor_a_reemplazar=new RegExp(atributo+'=.*?"',"i"); � var renovado=anterior.replace(valor_a_reemplazar,nuevo_valor); � document.getElementById(contenedor).innerHTML=renovado; � } � } � if(tipo=='web'){ � var anterior=document.getElementById(contenedor).innerHTML; � nuevo_valor=atributo+"=\""+nuevo_valor+"\""; � valor_a_reemplazar=new RegExp(atributo+'=.*?"',"i"); � var renovado=anterior.replace(valor_a_reemplazar,nuevo_valor); � document.getElementById(contenedor).innerHTML=renovado;

} }

Ver ejemplo Ver código fuente

� • Ficheros de sonido mp3

� La inclusión y reproducción de ficheros de sonido en una web vuelve a abrir todo un mundo de opciones y también de incompatibilidades. Por una parte los formatos y por otra las extensiones (plug-in) multimedia del navegador que va a mostrar los paneles de control en la página web. La «solución chapuza» (no es la más elegante pero probablemente nos retrasará un poco la demencia) podría ser la unificación. Podríamos pensar en unificar el formato de los ficheros de sonido. Existen aplicaciones gratuitas tales como pazera o Audacity que permiten la edición y el cambio de formato de audio. No hemos inclinado por unificar en formato mp3.

� El siguiente punto de unificación habrá de ser el plug-in reproductor. En este aspecto una opción que nos parece razonable y práctica es la desarrollada en flash por alsacreations.fr bajo licencia Creative Commons y que, tal como puede verse en su página principal, ofrecen varias opciones de paneles de control para el reproductor mp3.

� La utilización de esta opción requiere obtener el fichero dewplayer.zip y descomprimirlo en alguna parte del servidor. Ese archivo contiene una serie de ficheros con extensión .swf (son los distintos reproductores) e incluso unas instrucciones de uso.

Page 534: Manual PHP

� La utilización de este tipo de objetos requiere una sintaxis como esta: <object type="application/x-shockwave-flash" data="(ver comentario)" width="ancho" height="alto" id="ident"> <param name="movie" value="(ver comentario)" /> </object>

� La compatibilidad famosa nos obliga a incluir, junto con el tipo MIME específico de los ficheros flash, el parámetromovie con un value idéntico al requerido por data. Además de eso deberemos indicar el ancho y alto de visualización de plugin .swf (las dimensiones para las diferentes opciones puedes verlas en su página web).

� Los parámetros data y movie requieren particular atención. Están formados por: la ruta nombre y extension de fichero .swf que vayamos a utilizar como reproductor, seguida de ?mp3= y de la ruta, nombre y extensión de fichero mp3 que se pretende reproducir. Resultaría una expresión del tipo: /reproductores/dewplayer.swf?mp3=/sonidos/mi_sonido.mp3 El cambio de fichero a reproducir requeriría modificar el valor de movie en el caso de Internet Explorer o data en los restantes navegadores. Por lo que hemos podido comprobar, en este caso no es necesario recurrir a la inclusión del objeto en una div contenedora. Basta con asignar nuevo valor al parámetro data. Aquí tienes un ejemplo.

Ver ejemplo Ver código fuente

Cambios en los valores de los elementos de los formularios

Los diferentes elementos de los formularios incluyen, además de sus valores, algunas propiedades susceptibles de ser modificables por el usuario. Intentaremos ir viendo los diferentes elementos y sus propiedades modificables pero antes vamos a echar un vistazo a las diferentes formas de identificar los elementos de los formularios.

¿Como identificar un elemento de un formulario?

Los elementos de los formularios suelen tener como finalidad básica la transferencia de información a un servidor. Para que esa información pueda ser recogida es imprescindible que cada uno de los elementos incluya el atributo name tal como hemos comentado en esta página. Por tanto será requisito que los diferentes campos tengan, al menos, una estructura como esta:

� <input type="text" name="obligatorio" />

� <input type="password" name="obligatorio" />

� <textarea name="obligatorio" /></textarea>

� <select name="obligatorio" /><option>aaa</option>/><option>bbb</option></select>

� <input type="radio" name="obligatorio" />

� <input type="checkbox" name="obligatorio" />

Además de incluir el atributo name es imprescindible que los valores de los name sean distintos. De lo contrario al enviar los formularios tendríamos el problema de que el valor de último de los incluidos en el formulario sobrescribiría a los previos. Puedes verlo en este ejemplo:

Ver ejemplo Ver código fuente

La única excepción a esta obligatoriedad son los elementos de opción (los input type="radio") que como puedes rescordar aquí utilizan ese name común para formar agrupaciones.

Además del name podríamos incluir en cada uno de los elementos un identificador único utilizando el archimencionado atributoname. Aunque son perfectamente compatibles ambos atributos no resulta imprescindible,para nuestros propósitos, el uso de id. En cualquier caso, supongamos que disponemos de ambos atributos en una sintaxis tal como esta:

� <input type="text" name="obligatorio" id="identificador_unico" />

� <input type="password" id="identificador_unico"name="obligatorio" />

� <textarea id="identificador_unico"name="obligatorio" /></textarea>

� <select name="obligatorio" id="identificador_unico"/> <option id="identificador_unico">aaa</option>/>

Page 535: Manual PHP

<option id="identificador_unico">bbb</option> </select>

� <input type="radio" name="obligatorio" id="identificador_unico"/>

� <input type="checkbox" name="obligatorio" id="identificador_unico"/>

Como verás hemos puesto indentificadore (únicos) en todo, incluso en lo señalado como option. Para aludir a los elementos podemoas hacerlo ahora mediante el ya comentado:

document.getElementById('nombre del identificador') y podremos hacerlo por medio de una de estas otras dos opciones: document.getElementsByName('nombre').item(0) o document.getElementsByName('nombre')[0]

El método getElementById devuelve siempre un objeto único. Por el contrario, el método getElementsByName devuelvesiempre un array (incluso en los casos en lo que el array contenga un único elemento). Por esa razón es necesario ser más explícito e indicar a cual de los elementos del array nos referimos. Eso puede hacerse por medio del índice del array especificado como [indice] o mediante .item(indice). En este segundo supuesto se utiliza siempre el punto seguido de la palabra item y, entre paréntesis, el índice del array. Como es lógico en los casos en los que el array contenga un solo elemento el índice será cero.

¡Cuidado! Ten cuidado con la sintaxis. En un caso se usa getElementById y en el otro getElementsByName. No siempre prestamos a la s toda la atención que requiere.

¿Qué y cómo modificar?

� – Input type text, password y textarea

� Este tipo de elementos de los formularios permiten establecer y/o modificar los siguientes atributos:

� • disabled

� Este atributo puede asignarse incluyendo el atributo disabled="disabled" en la etiqueta <text> o en cualquier otra que defina un elemento.

� Puede activarse o desactivarse por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').disabled=true; � document.getElementById('identificador').disabled=false; � document.getElementsByName('nombre')[0].disabled=true; � document.getElementsByName('nombre')[0].disabled=false; � document.getElementsByName('nombre').item(0).disabled=true; � document.getElementsByName('nombre')item(0).disabled=false; � Cuando está activado impide la modificación de los contenidos de esos campos del formulario y, además, sus valores no son

enviados junto con el formulario. La variable identificada por el name de su formulario no será recibida por el servidor.

� • readOnly (cuidado con la sintaxis, JavaScript diferencia mayúsculas de minúsculas)

� Este atributo puede asignarse incluyendo el atributo readonly="readonly" en la etiqueta <text> o en cualquier otra que defina un elemento.

� Puede activarse o desactivarse por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').readOnly=true; � document.getElementById('identificador').readOnly=false; � document.getElementsByName('nombre')[0].readOnly=true; � document.getElementsByName('nombre')[0].readOnly=false; � document.getElementsByName('nombre').item(0).readOnly=true; � document.getElementsByName('nombre')item(0).readOnly=false; � Cuando está activado también impide la modificación de los contenidos de esos campos del formulario pero, a diferencia

de disabled permite el envío de los valores que contenga incluso cuando ese valor sea una cadena vacía.

Page 536: Manual PHP

� • value

� Este atributo puede asignarse incluyendo el atributo value="valor preasignado" en la etiqueta <text> o en cualquier otra que defina un elemento.

� Puede modificarsr por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').value='nuevo valor'; � document.getElementsByName('nombre')[0].value='nuevo valor'; � document.getElementsByName('nombre').item(0).value='nuevo valor';

Ver ejemplo Ver código fuente

� – Input type select

� También admite, y con las mismas consecuencias, la utilización de disabled. Además tiene una peculiaridad importante. Las diferentes posibilidades de elección se incluyen en el formulario dentro de etiquetas <option></option>

� Para referirnos desde JavaScript a una opción concreta tenemos varias posibilidades: � document.getElementById('identificador_del_select').options[indice] � La etiqueta select debe llevar un id. Las options constituyen un array escalar cuyos índices (empiezan en cero) y que mantiene el

mismo orden en que se van insertando las option. La prima tendrá índice cero, la segunda uno, etcétera. � document.getElementsByName('name_del_selector')[0].options[indice] � En este supuesto identificamos el select por su name. Tal como hemos comentado anteriormente el resultado se representa

mediante un array del que extraemos el primer elemento [0] (suponemos un solo select con ese nombre, de ahí el cero). Con este paso tenemos completada la primera parte de la elección. Hay que añadirle a cual de las opciones hacemos referencia y para ello usamos options[indice] igual que en el caso anterior.

� document.getElementById('identificador_especifico_de_la opcion') � Este último supuesto requiere que dentro de la etiqueta <option> se haya incluido un id para asignarle un identificador único � En el elemento elegido es factible seleccionar uno de estos atributos:

� • value � Por medio de esta propiedad y con una de estas sintaxis � document.getElementById('identificador').options[indice].value='nuevo valor'; � document.getElementsByName('nombre_del_select')[0].options[indice].value='nuevo valor'; � document.getElementsByName('identificador_de_la_opcion').value='nuevo valor';

� se permite modificar el valor de una opción. El cambio no afecta a la leyenda visualizada en pantalla pero sí modifica el contenido

transferido al servidor. Recuerda que la leyenda es considerada únicamente valor por defecto cuando no se asigna ningún value dentro de la etiqueta option

� • text � Por medio de esta propiedad y con una de estas sintaxis � document.getElementById('identificador').options[indice].text='nuevo texto'; � document.getElementsByName('nombre_del_select')[0].options[indice].text='nuevo texto'; � document.getElementsByName('identificador_de_la_opcion').text='nuevo texto';

� se permite modificar el texto de la leyenda de una opción. El cambio no afecta al valor enviado al servidor salvo que no haya sido

especificado el atributo value. Recuerda que el texto solo es tomado como value en el caso de que este no estuviera definido en la etiqueta option

� • selected � La selección de una de las opciones puede hacerse bien de la forma tradicional (eligiendo con el ratón en el selector) o bien por

medio de una sentencia javaScript independiente del selector. Mediante una de estas sintaxis:

� document.getElementById('identificador').options[indice].selected=true; � document.getElementsByName('nombre_del_select')[0].options[indice].selected=true; � document.getElementsByName('identificador_de_la_opcion').selected=true;

� podremos seleccionar la opción correspondiente. Al seleccionar una de las opciones se deseleccionarían las demás. � • remove � Por medio de este método puede eliminarse items de una lista de opciones. La sintaxis sería

� document.getElementById('identificador').remove(indice); � document.getElementsByName('nombre_del_select')[0].remove(indice);

Page 537: Manual PHP

� dónde índice es el número de orden (contado a partir de cero) de la posición que ocupa dentro del select la opción que pretendemos eliminar.

� • add � La selección de una de las opciones puede hacerse bien de la forma tradicional (eligiendo con el ratón en el selector) o bien por

medio de una sentencia javaScript independiente del selector. Mediante una de estas sintaxis:

� document.getElementById('identificador').options[indice].selected=true; � document.getElementsByName('nombre_del_select')[0]options[indice].selected=true; � document.getElementsByName('identificador_de_la_opcion').selected=true;

� podremos seleccionar la opción correspondiente. Al seleccionar una de las opciones se deseleccionarían las demás.

Ver ejemplo Ver código fuente

� � – Input type select multiple

� Mantiene los mismos métodos y propiedades ya comentado para el select simple. Solamente hay que tener en cuenta que se diferencia de aquel en que dentro de la etiqueta &select> incluye ahora los atributos:multiple="multiple" (para establecer la condición de multiple) size="numero" (para establecer el número de items que se visualizarán de forma simultánea). Además, hay que tomar en consideración algo de suma importancia. El atributo name ha de estar asociado con un array para permitir la transferencia de los múltiples valores eventualmente elegidos. La forma adecuada de hacerlo sería similar a: value="nombre_de_variable[ ]"

� Respecto a lo comentado para el input simple la única diferencia sería evitar el uso de la sintaxis que utilice el nombre_de_variable asignado en value. Es decir, no usar document.getElementsByName('nombre_del_select')comentada para el select simple.

Ver ejemplo Ver código fuente

� – Input type radio

� Los botones de opción tiene la peculiaridad de agruparse automáticamente sin más que asignarles el mismo valor al atributo name. De esa forma, cuando se selecciona una de las opciones se deseleccionan de forma automática todas las demás siempre que compartan su mismo name.

� Este tipo de elementos de los formularios permiten establecer y/o modificar los siguientes atributos:

� • disabled

� Este atributo puede incluirse en la etiqueta <input type="radio"> escribiendo disabled="disabled".

� Puede activarse o desactivarse por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').disabled=true; � document.getElementById('identificador').disabled=false; � document.getElementsByName('nombre')[indice].disabled=true; � document.getElementsByName('nombre')[indice].disabled=false; � Se usarían las dos primeras opciones cuando se haya establecido un id="identificador" como atributo de la etiqueta <input

type="radio">. En caso de que no exista ese id podemos hacer uso de las dos últimas opciones. En ellas nombre se refiere al valor común asignado a name siendo el valor de indice en[indice] el número de orden (empezando por cero) dentro del grupo de la opción. Como siempre ese número de orden atiende a aquel en el que han sido creados los diferentes elementos del grupo.

� • value

� Este atributo suele incluirse en la etiqueta <input type="radio"> escribiendo value="valor".

� Puede crearse o modificarse por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').value="valor_asignado"; � document.getElementsByName('nombre')[indice].value="valor_asignado"; � • checked

� Este atributo (indica que la opción está seleccionada por defecto) suele incluirse en la etiqueta <input type="radio"> escribiendo checked="checked".

� Puede crearse o modificarse por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').checked=true; � document.getElementById('identificador').checked=false; � document.getElementsByName('nombre')[indice].checked=true; � document.getElementsByName('nombre')[indice].checked=false;

Page 538: Manual PHP

� Cuando se selecciona una de las opciones (por medio del ratón o a través de una sentencia JavaScript) se deselecciona de forma automática cualquier otra que estuviera previamente seleccionada.

� • name

� Este atributo ya reseñado anteriormente puede crearse modificarse por medio de JavaScript utilizando una de las sintaxis siguientes:

� document.getElementById('identificador').name='nuevo_nombre'; � document.getElementsByName('nombre')[indice].name='nuevo_nombre';

¡Cuidado! Cuando se cambia el nombre el elemento deja de pertenecer al grupo. Por esa razón sería perfectamente factible seleccionarlo sin que se deseleccionen susantiguos compañeros de grupo.

� • agregar una nueva opción

� Agregar una nueva opción a un grupo requiere incluirla en el lugar adecuado (al lado de las opciones que comparten grupo) y asignarle un texto identificativo. Una de las opciones para poder llevar a cabo esa inclusión sería tomar la cautela de crear un contenedor (un elemento tipo div con un id a que llamaremos aquí contenedor aunque pueda tener cualquier nombre). El proceso de agregar una nueva opción consistiría en:

� – Leer el contenido de la div que incluye las opciones. Puede hacerse por medio de innerHTML � – Agregar a la cadena innerHTML la información correspondiente al nuevo elemento � – Actualizar el contenido de la div contenedora. � Esas modificaciones podrían hacerse por medio de una función similara a esta:

function agrega_radio(contenedor,nombre,texto,valor,identificador) { /* leemos el contenido actual del contenedor y lo guardamos en la variable contenido */ var contenido=document.getElementById(contenedor).innerHTML; if (identificador==null || identificador==''){ /*agregamos los datos del nuevo input sin incluir id especifico */ contenido += "<input type='radio' name='"+nombre+"' "; contenido += "value='"+valor+"' />"+texto; }else{ /*agregamos los datos del nuevo input incluyendo un id */ contenido += "<input type='radio' name='"+nombre+"' "; contenido += "id='"+identificador+"' value='"+valor+"' />"+texto; } /* reemplazamos los valores incluidos en el contenedor */ document.getElementById(contenedor).innerHTML=contenido; }

Ver ejemplo Ver código fuente

� – Input type checkbox

� Es conveniente tener bien presente que si dos elementos de distinto o igual tipo (con excepción de radio) tienen asignado un mismo name el valor transferido por medio del formulario será el último de los incluidos con ese nombre común. Puedes comprobarlo en este ejemplo.

Ver ejemplo Ver código fuente

� Es conveniente tener bien presente

que los distintos elementos de este tipo han de tener (por la razón comentada en el párrafo anterior) nombres distintos. Dicho esto los métodos y propiedades comentados para el caso del radio son aplicables en su mayoría en este tipo de elementos.

� La identificación de los elementos se haría ahora por medio de

� document.getElementById('identificador'); � document.getElementsByName('nombre')[0]

� El valor cero asignado en la segunda de las opciones se justifica por el hecho de que para cada nombre sólo existirá un elemento de

este tipo.

Ver ejemplo Ver código fuente

Page 539: Manual PHP

Esta miscelánea de posibilidades tiene una limitación importante. A la hora de efectuar modificaciones en los valores de los diferentes elementos ya que no dispone de medios para obtener esos nuevos valores desde el servidor. En la página siguiente intentaremos ver la utilidad y forma de utilización de AJAX para estos menesteres.

Empezando con Ajax

AJAX (Asynchronous JavaScript and XML)

Modificaciones parciales en una web

Sin meternos en grandes profundidades del JavaScript ni del llamado DOM (Document Object Model) lo conocido como AJAX(Asynchronous JavaScript and XML) no es un lenguaje de programación ni tan siquiera una herramienta. Es simplemente la combinación de otras tecnologías ya existentes entre las que cabe destacar: HTML y CSS, DOM y JavaScript, el objeto XMLHttpRequest y, también, XML. El uso conjunto de estas tecnología nos va a permitir intercambiar datos con el servidor y actualizar partes de una página web sin necesidad de recargar totalmente la página.

Un elemento imprescindible para estos propósitos es la interface conocida como XMLHttpRequest que, aunque desarrollada inicialmente por Microsoft, en la actualidad está integrada prácticamente todo los navegadores como un clase Javascript cuyos objetos se encargan, desde el propio navegador y a través de sus propios métodos, de realizar la comunicación bidireccional con el servidor.

Crear objetos JavaScript

Dado que XMLHttpRequest es un objeto JavaScript empezaremos por comentar la sintaxis de creación de objetos en este lenguaje que no difiere prácticamente en nada de la ya comentada cuando los creábamos en PHP. Igual que ocurría allí, mediante la sintaxis

objeto = new XMLHttpRequest()

incluida entre dos etiquetas <script type="text/javascript"></script> estaríamos creando un objeto de la clase XMLHttpRequest. Este será el elemento básico para el uso de AJAX y funcionará con las versiones más actuales de los navegadores habituales.

Por «no romper con las tradiciones», es posible que puedas encontrarte con algunos navegadores antiguos que no incluyen esta interface y que, en el caso de Internet Explorer, van a requerir el uso de una sintaxis alternativa tal como puedes ver en este ejemplo de código fuente.

<html> <head> <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"> <title>Objeto Ajax</title> <script type="text/javascript"> if(window.XMLHttpRequest) { objetoAjax = new XMLHttpRequest(); } else if(window.ActiveXObject) { objetoAjax = new ActiveXObject("Microsoft.XMLHTTP"); } alert(objetoAjax); </script> </head> <body> </body> </html>

Ver ejemplo

Page 540: Manual PHP

Propiedades del los objetos XMLHttpRequest

Es importante tener bien que la secuencia de utilización práctica de los objetos XMLHttpRequest que es la siguiente:

� • Se crea un objeto XMLHttpRequest

� • El objeto XMLHttpRequest realiza un petición al servidor

� • El servidor atiende la petición y devuelve su respuesta

� • El resultado obtenido por XMLHttpRequest es utilizado para visualizar los resultados de la petición

Igual que ocurría en PHP y tal como comentamos en la página anterior los objetos JavaScript tiene propiedades a las que se alude mediante la sintaxis nombre_del_objeto.nombre_de_la_propiedad. En el caso de los objetos de la clase XMLHttpRequest las propiedades más importantes son las relativas a los pasos enemurados anteriormente y que en resumen son las siguientes:

� – Información sobre el estado del objeto

� objeto.readyState

� – Informa sobre el estado actual del objeto que se resume en uno de estos cuatro valores:

� 0 En reposo. Sin comenzar una comunicación con el servidor

� 1 Transacción iniciada. Abierta la comunicación

� 2 Transacción en ejecución. Cabeceras recibidas

� 3 Transacción ejecutándose. Cargando datos

� 4 Transacción con el servidor completada

� – Información sobre el estado de la petición

� objeto.status

� – Devuelve el código de estado de la petición HTTP al servidor. Algunos de sus valores son los siguientes:

� 200 Petición correcta

� 400 Petición incorrecta

� 403 Acceso prohibido

� 404 Página no encontrado

� 500 Error interno del servidor

� objeto.statusText

� – Devuelve una cadena informativa sobre el estado de la petición HTTP al servidor

� OK Petición correcta

� Bad Request Petición incorrecta

� Forbidden Acceso prohibido

� Not Found Página no encontrado

� Internal Server Error Error interno del servidor

� – Información recibida desde el servidor

� objeto.responseText

� – Se utiliza para tratar los datos recibidos desde el servidor que no tienen formato XML

� objeto.responseXML

� – Se utiliza para tratar los datos suministrados en formato XML por el servidor. Para poder acceder a esa información la respuesta del servidor ha de tener el encabezado text/xml, application/xml. En caso contrario su valor sería null.

Métodos del los objetos XMLHttpRequest

Page 541: Manual PHP

Igual que ocurría en PHP y tal como comentamos en la página anterior los objetos JavaScript disponen de métodos a los que se alude con la sintaxis nombre_del_objeto.nombre_del_metodo(parametros). En el caso de los objetos de la clase XMLHttpRequest las métodos más usuales son los siguientes:

� objeto.getAllResponseHeaders()

� – Devuelve todas las de cabeceras HTTP de la petición como una cadena

� 0 En reposo. Sin comenzar una comunicación con el servidor

� 1 Transacción iniciada. Abierta la comunicación

� 2 Transacción en ejecución. Cabeceras recibidas

� 3 Transacción ejecutándose. Cargando datos

� 4 Transacción con el servidor completada

� objeto.getResponseHeader('nombre_de_la_cabecera')

� – Devuelve la cabecera HTTP cuyo nombre se incluye como parámetro. Algunos de esos nombres son:

� Server En reposo. Sin comenzar una comunicación con el servidor

� Date Transacción iniciada. Abierta la comunicación

� Content-length Transacción en ejecución. Cabeceras recibidas

� Content-type Transacción ejecutándose. Cargando datos

� Conection Transacción en ejecución. Cabeceras recibidas

� Keep-alive timeout:Es el tiempo en segundos que Apache esperará peticiones subsiguientes antes de cerrar una conexión persistente. Max: es el número de peticiones permitidas por conexión.

� objeto.open(metodo, URL, tipo, usuario, contraseña')

� – Prepara una petición de información al servidor, pero no la envía. Requiere los siguientes parámetros

� metodo Puede tomar los valores: GET, POST o PUT (este último de uso mucho más infrecuente)

� URL Es la dirección absoluta o relativa de la página solicitada al servidor. Como norma general, cuando se utiliza el método GET se incluyen junto con URL las variables que deseen incluirse en la petición. Es decir la ya conocida sintaxis: nombre_de_la_pagina.extension?variable1=valor1&variable2=valor2. Cuando se usa el método POST no se agregan valores de las variables aquí. En ese caso se incluirán (en forma de cadenas del tipo "variable1=valor1&variable2=valor2") como parámetros datos en la llamada al método sendque comentamos más abajo

� tipo Admite como valores true o false. Un valor true indica que el proceso del script continúa después del método send(), sin esperar a la respuesta, y false indica que el script se detiene hasta que se complete la operación, tras lo cual se reanuda la ejecución.

� usuario y contraseña Son parámetros opcionales para aquellos casos en que esos valores sean requeridos por el servidor.

� objeto.setRequestHeader(etiqueta, valor )

� – Permite establecer cabeceras personalizadas en la petición HTTP. Cuando se utiliza el método POST debe incluirse una llamada a este método –antes de invocar el método send()– incluyendo como parámetros etiqueta-valor los siguientes:"Content-Type", "application/x-www-form-urlencoded". Como podrás comprobar guarda una gran similitud con lo indicado para ENCTYPE cuando hablábamos del envío de formularios.

� objeto.send(datos)

� – Envía la petición al servidor. El parámetro datos debe incluirse obligatoriamente. Si no hay datos que deban enviarse se escribirá null. Se entienden como datos de este parámetro cadenas del tipo: "usuario=pepe&password=pepa" similares a las ya mencionadas cuando hacíamos alusión al método GET cuanto tratábamos los formularios

El «gran hermano» de XMLHttpRequest

Además de los métodos y propiedades antes aludidos un elemento imprescindible para su correcta utilización. Se trata del evento:

� objeto.onreadystatechange = gran_hermano()

Page 542: Manual PHP

� – Reacciona a cada cambio de estado –modificación de una propiedad, ejecución de un método, etcétera– del objeto ejecutando la función gran_hermano(). Dicho de otra forma, reacciona automáticamente cada vez que algo cambia pudiendo hacerlo de forma distinta en cada situación de acuerdo con lo especificado en la propia función gran_hermano() .

En este ejemplo puedes ver (los resultados varían dependiendo del navegador que estés usando) la lista de propiedades de un objetoXMLHttpRequest. La visualización utiliza un bucle Javascript con la siguiente sintaxis:

for (var=variable in nombre_del_objero){ //recorre el objeto pasando por cada una de sus propiedades document.write(variable+"<br>"); //escribimos el nombre de la propiedad concatenado con un salto de linea }

Ver ejemplo Ver código fuente

Lectura de datos de un fichero de texto

Empezaremos con un ejemplo muy sencillo. Se trata de utilizar el objeto XMLHttpRequest para acceder al servidor, leer el contenido de un fichero de texto e incluir su contenido en una div de la página actual. El proceso es el siguiente:

� – Creamos un objeto XMLHttpRequest

� – Preparamos una petición al servidor

� – Activamos el detector de eventos para que escriba el resultado de la petición una vez se confirme que la petición al servidor ha resultado correcta y que la transacción se ha completado.

� – Enviamos la petición al servidor y esperamos el final de la transacción

La forma de creación del objeto XMLHttpRequest ha sido descrita más arriba. El resto del proceso podría incluirse en una función como esta:

/* la función recibe dos parámetros, la URL de la página que va a solicitarse al servidor (origen) y la div en la que van a escribirse los resultados */ function obtenerDatos(origen, dest){ /* comprueba que existe el objeto XMLHttpRequest */ if(objetoAjax) { /*recogemos el valor dest en la variable GLOBAL destino su condición de global se debe a que no lleva delante la palabra reservada var. De esta forma (GLOBAL) destino estará disponible para cualquier funcion o parte del script */ destino=dest; /*preparamos la petición mediante el open. Incluimos el parámetro GET y el la dirección de la URL que solicitaremos al servidor. No incluimos el parámetro tipo con lo cual tomará su valor por defecto que es true. */ objetoAjax.open("GET", origen); /* asignamos al evento cambio de estado la ejecucion de la funcion gran_hermano. Observa que no ponemos paréntesis en la llamada. Elo se debe a que lo que pretendemos es asignar al evento la ejecucion de la función. Si incluyéramos los paréntesis tipicos gran_hermano() lo que estaríamos tratando de asignar al evento no sería la ejecución de la función sino el resultado de esta. */ objetoAjax.onreadystatechange = gran_hermano; /* preparada la peticion y activada la captura de eventos ya solo nos queda enviar la peticion al servidor */ objetoAjax.send(null); } //cerramos el if } //finalizamos la funcion /* esta funcion no recibe ningun parámetro pero tiene disponible el valor de

Page 543: Manual PHP

la variable destino creada (creada como GLOBAL) por la función anterior */ function gran_hermano(){ /* comprueba si la propiedad readyState vale 4 (Transaccion completada) y que la petición haya sido considerada correcta por el servidor (status=200)*/ if (objetoAjax.readyState==4 && objetoAjax.status==200) { /* si se completo correctamente la transacción escribe en la div de destino el texto recibido del servidor (responseText) */ document.getElementById(destino).innerHTML = objetoAjax.responseText; }else{ /* si no se completó la peticion le indicamos que escriba "procesando " */ document.getElementById(destino).innerHTML ="procesando.."; } }

Aquí tienes dos casos de utilización de esa función. Observa las diferencias de respuesta.

Ver ejemplo Ver código fuente Ver ejemplo Ver código fuente

Habrás podido observar que el segundo ejemplo presenta incorrectamente algunos carácteres. La explicación es simple. En el primer caso al crear el fichero de texto lo hemos guardado con codificación UTF8 que es la que lee correctamente. El segundo fichero, idéntico al anterior, ha sido guardado con codificación ANSI y ese el origen de los problemas de visualización de los carácteres con tilde, las eñes, etcétera.

Existen otras soluciones alternativas a descrita anteriormente. En vez de la función gran_hermano incluimos una función anónima tal como puedes ver en este código fuente.

/* la función recibe dos parámetros, la URL de la página que va a solicitarse al servidor (origen) y la div en la que van a escribirse los resultados */ function obtenerDatos(origen, dest){ /* comprueba que existe el objeto XMLHttpRequest */ if(objetoAjax) { /*recogemos el valor dest en la variable GLOBAL destino su condición de global se debe a que no lleva delante la palabra reservada var. De esta forma (GLOBAL) destino estará disponible para cualquier funcion o parte del script */ destino=dest; /*preparamos la petición mediante el open. Incluimos el parámetro GET y el la dirección de la URL que solicitaremos al servidor. No incluimos el parámetro tipo con lo cual tomará su valor por defecto que es true. */ objetoAjax.open("GET", origen); /* asignamos al evento cambio de estado la ejecucion de la funcion anómina que definimos con la palabra function() ahora con paréntesis ya que esa es exigencia para su definición*/ objetoAjax.onreadystatechange = function(){ /* comprueba si la propiedad readyState vale 4 (Transaccion completada) y que la petición haya sido considerada correcta por el servidor (status=200)*/ if (objetoAjax.readyState==4 && objetoAjax.status==200) { /* si se completo correctamente la transacción escribe en la div de destino el texto recibido del servidor (responseText) */ document.getElementById(dest).innerHTML = objetoAjax.responseText; }else{ /* si no se completó la peticion le indicamos que escriba "procesando " */ document.getElementById(dest).innerHTML ="procesando.."; } } /* preparada la peticion y activada la captura de eventos ya solo nos queda enviar la peticion al servidor */

Page 544: Manual PHP

objetoAjax.send(null); } //cerramos el if } //finalizamos la funcion

Estos son los resultados utilizando esta nueva sintaxis

Ver ejemplo Ver código fuente Ver ejemplo Ver código fuente

Una petición que envía datos y recoge resultados

� – Mediante GET

� En este primer ejemplo utilizaremos GET. En tal situación incluiremos las variables y sus valores junto con la URL utilizando la sintaxis clásica: multiplica.php?a=13&b=47..

� No es necesario incluir objetoAjax.setRequestHeader()

� Se utiliza objetoAjax.send(null) ya que los datos fueron incluidos junto con la URL

Ver ejemplo Ver código fuente Ver código fuente del script

� – Mediante POST

� En este otro ejemplo utilizaremos POST. En esta ocasión pondremos como valor del parámetro URL del método openúnicamente multiplica.php

� Incluimos objetoAjax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") antes de invocar el métodosend

� Ahora deberemos incluir los datos. objetoAjax.send("a=13&b=48$c=Hola").

Ver ejemplo Ver código fuente Ver código fuente del script

¡Cuidado! Observa las diferencias entre los resultados de ambos ejemplos. Cuando se usa GET la correcta visualización de los resultados en el script php (en este caso multiplica.php) requeriría tratar la cadena de texto por medio de la función PHP utf8_decode. No olvides que esta función, junto con utf8_encode pueden resultar muy útiles en muchas ocasiones.

Aplicación de AJAX al autocompletado de formularios

Cada día es más frecuente encontrarnos con páginas en las que los datos a incluir dentro de un campo de un formulario se nos van autosugiriendo a medida que vamos escribiendo caracteres en ese campo. A lo largo de los párrafos siguientes intentaremos ir creando, paso a paso, uno de esos formularios.

� 1.º Incluir en el <head> del documento el código JavaScript que cree un objeto XMLHttpRequest

� • De esta forma el objeto XMLHttpRequest se creará al cargarse la página y lo tendremos disponible para cuando sea necesario. La sintaxis es idéntica a la vista en ejemplos anteriores.

� 2.º Incluir un <input type="text"> con un identificador y configurarlo para que capture el evento soltar una tecla.

� • El objeto podría tener un comportamiento como este: donde a medida que vamos pulsando teclas dentro del campo nos aparecen alertas donde se visualiza el contenido actual del campo.

� • La sintaxis del input anterior es: <input type="text" id="entrada" name="miprueba" value="" onKeyUp="alert(this.value)"; />. Identificaremos el elemento como entrada, le asignamos como name miprueba e incluiremos el evento onKeyUP (soltaruna tecla lo cual implica necesariamente haberla tenido que pulsar antes y en consecuencia haber añadido o eliminado un carácter en el campo

Page 545: Manual PHP

de texto). Este evento activa un función (en este caso alert, más adelante hemos de cambiarla y sustituirla por el nombre de una función) y además, por medio del parámetro this.value estamos recogiendo el valor actual de ese campo.

� 3.º Necesitamos disponer y visualizar todos los valores posibles que contengan los carácteres tecleados. Para visualizarlos necesitaremos agregar un elemento a la página, por ejemplo una <div id="pruebas"> que será la encargada de visualizar los resultados.

� • Supongamos que disponemos de los datos en una tabla MySQL llamada paises, que contiene un campo llamado pais en el que se incluyen los nombres de los paises del mundo

. � • Necesitamos un script PHP que consulte esa tabla cada vez que modificamos el input (tecleando o borrando) y que esa consulta

considere coincidencias todos los registros que contengan la cadena escrita en el input.

. � • Entre las muchas opciones posibles cabría efectuar esa consulta utilizando una claúsula WHERE Nombres LIKE

'%$busca%' (bucaría el contenido de la variable $busca en cualquier posición del nombre del país) o por medio de WHERE Nombres LIKE '$busca%' buscaría unicamente la coincidencia al comienzo del nombre. Este podría ser el script de una consulta de este tipo:

Ver ejemplo Ver código fuente

� 4.º Una vez disponemos del script que realiza la consulta MySQL ya solo necesitamos hacer algunas modificaciones en la página

que el objeto XMLHttpRequest

� Modificar la función invocada por el evento onKeyUp en el campo input escribiendo algo como: <input type="text" id="entrada" name="miprueba" value="" onKeyUp="obtenerDatos('ejemplo779.php?busca='+this.value,,'pruebas');" />

� De esta forma cada vez que se pulsara una tecla se ejecutaria la función obtenerDatos pasando dos parámetros: la cadena formada por ejemplo779.php?busca= (nombre del script que ejecuta la búsqueda seguido del nombre de la variable utilizada para aquella búsqueda y del signo igual) concatenada con el valor actual del campo de texto this.value El segundo paramétro es identificador del div al que hemos llamado pruebas y que será el lugar dónde aparecerán los resultados.

� Tenemos que definir la función JavaScript obtenerDatos() que puede tener una sintaxis como esta:

� function obtenerDatos(origen, destino){ � if(objetoAjax) { � /* la variable origen incluye en este caso la cadena de busqueda*/ � objetoAjax.open("GET",origen); � objetoAjax.onreadystatechange = function(){ � if (objetoAjax.readyState==4 && objetoAjax.status==200){ � /* el estilo de la div destino tiene asignado display=none, eso significa � que ahora que va a tener datos hay que cambiar es estilo a block � para que pueda visualizarse */ � window.document.getElementById(destino).style.display='block'; � /* se visualiza la lista utilizando el estilo que asignado a ese elemento � en el script de búsqueda. Los resultados resultantes de la consulta � aluden a una funcion llamada selecciona que aun no ha sido definida */ � window.document.getElementById(destino).innerHTML=objetoAjax.responseText; � } � } � objetoAjax.send(null); � }

}

� Aún tendríamos pendiente definir la función selecciona() que hemos asociado al evento onClick para cada uno de los elementos de la lista resultante de la búsqueda. El objetivo de esa función sería que al hacer clic se rellenara el campo de texto del formulario con el valor incluido en ella como parámetro y que dejara de visualizarse la capa que contiene la lista de elementos. Para lograr esos fines bastaría con que fuera similar a esta:

� function selecciona(valor){ � document.getElementById('entrada').value=valor; � document.getElementById('pruebas').style.display='none';

}

� El resultado de lo comentado hasta ahora sería algo como lo que puedes ver en este ejemplo en el que hemos optado con utilizar la opción GET al usar el método open.

Page 546: Manual PHP

Ver ejemplo Ver código fuente

� El uso de la opción POST requeriría una pequeña modificación de la funcion javascript obtenerDatos() que podría quedar ahora de

la forma siguiente:

� function obtenerDatos(valor, origen, destino){ � if(objetoAjax) { � /* formamos una cadena de consulta con la palabra busca, nombre � de la variable y el valor capturado de input text. Esa cadena � será incluida como parámetro al invocar el metodo send */ � cadena_de_busqueda="busca="+valor; � /* la variable origen incluye en este caso unicamente el nombre del script*/ � objetoAjax.open("POST",origen); � objetoAjax.onreadystatechange = function(){ � if (objetoAjax.readyState==4 && objetoAjax.status==200){ � /* el estilo de la div destino tiene asignado display=none, eso significa � que ahora que va a tener datos hay que cambiar es estilo a block � para que pueda visualizarse */ � window.document.getElementById(destino).style.display='block'; � /* se visualiza la lista utilizando el estilo que asignado a ese elemento � en el script de búsqueda. Los resultados resultantes de la consulta � aluden a una funcion llamada selecciona que aun no ha sido definida */ � window.document.getElementById(destino).innerHTML=objetoAjax.responseText; � } � } � /* el uso de POST nos obliga a llamar a este método incluyendo sus encabezado */ � objetoAjax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") � /* la cadena_de_busqueda se incluye ahora como parámetro del método POST */ � objetoAjax.send(cadena_de_busqueda); � }

}

Ver ejemplo Ver código fuente

Los objetos en JavaScript (JSON)

Los objetos JavaScript

Clases y prototipos

En la página anterior nos referíamos a la creación de objetos XMLHttpRequest por medio de objeto =new XMLHttpRequest() que nos recordaba, al menos desde el punto de vista operativo, la forma de hacerlo en PHP.

Sin embargo existen diferencias sustanciales entre los objetos PHP y los objetos JavaScript. Los objetos PHP –como en la mayoría de los lenguajes de programación – están basadas en la utilización de clases. Por el contrario los objetos JavaScript no utilizan ninguna un concepto formal de clase sino que se crean mediante un proceso de copia o clonación, a partir de otros objetos ya existentes llamadosprototipos. Por esa razón cuando creábamos el objeto =new XMLHttpRequest() no utilizábamos clase sino que partíamos de otro objeto, el objeto XMLHttpRequest. Este hecho diferencial nos va a permitir disponer de varias posibilidades (también algunas restricciones)a la hora de manejar objetos en JavaScript.

Creación de objetos vacíos en JavaScript

Una de las formas de creación de un objeto JavaScript es utilizar la notación JSON (JavaScript Object Notation) que mediante:

nombre_del_objeto = {}

Page 547: Manual PHP

dónde nombre_del_objeto, será el nombre del nuevo objeto y {} la sintaxis obligatoria para la creación de un nuevo objeto –por el momento vacío– usando la notación JSON.

Otra de las opciones más simples es la utilización de la sintaxis:

nombre_del_objeto =new Object()

dónde nombre_del_objeto será, igual que en el caso anterior, el nombre del nuevo objeto y new Object() la sintaxis obligatoria (recuerda que JavaScript diferencia entre letras mayúsculas y minúsculas) para la creación de nuevo objeto. Como podrás observar, el nuevo objeto carece de propiedades y/o métodos. Por el momento es una especie de «objeto vacío».

También es posible la creación de objetos por medio de una función constructora que, de forma similar a las clases de PHP, nos permite, al ser invocada, la creación y la asignación de propiedades y sus valores a un nuevo objeto. En ese caso son necesarios dos elementos: la función constructora y la llamada a esa función. El primero de los elementos tendría una estructura de este tipo:

function funcion_constructora(parametro1, parametro2,...) { this.propiedad1 = parametro1 this.propiedad1 = parametro1 .... }

La palabra reservada this tiene una significación idéntica a la comentada para $this en PHP. De forma muy similar a lo que ocurría en aquel caso. De igual modo this.propiedad se comportaría en JavaScript de forma similar a $this->propiedad en PHP. En ambos casos aludiría a la propiedad en el objeto actual.

La creación de objetos utilizando una función constructora requeriría algo como esto:

nombre_del_objeto = new funcion_constructora (parametro1, parametro2,... )

ahora la palabra reservada new va seguida del nombre de la función constructora y de los paréntesis que pueden incluir los eventuales parámetros requeridos por la función. Esta forma de creación de objetos permite una variante. Es la siguiente:

variable = function funcion_constructora(parametro1, parametro2,...) { this.propiedad1 = parametro1 this.propiedad1 = parametro1 .... }

que sólo difiere de la anterior en el hecho de que hemos incluido la función constructora como valor de una variable. Ahora para la creación del nuevo objeto debemos invocar el nombre de la variable con una sintaxis similar a esta:

nombre_del_objeto = new variable (parametro1, parametro2,... )

No acaban aquí las posibilidades de creación de objetos JavaScript. Los objetos con notación JSON pueden ser creados a partir de una cadena de texto. Esa posibilidad tiene particular interés ya que nos va a permitir crear objetos JavaScript a partir de cadenas recibidas por medio de una petición a un servidor. Es decir, se abre la posibilidad de intercomunicar objetos JavaScript con PHP y también objetos PHP con JavaScript. Por el momento conformémosnos con conocer el procedimiento de creación de esos objetos JavaScript.

Por medio de la función eval podemos convertir una cadena de carácteres en un objeto JavaScript. Esta transformación requiere dos cosas importantes:

� – Qué la cadena empiece y acabe por paréntesis

� – Qué la cadena utilice la santaxis correcta de la notación JSON.

Si se cumplen las premisas anteriores la creación de un objeto (en notación JSON) se haría de la siguiente forma:

cadena= "{ ... contenido... }" nombre_del_objeto =eval('('+cadena+')')

Page 548: Manual PHP

dónde la cadena ha de empezar por { y acabar con }, su contenido ha de respetar estrictamente la sintaxis JSON (más adelante nos referiremos a ella) aunque por el momento no incluyamos nada y por tanto la utilizaremos para crear un «objeto vacío». Para la transformación de la cadena en objeto JavaScript utilizaremos la función eval() en la que incluiremos como parámetros el valor de la cadena resultante de agregar a la anterior un paréntesis de apertura y otro de cierre.

Cualquiera de las formas descritas permite crear objetos JavaScript tal como puedes ver en el ejemplo que encontrarás un poco más abajo.

<script type="text/javascript"> /* creamos un objeto con notación JSON de nombre objeto1 */ var objeto1 = {}; /* creamos un objeto vacío llamado objeto 2 */ var objeto2 = new Object(); /* creamos un funcion costructora vacía*/ function constructora(){ } /* creamos un objeto por medio de la funcion anterior */ var objeto3= new constructora(); /* creamos una variable cuyo contenido es una funcion por el momento esa función no tendrá contenidos */ var creadora= function otra_constructora(){ } /* creamos un nuevo objeto utilizando la variable creadora */ var objeto4= new creadora(); /* crearemos ahora un objeto JSON a partir de una cadena la cadena unicamente incluye las llaves */ var cadena="{}"; /* por medio de la funcion eval, evaluamos la cadena anterior, incluyendola previamente entre paréntisis. La función eval va a devolvernos un nuevo objeto, esta vez con notación JSON */ objeto5=eval('('+cadena+')'); /* vamos a comprobar la existencia de esos objetos (vacios) por medio de alert */ alert('El alert dice que el objeto1='+objeto1); alert('El alert dice que el objeto2='+objeto2); alert('El alert dice que el objeto3='+objeto3); alert('El alert dice que el objeto4='+objeto4); alert('El alert dice que el objeto5='+objeto5); </script>

Ver ejemplo Ver código fuente

Creación de objetos JavaScript incluyendo propiedades y valores

Los objetos creados en el ejemplo anterior no tienen ninguna utilidad práctica ya que carecen de propiedades (valores) y métodos (procedimientos o funciones) que son la esencia y razón de ser de los objetos. En los párrafos siguientes intentaremos las diferentes formas de añadir propiedades (junto con sus valores) a los diferentes objetos JavaScript clasificándolas en dos modalidades:

Agregar propiedades en el momento de crear los objetos

Cuando se trata de crear objetos utilizando la notación JSON pueden incluirse propiedades junto con sus valores utlizando la sintaxis:

nombre_del_objeto ={ "propiedad1" : "valor1" ,"propiedad2"" : "valor2", ... }

Page 549: Manual PHP

dónde las llaves ({ }), tal como comentamos anteriormente, son obligatorias como comienzo y final del objeto y dónde las parejas"propiedad" : "valor" representan el nombre que pretendemos asignar a cada propiedad y su valor. Son obligatorios los dos puntos : que separan ambos parámetros. Cuando vayamos a incluir más de una propiedad iremos separando mediante comas (,) las diferentes parejas propiedad valor.

Cuando se trabaja en entornos de PHP cosa que ocurre cuando un objeto JSON va a ser transferido a un servidor e interpretado desde PHP) hay que tener particular cuidado con los siguientes aspectos sintácticos:

� – Los datos que deban ser tratados por PHP han de tener codificacón UTF-8

� – Utilizar siempre comillas dobles (" ") para rodear tanto nombres como valores alfanuméricos

� – Los valores numéricos, booleanos y el valor null no requieren comillas

En los casos en los que la creación del objeto se realiza invocando su función constructura, los nombres de las propiedades y sus valores se establecen dentro de la propia función constructora por medio de la sintaxis: this.nombre_de_la_propiedad=valor_asignado

Cuando la creación del nuevo objeto Javascript se realiza por medio de eval() se utilizaría una notación similar a la descrita para JSON con la difrencia de que ahora, por tratarse de una cadena ha de incluirse todo entre comillas

cadena= '{ "propiedad1" : "valor1", "propiedad2" : "valor2"}' nombre_del_objeto =eval('('+cadena+')')

tratando siempre de usar comillas simples para las más externas y reservando las dobles para propiedades o valores, o, si se utilizan comillas dobles para las más externas utilizar \" en nombres y valores. En cualquiera de las opciones estaríamos logrando el objetivo propuesto que no es otro que evitar las comillas simples como delimitadores de nombres de propiedades o de sus valores.

Cuando los objetos JavaScript son creados por medio de una función constructora la asignación de propiedades y valores puede hacerse incluyéndolos en la propia función constructora por medio la sintaxis this.propiedad = valor

Agregar o modificar propiedades a objetos preexistentes

Para el caso de que necesitemos agregar una nueva propiedad a un objeto ya existente o modificar el valor de la misma disponemos de la opción:

objeto.propiedad= valor

dónde propiedad es el nombre de la nueva propiedad (o de la ya existente cuyo valor queremos modificar) y valor es el nuevo valor de dicha propiedad.

¿Qué puede incluirse como valor de una propiedad?

A las propiedades de los objetos puede asignárseles como valor:

� – Un valor númerico. Como es habitual no requiere que se incluya entre comillas. Los valores así asignados serán identificados por la función tipeof (objeto.propiedad) o tipeof(variable) como number.

� – Una cadena alfanumérica. Como es habitual requiere que se incluya entre comillas. Los valores así asignados serán identificados por la función tipeof (objeto.propiedad) o tipeof(variable) como string.

� – Una valor booleano. Requeriría asignar como valor true o false y no requiere que se incluya entre comillas esas palabra reservadas. Los valores así asignados serán identificados por la función tipeof (objeto.propiedad) o tipeof(variable) como boolean.

� – Una valor nulo. Requeriría asignar como valor null sin requerir que se incluya entre comillas esa palabra reservadas. Los valores así asignados serán identificados por la función tipeof (objeto.propiedad) o tipeof(variable) como null.

� – Un array escalar. Requeriría asignar como valor una expresión del tipo: [1,2,"a",3,"z"] en la que como puedes observar los valores encerrados en corchetes separados por comas siendo necesario encerrar entre comillas los valores alfanuméricos. También permitiría, tal como puedes ver en el ejemplo crear previamente el array (por medio de new Array) y después incluirlo asignado como valor el nombre del mismo. Los valores así asignados serán identificados por la función tipeof (objeto.propiedad) o tipeof(variable)como object.

Page 550: Manual PHP

� – Un objeto. Requeriría asignar como valor el nombre de objeto a incluir. Los valores así asignados serán identificados por la función tipeof (objeto.propiedad) o tipeof(variable) como object.

<html> <head> <title>Objetos JavaScript</title> <script type="text/javascript"> /* creamos un objeto con notación JSON de nombre objeto1 incluyendo algunas propiedades*/ var objeto1 = {"var nombre":"pepe1", "pass":"pepa", "edad":26, "usuario":true, "soporte":false, "antecedentes":null, "miarray":["a",2,"b",4] }; /* creamos un objeto vacío llamado objeto 2 */ var objeto2 = new Object(); /* aqui las propiedades las agregamos invocando el objeto recien creado por medio de esta sintaxis */ objeto2.nombre="pepe2"; objeto2.pass="pepa2"; objeto2.edad=16; objeto2.usuario=false; objeto2.soporte=true; objeto2.antecedentes=25; objeto2.miarray=["a",2,"b",4]; /* creamos un funcion costructora incluyendo las propiedades y sus valores correspondientes*/ function constructora(){ this.nombre="pepe3"; this.pass="pepa3"; this.edad=36; this.usuario=false; this.soporte=true; this.antecedentes=null; this.miarray=["a",2,"b",4]; } /* creamos un objeto por medio de la funcion anterior. Se le asignarán, de forma autómatica, las propiedades incluidas en la funcion constructora */ var objeto3= new constructora(); /* creamos una variable cuyo contenido es una funcion asignándole sus propiedades después de leer los parámetros recibidos al ser invocada la función. El operador ternario asinga valores por defecto a las propiedades en el caso que no sean asignados en la llamada a la función constructora */ var creadora= function otra_constructora(a,b,c,d,e,f,g){ (a == 'undefined' || a == null || a == '') ? this.nombre="pepe3" : this.nombre=a; (b == 'undefined' || b == null || b == '') ? this.pass="pepa3" : this.pass=b; (c == 'undefined' || c == null || c == '') ? this.edad=35 : this.edad=c; (d == 'undefined' || d == null || d == '') ? this.usuario=false : this.usuario=d; (e == 'undefined' || e == null || e == '') ? this.soporte=true : this.soporte=e; (f == 'undefined' || f == null || f == '') ? this.antecedentes=null : this.antecedentes=f; (g == 'undefined' || g == null || g == '') ? this.miarray=["a",2,"b",4] : this.antecedentes=f; }

Page 551: Manual PHP

/* creamos un nuevo objeto utilizando la variable creadora */ var objeto4= new creadora('luis','',58); /* crearemos ahora un objeto a partir de una cadena escrita con notación JSON Evitaremos incluir saltos de línea en la cadena. Cuando por la razón que sea necesitamos "partirla en trozos" utilizamos el operador de concatenación (+) */ var cadena='{"nombre":"pepe5","pass":"pepa5","edad":26,'; cadena +='"usuario":true,"soporte":false,"antecedentes":null,"miarray":["a",2,"b",4]}'; /* por medio de la funcion eval, evaluamos la cadena anterior, incluyendola previamente entre paréntisis. La función eval va a devolvernos un nuevo objeto, esta vez con notación JSON */ objeto5=eval('('+cadena+')'); </script> </head> <body> <h3>Listado de propiedades del objeto1</h3> <!-- el bucle variable in objeto nos permitirá ir recorriendo el objeto como un array. Los sucesivos valores de variable serán los nombres de las propiedades y los de objeto[variable] los valores actuales de la propiedad contenida en variable --> <script type="text/javascript"> for (i in objeto1){ document.write(i+"="+objeto1[i]+"<br />"); } </script> <h3>Listado de propiedades del objeto2</h3> <script type="text/javascript"> for (i in objeto2){ document.write(i+"="+objeto2[i]+"<br />"); } </script> <h3>Listado de propiedades del objeto3</h3> <script type="text/javascript"> for (i in objeto3){ document.write(i+"="+objeto3[i]+"<br />"); } </script> <h3>Listado de propiedades del objeto4</h3> <script type="text/javascript"> for (i in objeto4){ document.write(i+"="+objeto4[i]+"<br />"); } </script> <h3>Listado de propiedades del objeto5</h3> <script type="text/javascript"> for (i in objeto5){ document.write(i+"="+objeto5[i]+"<br />"); } </script> <h3>Agregamos una nueva propiedad y listamos el objeto1</h3> <script type="text/javascript"> objeto1.nuevapropiedad="soy la nueva"; for (i in objeto1){ document.write(i+"="+objeto1[i]+"<br />"); } </script> <h3>Modificamos una propiedad y listamos el objeto5</h3> <script type="text/javascript"> objeto5.nombre="Me han cambiado de nombre"; for (i in objeto5){ document.write(i+"="+objeto5[i]+"<br />");

Page 552: Manual PHP

} </script> </body> </html>

Ver ejemplo

Opciones alternativas a la función eval()

La función eval() tiene la gran ventaja de su rapidez pero tiene también el incoveniente de que puede intepretar y ejecutar cualquier programa JavaSCript lo cual eleva los riesgos de su utilización cuando el origen de los datos evaluados (la cadena de texto incluida como parámetro en la llamada a la función eval()) no es de absoluta confianza. Para reducir esos riesgos resulta muy útil utilizar como alternativa un analizador JSON. Este analizador (parser) viene incluido como elemento nativo en los navegadores más habituales a partir de las versiones:Internet Explorer 8, Firefox 3.1, Safari 4, Chrome 3, y Opera 10.5.

Cuando los navegadores diponen de JSON nativo están disponibles dos métodos muy interesantes:

objeto =JSON.parse(cadena, funcion_opcional(){...})

que crea un objeto JSON a partir de las parejas propiedad : valor contenidas en la cadena. Opcionalmente, cuando se incluye como segundo parámetro la función_opcional los valores contenidos en la cadena son transformados por medio esta función antes de ser asignados como valores del objeto JSON.

cadena =JSON.stringify(objeto)

realiza en proceso inverso a la anterior. En este caso, al apliclar éste método a un objeto JavaScript tendremos como resultado una cadena en notación JSON. Ambos métodos resultan de particular interés cuandos e trata de intercambiar información entre servidor-cliente y viceversa.

En el caso en que los navegadores no disponga de estos métodos en forma nativa es pueden incluirse en la página web documentos externos de JavaScript que los contengan. Desde la página oficial de JSON podremos acceder a un enlace que nos llevaría a este otro desde dónde podremos obtener un fichero con extensión zip que contiene varios ficheros .js. Entre ellos hay uno llamado json2.js que incluye los dos métodos comentados anteriormente. Bastaría con descomprimirlo, colocarlos en algún lugar del servidro y agregarlo como un fichero externo por medio de <script src="ruta_hasta_el_fichero/json2.js" type"text/Javascript"></script> incluido entre las etiquetas <head></head> de la página.

¡Cuidado! Es importante descargar el fichero comprimido al que se accede desde este enlace. Cuando hemos descargado directamente el fichero json2.js desde este otro enlace hemos tenido problemas porque al parecer este último fichero tiene errores. En cualquier caso, para facilitarte la labor, en este enlace tienes un fichero llamado json2.zip con la versión correcta de json2.js.

En el ejemplo que tienes a continuación puedes ver la forma de utilizar estos recursos. Presta atención a los comentarios porque es muy importante tener en cuenta las peculiaridades de la sintaxis para lograr un aceptable grado de compatibilidad entre navegadores.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>JSON</title> <script type="text/javascript"> /* Comprobamos la existencia del objeto JSON. Si existe es que la versión del navegador lo incluye. Si no existe el objeto JSON incluimos el fichero externo json2.js La particular manera de partir las palabra script tiene como finalidad evitar errores del navegador al interpertar ya que se presenta una confusión

Page 553: Manual PHP

al desconocer si debe tratarlo como una cadena o como un script que deba ejecutarse. La cabecera <!DOCTYPE HTML PUBLIC ... es imprescindible en este caso ya que el navegador IE dará un error «JSON no está definido» en el caso de no incluirla*/ if (typeof(JSON)==="undefined"){ alert('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); document.write('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); }else{ document.write("No es necesario el json2.js externo. El navegador incluye JSON<br>"); } /* ¡Cuidado! la etiqueta script debe cerrarse aquí, después de la comprobación y abrirse nuevamente para continuar con el resto de las instrucciones. De esta forma el fichero JSON ya estará disponible cuando se acceda desde el bloque que insertamos a continuación */ </script> <script type="text/javascript"> /* creamos la cadena con las propiedades y valores del objeto */ var cadena='{"nombre":"pepe5","pass":"pepa5","edad":26,"usuario":true,"soporte":false,'; cadena+='"antecedentes":null,"miarray":["a",2,"b",4]}'; /* utilizamos el método parse para crear el objeto */ objeto=JSON.parse(cadena); /* visualizamos las propiedades del objeto */ document.write("<br>Estas son las propiedade del objeto JSON<br>"); for (i in objeto){ document.write(i+"="+objeto[i]+"<br />"); } /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br>El objeto JSON convertido en cadena por JSON.stringify<br>"); document.write(JSON.stringify(objeto)); /* utilizaremos el segundo parámetro de la funcion para que invoque una funcion llamada funcion_opcional Esta vez no lleva los () porque lo que pretendemos de esa funcion no es que nos devuelva un valor sino que se ejecute*/ var objeto1 = JSON.parse(cadena, funcion_opcional); /* la llamada a la función opcional ´se hará reiterativamente mientras va recorriendo todos los pares propiedad:valor que son recibidos en la llamada a la función */ function funcion_opcional(propiedad, valor) { /* si el valor es tipo cadena le añadimos un texto delante */ if (typeof valor === 'string') { return "A&ntilde;adido + "+ valor; } /* si el valor es tipo boleano lo ponemos como false */ if (typeof valor === 'boolean') { return false; } /* si es tipo numero le multiplicamos por 7 */ if (typeof valor === 'number') { return valor*7; } /* si el valor es null lo cambiamos por 45 */ if (valor==null){ return 45; } /* si el valor no es de los tipos anteriores lo devuelve sin modificar */

Page 554: Manual PHP

return valor; } /* listamos las propiedades del objeto resultante */ document.write("<br>Listado de propiedades del objeto JSON modificado<br>"); for (i in objeto1){ document.write(i+"="+objeto1[i]+"<br />"); } /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br>El objeto modificado convertido en cadena por JSON.stringify<br>"); document.write(JSON.stringify(objeto1)+"<br><br>"); /* el metodo JSON.strinfgify es aplicable a cualquier objero JavaScript Crearemos un objeto incluyendo propiedades de todo tipo */ var una_matriz=new Array('Aviles','Oviedo','Gijón'); var otro_objeto= function crea_objeto(){ this.propiedad1="valor1"; this.propiedad2=45; this.propiedad3=true; this.propiedad4=null this.objeto_incluido=objeto; this.otro_objeto=objeto1; this.matriz=[1,2,"a","b"]; this.matriz2=una_matriz; } objeto_complejo=new otro_objeto(); /* listamos los tipos y valores de las propiedades del objeto resultatne*/ for (i in objeto_complejo){ document.write(typeof(objeto_complejo[i])+"-->"); document.write(i+"="+objeto_complejo[i]+" "); } document.write('<br><br>Listamos las propiedades, tipos y valores del objeto'); document.write(' incluido como propiedad en el objeto principal<br>'); for (i in objeto_complejo.objeto_incluido){ document.write(typeof(objeto_complejo.objeto_incluido[i])+"-->"); document.write(i+"="+objeto_complejo.objeto_incluido[i]+"<br>"); } /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante<br><br>"); document.write(JSON.stringify(objeto_complejo)); </script> </head> <body> </body> </html>

Ver ejemplo

Incluyendo métodos en objetos JavaScript

Igual que ocurre en PHP los métodos son funciones incluidas dentro del objeto. Hay varios formas para crear un método nuevo: incluyendo una función anónima o vinculando una funcion externa en la la función constructora, extendiendo el prototipo, o agregando una función (anónima o externa) a una propiedad de un objeto.

Veamos las diferentes posibilidades:

Incluir una función anónima

Page 555: Manual PHP

Entendemos como función anónima aquella que es definida sin incluir ningún nombre de la función. Tendría por tanto una estructura del tipo:

function () { ... instrucciones }

Este tipo de funciones pueden incluidas como métodos de un objeto JavaScript de una de las formas siguientes:

� – Incluyéndolas en un función constructora invocable directamente para crear un objeto.

� function constructora(){ ... instrucciones en la función constructora this.mi_metodo = function(){ ... instrucciones de la función anónima } ... instrucciones en la función constructora }

� El metodo se invoca utilizando la sintaxis:

� objeto= new constructora(); objeto.mi_metodo();

� Antes de invocar el método debe haber sido creado un objeto y el método se invoca (y esto es muy importante) agregando paréntesis () después del nombre del método. En este caso el resultado de la ejecución del método sería el resultado de la ejecución de la función anónima.

� Si se invocara el método sin agregar los paréntesis () finales lo que se obtendría sería un copia de la función anónima pero no el resultado de su ejecución.

� – Incluyéndolas en la función constructora incluida como contenido de una variable.

� variable =function constructora(){ ... instrucciones en la función constructora this.mi_metodo = function(){ ... instrucciones de la función anónima } ... instrucciones en la función constructora }

� El metodo se invoca utilizando la sintaxis:

� objeto= new variable(); objeto.mi_metodo();

� Este caso es una leve variante del anterior. La única diferencia es que ahora la función constructora está asignada a una variable. Por tanto, para crear un objeto se invoca tal variable en vez de invocar a la función como ocurría en el caso anterior. El resto de los comentarios del caso anterior, incluidos los relativos a los paréntesis () siguen siendo válidos en este supuesto.

� – Agregándolas a un objeto JavaScript ya creado.

� objeto =new Object(); objeto.mi_metodo = function(){ ... instrucciones de la función anónima }

� El metodo se invoca utilizando la sintaxis:

� objeto.mi_metodo();

� Este supuesto nos permite visualizar la importancia de los mencionados paréntesis. Cuando lo escribimos sin paréntesis le estamos asignando (o leyendo) la funcion anónima. Sin embargo, cuando le agregamos los paréntesis le estamos solicitando que nos devuelva el resultado de la ejecución de la función

� – En este ejemplo puedes ver las distintas opciones de definir métodos utilizando funciones anónimas.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>)Objetos JavaScript</title>

Page 556: Manual PHP

<script type="text/javascript"> /* comprobamos en un script independiente si JSON es soportado nativamente y en caso contrario carga el fichero correspondiente */ if (typeof(JSON)== "undefined"){ alert('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); document.write('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); } /* continuamos escribiendo el código JavaScript pero incluyéndolo dentro de una nuevas etiquetas */ </script> <script type="text/javascript"> function crea_objeto1(){ this.nombre="soy el objeto 1"; /* defino un método por medio de una función anónima (observa que entre function y () no lleva ningún nombre de ahí lo de anónima incluida dentro de la propia funcion constructora */ this.metodo1=function(){ return (5*3); } /* escribirlo así es pura redundancia ya que la propiedad1 y el método1 son la misma cosa */ this.propiedad1=this.metodo1; /* esto ya es diferente. La propiedad2 tomará el valor resultante de la ejecución del método, es decir 15*/ this.propiedad2=this.metodo1(); } /* creamos un objeto utilizando la funcion constructora crea_objeto1 */ objeto1=new crea_objeto1(); document.write("<h3>Utilizando una función constructora</h3>"); document.write("<br><br>Probando la función anónima<br><br>"); document.write("Soy el resultado de this.metodo1 leido desde la propiedad1 = "); document.write(objeto1.propiedad1+"<br>"); document.write("Soy el resultado de objeto1.metodo1 = "); document.write(objeto1.metodo1+"<br>"); document.write("Soy el resultado de this.metodo1() leido desde la propiedad2 = "); document.write(objeto1.propiedad2+"<br>"); document.write("Soy el resultado de objeto1.metodo1() = "+objeto1.metodo1()+"<br><br>"); document.write("Soy el resultado de objeto1.metodo1() = "); document.write(objeto1.metodo1()+"<br>"); /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante"); document.write(" (sólo transforma propiedades)<br><br>"); document.write(JSON.stringify(objeto1)+"<br>"); /* ahora la funcion constructora será una función anónima asignada a la variable que hemos llamado trabajadora */ var trabajadora=function (){ this.nombre="Yo soy el objeto creado por la trabajadora"; /* defino un método por medio de una función anónima (observa que entre function y () no lleva ningún nombre de ahí lo de anónima incluida dentro de la propia funcion constructora */ this.metodo1=function(){ return (25-3); } this.propiedad1=this.metodo1; this.propiedad2=this.metodo1();

Page 557: Manual PHP

} /*creamos el nuevo objeto utilizando la variable trabajadora */ objeto2=new trabajadora(); document.write("<h3>Utilizando una variable que contiene la función constructora</h3>"); document.write("<br><br>Probando la función anónima<br><br>"); document.write("Soy el resultado de this.metodo1 leido desde la propiedad1 = "); document.write(objeto2.propiedad1+"<br>"); document.write("Soy el resultado de objeto2.metodo1 = "); document.write(objeto2.metodo1+"<br>"); document.write("Soy el resultado de this.metodo1() leido desde la propiedad2 = "); document.write(objeto2.propiedad2+"<br>"); document.write("Soy el resultado de objeto2.metodo1() = "); document.write(objeto2.metodo1()+"<br>"); /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante"); document.write(" (sólo transforma propiedades)<br><br>"); document.write(JSON.stringify(objeto2)+"<br>"); document.write("<h3>Utilizando new Object()</h3>"); objeto3=new Object(); /* asignamos un método al objeto recién creado */ objeto3.metodo1=function(){ return "Soy le resultado del metodo1 (anonimo) en el objeto3"; } /* visualizamos los elementos del nuevo objeto. */ document.write("Soy el resultado de objeto3.metodo1 = "); document.write(objeto3.metodo1+"<br>"); document.write("Soy el resultado de objeto3.metodo1() = "); document.write(objeto3.metodo1()+"<br>"); /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante<br>"); document.write("Como sólo transforma propiedades no tiene contenidos<br><br>"); document.write(JSON.stringify(objeto3)+"<br>"); </script> </head> <body> </body> </html>

Ver ejemplo

Incluir una función externa

Esta opción requiere tener definido el método en un función externa (totalmente ajena a los objetos JavaScript) tal como esta:

Page 558: Manual PHP

function externa () { ... instrucciones }

También puede se utilizada en los tres supuestos planteados para las funciones anónimas.

� – Incluyéndolas en un función constructora invocable directamente para crear un objeto.

� function constructora(){ ... instrucciones en la función constructora this.mi_metodo = externa ... instrucciones en la función constructora }

� El metodo se invoca utilizando la sintaxis:

� objeto= new constructora(); objeto.mi_metodo();

� Cuando desde this.mi_metodo se invoca la función constructora no se ponen paréntesis dado que en este caso no pretendemos que se ejecute la función sino que se agregue su contenido al método que estamos creando.

� Una vez creado el método se invoca exactamente igual que hacíamos cuando se trataba de funciones anónimas

� – Incluyéndola en la función constructora incluida como contenido de una variable.

� variable =function constructora(){ ... instrucciones en la función constructora this.mi_metodo = externa ... instrucciones en la función constructora }

� El metodo se invoca utilizando la sintaxis:

� objeto= new variable(); objeto.mi_metodo();

� Son válidos los mismos comentarios realizados en el supuesto anterior.

� – Agregándolas a un objeto JavaScript ya creado.

� objeto =new Object(); objeto.mi_metodo = constructora

� El metodo se invoca utilizando la sintaxis:

� objeto.mi_metodo();

� También aquí tienen plena validez los comentarios relativos a la no inclusión de paréntesis detrás del nombre de la función externa cuando se trata de crear un nuevo método

� – En este ejemplo puedes ver las distintas opciones de definir métodos utilizando una función externa.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>)Objetos JavaScript</title> <script type="text/javascript"> /* comprobamos en un script independiente si JSON es soportado nativamente y en caso contrario carga el fichero correspondiente */ if (typeof(JSON)== "undefined"){ alert('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); document.write('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); } /* continuamos escribiendo el código JavaScript pero incluyéndolo dentro de una nuevas etiquetas */ </script> <script type="text/javascript"> /* esta sería la función externa */ function externa(){

Page 559: Manual PHP

return "Solo el resultado de la funcion externa"; } /* funcion constructora */ function crea_objeto1(){ this.nombre="soy el objeto 1"; /* defino un método por medio de la función externa a la que llamamos sin agregar los paréntesis finales*/ this.metodo1=externa; /* escribirlo así es pura redundancia ya que la propiedad1 y el método1 son la misma cosa */ this.propiedad1=this.metodo1; /* esto ya es diferente. La propiedad2 tomará el valor resultante de la ejecución del método */ this.propiedad2=this.metodo1(); } /* creamos un objeto utilizando la funcion constructora crea_objeto1 */ objeto1=new crea_objeto1(); document.write("<h3>Utilizando una función constructora</h3>"); document.write("<br><br>Probando la función externa<br><br>"); document.write("Soy el resultado de this.metodo1 leido desde la propiedad1 = "); document.write(objeto1.propiedad1+"<br>"); document.write("Soy el resultado de objeto1.metodo1 = "); document.write(objeto1.metodo1+"<br>"); document.write("Soy el resultado de this.metodo1() leido desde la propiedad2 = "); document.write(objeto1.propiedad2+"<br>"); document.write("Soy el resultado de objeto1.metodo1() = "+objeto1.metodo1()+"<br><br>"); document.write("Soy el resultado de objeto1.metodo1() = "); document.write(objeto1.metodo1()+"<br>"); /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante"); document.write(" (sólo transforma propiedades)<br><br>"); document.write(JSON.stringify(objeto1)+"<br>"); /* ahora la funcion constructora será una función anónima asignada a la variable que hemos llamado trabajadora */ var trabajadora=function (){ this.nombre="Yo soy el objeto creado por la trabajadora"; /* defino un método por medio de la funcion externa */ this.metodo1=externa this.propiedad1=this.metodo1; this.propiedad2=this.metodo1(); } /*creamos el nuevo objeto utilizando la variable trabajadora */ objeto2=new trabajadora(); document.write("<h3>Utilizando una variable que contiene la función constructora</h3>"); document.write("<br><br>Probando la función externa<br><br>"); document.write("Soy el resultado de this.metodo1 leido desde la propiedad1 = "); document.write(objeto2.propiedad1+"<br>"); document.write("Soy el resultado de objeto2.metodo1 = "); document.write(objeto2.metodo1+"<br>"); document.write("Soy el resultado de this.metodo1() leido desde la propiedad2 = "); document.write(objeto2.propiedad2+"<br>");

Page 560: Manual PHP

document.write("Soy el resultado de objeto2.metodo1() = "); document.write(objeto2.metodo1()+"<br>"); /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante"); document.write(" (sólo transforma propiedades)<br><br>"); document.write(JSON.stringify(objeto2)+"<br>"); document.write("<h3>Utilizando new Object()</h3>"); objeto3=new Object(); /* asignamos un método al objeto recién creado */ objeto3.metodo1=externa /* visualizamos los elementos del nuevo objeto. */ document.write("Soy el resultado de objeto3.metodo1 = "); document.write(objeto3.metodo1+"<br>"); document.write("Soy el resultado de objeto3.metodo1() = "); document.write(objeto3.metodo1()+"<br>"); /* ahora creamos una cadena JSON a partir del contenido del objeto */ document.write("<br><br>Cadena JSON del objeto resultante<br>"); document.write("Como sólo transforma propiedades no tiene contenidos<br><br>"); document.write(JSON.stringify(objeto3)+"<br>"); </script> </head> <body> </body> </html>

Ver ejemplo

Agregar métodos extendiendo el prototipo

En aquellos casos en los que se utiliza una función constructora (bien de manera directa o asignándola a una variable) cada vez que es invocada esta se creará un nuevo objeto conteniendo los métodos y/o propiedades definidos en aquella. Si pretendiéramos agregar un nuevo método podríamos editar la función constructora e incluirlo en ella pero también sería posible hacerlo extendiendo el prototipo. Este proceso se realiza de la forma siguiente:

constructora.prototype.nuevo_metodo = function() { ... instrucciones de la funcion anónima del nuevo método } o constructora.prototype.nuevo_metodo = nombre_de_la_funcion_externa

en el primer caso se trataría de una función anónima y en el segundo en el caso de una función externa. Este procedimiento es válido tanto para el caso de que constructora sea el nombre de una función como para el supuesto que sea el nombre de una variable que contenta una función de este tipo.

Este procedimiento no es aplicable en los casos de objetos creados por medio de new Object().

Este es un ejemplo de aplicación de este procedimiento.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head>

Page 561: Manual PHP

<title>)Objetos JavaScript</title> <script type="text/javascript"> /* comprobamos en un script independiente si JSON es soportado nativamente y en caso contrario carga el fichero correspondiente */ if (typeof(JSON)== "undefined"){ alert('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); document.write('<sc'+'ript type="text/javascript" src="json2.js">'+'</sc'+'ript>'); } /* continuamos escribiendo el código JavaScript pero incluyéndolo dentro de una nuevas etiquetas */ </script> <script type="text/javascript"> /* esta sería la función externa */ function externa(){ return "Solo el resultado de la funcion externa"; } /* funcion constructora la defino vacía*/ function crea_objeto1(){ } /* agregamos un nuevo método -en este caso una funcion anónima- a la función constructora anterior */ crea_objeto1.prototype.metodo1 = function() { return ("Esta es la funcion anonima incluida como metodo 1"); } /* agregamos un segundo método -en este caso una funcion externa- a la función constructora anterior */ crea_objeto1.prototype.metodo2 = externa; /* creamos un objeto utilizando la funcion constructora crea_objeto1 */ objeto1=new crea_objeto1(); /* ejecutamos ambos métodos con y sin paréntesis */ document.write("Soy el resultado de objeto1.metodo1 = "); document.write(objeto1.metodo1+"<br>"); document.write("Soy el resultado de objeto1.metodo1() = "); document.write(objeto1.metodo1()+"<br>"); document.write("Soy el resultado de objeto1.metodo2 = "); document.write(objeto1.metodo2+"<br>"); document.write("Soy el resultado de objeto1.metodo2() = "); document.write(objeto1.metodo2()+"<br>"); /* ahora la funcion constructora será una función anónima asignada a la variable que hemos llamado trabajadora. Tambien la crearemos vacía*/ var trabajadora=function (){ } /* agregamos un nuevo método -en este caso una funcion anónima- a la variable anterior */ trabajadora.prototype.metodo1 = function() { return ("Esta es otra funcion anonima incluida como metodo 1 en trabajadora"); }

Page 562: Manual PHP

/* agregamos un segundo método -en este caso una funcion externa- a la variable trabajadora */ trabajadora.prototype.metodo2 = externa; /* creamos un objeto utilizando la variable trabajadora */ objeto2=new trabajadora(); /* ejecutamos ambos métodos con y sin paréntesis */ document.write("Soy el resultado de objeto2.metodo1 = "); document.write(objeto2.metodo1+"<br>"); document.write("Soy el resultado de objeto2.metodo1() = "); document.write(objeto2.metodo1()+"<br>"); document.write("Soy el resultado de objeto2.metodo2 = "); document.write(objeto2.metodo2+"<br>"); document.write("Soy el resultado de objeto2.metodo2() = "); document.write(objeto2.metodo2()+"<br>"); </script> </head> <body> </body> </html>

Ver ejemplo

Una vez conocemos los rudimentos de los objetos en JavaScript y dado que también hemos manejado los objetos PHP ya estaremos en condiciones de afrontar la labor de intercambiar objetos entre ambos lenguajes y, por lo tanto, intercambiar objetos entre cliente y servidor y también entre servidor y cliente. Será lo que afrontaremos en la página siguiente.