Nisu - UJI

php.

05 julio 2008

CopyLeft mm AT nisu.org

En esta página ...

  1. El lenguaje PHP.
    1. El lenguaje.
      1. Sintaxis
        1. eval
      2. Tipos de datos.
      3. Estructuras de control.
      4. Arrays.
      5. Funciones.
      6. PCRE.
        1. Expresiones regulares.
        2. Expresiones más avanzadas.
        3. Funciones.
    2. Interacción con el web.
      1. Variables predefinidas.
      2. La instalación.
      3. Formularios.
      4. Subida de archivos.
      5. Campos de entrada.
      6. Sesiones.
        1. Descripción.
        2. Uso avanzado.
    3. Acceso a base de datos.
      1. Fundamentos.
      2. Ejemplos con formularios.
    4. Programación web 1.0.
      1. Programación guiada por eventos.
      2. Un posible método.
      3. Plantillas.
      4. Ejemplos.
    5. Problemas comunes.
      1. Concurrencia.
      2. Portabilidad.
      3. Internacionalización.
        1. Ideas básicas.
        2. Plantillas y mensajes.
        3. Realización.
        4. El problema de la base de datos.
      4. Distribución.
        1. Uso básico.
        2. Un instalador más completo.
        3. Inicialización.
        4. Compatibilidad
        5. Prueba del instalador.
      5. Depuración.
      6. Código cerrado.
        1. eAccelerator.
    6. Ejemplos típicos.
      1. Autenticación.
        1. Con usuario y contraseña.
        2. Vía mail.
        3. Con certificados.
        4. Cruzada.
      2. Almacenamiento de imágenes.
    7. Misc.
      1. Mejores URLs.
  2. Seguridad Web
    1. Limpia la entrada.
      1. Limpia lo que vas a mostrar.
      2. Limpia lo que vas a consultar.
      3. Limpia lo que vas a ejecutar.
      4. Limpia los nombres de fichero.
      5. Normaliza los nombres de fichero.
    2. Cuida la autenticación.
      1. Registra la IP.
      2. Introduce información secreta.
    3. Captcha
  3. Enlaces externos.
Este tutorial no es para imprimir. Cambia frecuentemente, léelo en la pantalla, puedes aumentar el tamaño de letra. No despilfarres papel.

Introducción

En estos apuntes pretendo dar una introducción a la programación web clásica mediante PHP. Por favor enviadme sugerencias, erratas, mejoras, peticiones de ampliaciones, etc. Las explicaciones son breves, pues gran parte de la información está en los ejemplos, que deben ser leídos con atención.
Las zonas con borde izquierdo punteado son la ejecución en tiempo real del código PHP que le precede. Las zonas con un borde izquierdo rojo fino son zonas de lectura para usuarios especialmente interesados. El texto incluye ejercicios, que te recomiendo hacer si usas el texto para un aprendizaje programado.

El lenguaje PHP. ^

PHP es un lenguaje interpretado, fácil de aprender y de emplear, que dispone de una amplia colección de funciones en librerías. Fue concebido para ser incrustado en páginas web, pero puede (o debe) emplearse como un lenguaje de programación con entidad propia, independiente del web. Su integración con el web es enorme, de modo que muchas operaciones costosas con otros lenguajes, en PHP están resueltas de forma implícita. Esto explica su gran popularidad.

El lenguaje. ^

Describo aquí los elementos principales del lenguaje. Para una completa explicación, leer aquí.
Sintaxis ^
Los programas PHP se determinan por la presencia de las marcas <?php y ?>. Lo que está dentro de las marcas se interpreta como código PHP. Lo que está fuera simplemente se muestra por el output: Hola
<?php
  
echo "mundo<br>";
  echo 
strftime("Son las %H horas");
?>
Hola mundo
Son las 22 horas
Con este ejemplo queda claro que PHP fue concebido para integrarse dentro del HTML, pero usar así PHP sólo tiene sentido en pequeños scripts (como la página que estás leyendo, que es un script PHP). En programas de una envergadura mínima no debería de haber nada fuera de las marcas <?php y ?>. En este documento emplearé la forma abreviada de marcas <? y ?>.

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.

Abre este archivo con el editor vi (vim) y observa una aplicación usual de los comentarios. Esta funcionalidad de vim está disponible para cualquier lenguaje que admita comentarios (observa el código fuente de esta misma página).
Edita un archivo que contenga algo de PHP y ábrelo localmente con tu navegador, es decir que la URL que ves en la barra de dirección sea algo como /home/fulano/mi.php (en windows c:\blabla\mi.php). ¿Por qué estás viendo el código PHP?.
Edita un archivo info.php que contenga: <? 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:
  
// 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;
  
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.

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."'";

La variable 'variable' lleva un '6'
Un ejemplo más complejo puede ser:
  
for ($i=1$i <100$i++) {
    
$nom="nom$i";
    $
$nom=$i;
    echo 
"La variable '$nom' lleva un '".$$nom."'\n";
  } 
La variable 'nom1' lleva un '1' La variable 'nom2' lleva un '2' La variable 'nom3' lleva un '3' La variable 'nom4' lleva un '4' La variable 'nom5' lleva un '5' La variable 'nom6' lleva un '6' La variable 'nom7' lleva un '7' La variable 'nom8' lleva un '8' La variable 'nom9' lleva un '9' La variable 'nom10' lleva un '10' La variable 'nom11' lleva un '11' La variable 'nom12' lleva un '12' La variable 'nom13' lleva un '13' La variable 'nom14' lleva un '14' La variable 'nom15' lleva un '15' La variable 'nom16' lleva un '16' La variable 'nom17' lleva un '17' La variable 'nom18' lleva un '18' La variable 'nom19' lleva un '19' La variable 'nom20' lleva un '20' La variable 'nom21' lleva un '21' La variable 'nom22' lleva un '22' La variable 'nom23' lleva un '23' La variable 'nom24' lleva un '24' La variable 'nom25' lleva un '25' La variable 'nom26' lleva un '26' La variable 'nom27' lleva un '27' La variable 'nom28' lleva un '28' La variable 'nom29' lleva un '29' La variable 'nom30' lleva un '30' La variable 'nom31' lleva un '31' La variable 'nom32' lleva un '32' La variable 'nom33' lleva un '33' La variable 'nom34' lleva un '34' La variable 'nom35' lleva un '35' La variable 'nom36' lleva un '36' La variable 'nom37' lleva un '37' La variable 'nom38' lleva un '38' La variable 'nom39' lleva un '39' La variable 'nom40' lleva un '40' La variable 'nom41' lleva un '41' La variable 'nom42' lleva un '42' La variable 'nom43' lleva un '43' La variable 'nom44' lleva un '44' La variable 'nom45' lleva un '45' La variable 'nom46' lleva un '46' La variable 'nom47' lleva un '47' La variable 'nom48' lleva un '48' La variable 'nom49' lleva un '49' La variable 'nom50' lleva un '50' La variable 'nom51' lleva un '51' La variable 'nom52' lleva un '52' La variable 'nom53' lleva un '53' La variable 'nom54' lleva un '54' La variable 'nom55' lleva un '55' La variable 'nom56' lleva un '56' La variable 'nom57' lleva un '57' La variable 'nom58' lleva un '58' La variable 'nom59' lleva un '59' La variable 'nom60' lleva un '60' La variable 'nom61' lleva un '61' La variable 'nom62' lleva un '62' La variable 'nom63' lleva un '63' La variable 'nom64' lleva un '64' La variable 'nom65' lleva un '65' La variable 'nom66' lleva un '66' La variable 'nom67' lleva un '67' La variable 'nom68' lleva un '68' La variable 'nom69' lleva un '69' La variable 'nom70' lleva un '70' La variable 'nom71' lleva un '71' La variable 'nom72' lleva un '72' La variable 'nom73' lleva un '73' La variable 'nom74' lleva un '74' La variable 'nom75' lleva un '75' La variable 'nom76' lleva un '76' La variable 'nom77' lleva un '77' La variable 'nom78' lleva un '78' La variable 'nom79' lleva un '79' La variable 'nom80' lleva un '80' La variable 'nom81' lleva un '81' La variable 'nom82' lleva un '82' La variable 'nom83' lleva un '83' La variable 'nom84' lleva un '84' La variable 'nom85' lleva un '85' La variable 'nom86' lleva un '86' La variable 'nom87' lleva un '87' La variable 'nom88' lleva un '88' La variable 'nom89' lleva un '89' La variable 'nom90' lleva un '90' La variable 'nom91' lleva un '91' La variable 'nom92' lleva un '92' La variable 'nom93' lleva un '93' La variable 'nom94' lleva un '94' La variable 'nom95' lleva un '95' La variable 'nom96' lleva un '96' La variable 'nom97' lleva un '97' La variable 'nom98' lleva un '98' La variable 'nom99' lleva un '99'
Un tema interesante es cómo incluir variables dentro de cadenas.
Saltar

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"

He llegado hasta quí sin errores

En consecuencia, el anterior bucle se escribe mejor como:
  
for ($i=1$i <100$i++)
    ${
"nom".$i}=$i;

eval

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>");

$nom1=$i;
$nom2=$i;
$nom3=$i;
$nom4=$i;
$nom5=$i;
$nom6=$i;
$nom7=$i;
$nom8=$i;
$nom9=$i;
$nom10=$i;
$nom11=$i;
$nom12=$i;
$nom13=$i;
$nom14=$i;
$nom15=$i;
$nom16=$i;
$nom17=$i;
$nom18=$i;
$nom19=$i;
$nom20=$i;
$nom21=$i;
$nom22=$i;
$nom23=$i;
$nom24=$i;
$nom25=$i;
$nom26=$i;
$nom27=$i;
$nom28=$i;
$nom29=$i;
$nom30=$i;
$nom31=$i;
$nom32=$i;
$nom33=$i;
$nom34=$i;
$nom35=$i;
$nom36=$i;
$nom37=$i;
$nom38=$i;
$nom39=$i;
$nom40=$i;
$nom41=$i;
$nom42=$i;
$nom43=$i;
$nom44=$i;
$nom45=$i;
$nom46=$i;
$nom47=$i;
$nom48=$i;
$nom49=$i;
$nom50=$i;
$nom51=$i;
$nom52=$i;
$nom53=$i;
$nom54=$i;
$nom55=$i;
$nom56=$i;
$nom57=$i;
$nom58=$i;
$nom59=$i;
$nom60=$i;
$nom61=$i;
$nom62=$i;
$nom63=$i;
$nom64=$i;
$nom65=$i;
$nom66=$i;
$nom67=$i;
$nom68=$i;
$nom69=$i;
$nom70=$i;
$nom71=$i;
$nom72=$i;
$nom73=$i;
$nom74=$i;
$nom75=$i;
$nom76=$i;
$nom77=$i;
$nom78=$i;
$nom79=$i;
$nom80=$i;
$nom81=$i;
$nom82=$i;
$nom83=$i;
$nom84=$i;
$nom85=$i;
$nom86=$i;
$nom87=$i;
$nom88=$i;
$nom89=$i;
$nom90=$i;
$nom91=$i;
$nom92=$i;
$nom93=$i;
$nom94=$i;
$nom95=$i;
$nom96=$i;
$nom97=$i;
$nom98=$i;
$nom99=$i;
nos dice las instrucciones que se van a ejecutar.

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"

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.

Supongamos que en nuestro programa hay una función definida y que el código evaluado contiene una que se llama igual. ¿Qué sucedería?. ¿Cómo resolverlo?.

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";

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.

Tipos de datos. ^
Como en todos los lenguajes de programación, en PHP encontramos distintos tipos de datos que simplemente pasamos a enumerar: boolean, integer, float, string, array, object, resource. La naturaleza interpretada de PHP permite que no sea un lenguaje fuertemente tipado como C. Así, los cuatro primeros tipos pueden llegar a confundirse fácilmente, pues PHP realiza una conversión automática entre ellos. Así, la cadena "0" es casi lo mismo que el entero 0, pues PHP hace la conversión de tipos cuando lo necesita:
  
// $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á 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.

Saltar
Para reflexionar un poco sobre los enteros, observa este código (máquina de 32 bit):
  $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";
  
2147483647 1 4294967294 4294967295 2147483647 1
La primera es una asignación normal, $x es entero. En la segunda, al producirse desbordamiento, PHP convierte el valor de $x a float y lo asigna a una nueva $x de tipo float. En la tercera la constante es entera pero sin signo y al asignarse a una variable, como los enteros son sin signo, PHP decide que $x sea float. En la cuarta asignación, la constante desborda, es convertida al entero mayor y asignada a una variable entera. Así una forma fácil de calcular el entero máximo en arquitecturas de 32 o 64 bits podría ser:  $x=0x10000000000000000 ; echo $x;
Podemos pensar que podemos usar los float como enteros con más capacidad. Eso no es así. Construye un programa que averigue cual es el primer float tal que $x == $x+1. A mi me sale 2^53. ¿Por qué?

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';

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";

Somos 2300

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";

Somos Somos 2300 Somos 2300
El formato {$variable} es un agrupador, frente al significado evaluador de ${variable}. Leer atentamente este ejemplo:
  $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.

Estructuras de control. ^
Las estructuras de control son básicamente las de cualquier lenguaje de programación:
  
if (condición)
    
acción ;

  if (
condición)
    
acción ;
  else
    
otra_acción ;

  if (
condiciónacción ;
  elseif (
otra_condiciónotra_acción
  
else más_acción;

  switch ((
expresión) {
    case 
valoracciones;
    case 
valoracciones;
    default: 
acciones;
    }

  while (
condiciónacción;
  do 
acción while (condición);

  for (
expresión inicialexpinicial2 ;
       
condición ;
       
expresión de pasoexpresió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";
  }

Hola 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);
  

dom dom Array ( [0] => lun [1] => mar ) Array ( [0] => dom [1] => dom )
En este documento hago uso extensivo de foreach, con lo que podrás ver más ejemplos.

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.

Arrays. ^
El tipo array destaca por su versatilidad si lo comparamos con los clásicos vectores de los lenguajes compilados. Realmente se trata de listas indexadas con elementos heterogéneos. Para crear un array basta con hacer:   $a[3]=8que 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); 
Array ( [3] => 8 [1] => Hola )
Como podemos observar, el índice no es un orden, sino simplemente una marca, de modo que los elementos quedan en el orden en que los insertamos.

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) ; 

Array ( [clave1] => hola [clave2] => adios [0] => 1 [1] => 2 [20] => 3 [21] => 4 )
Como vemos, el uso de [] produce el incremento (y uso) del anterior índice numérico. Como curiosidad:
  $a
[20]= 3;
  foreach (
$a as $elemento => $valor)
    unset(
$a[$elemento]) ;
  
$a[]= 4;
  
print_r($a) ; 
Array ( [21] => 4 )
Pese a que la instrucción foreach ha destruído los elementos del array, no ha destruído al propio array y recuerda el último índice.

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); 

Array ( [coche] => bmw [moto] => honda ) Array ( [coche] => audi [moto] => honda ) Array ( [coche] => audi [moto] => ferrari )

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); 

Array ( [8] => Array ( [3] => Array ( [5] => Array ( [3] => 22 ) ) ) )

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); 

Array ( [?mysql_query] => 3 [?php] => 3 [?switch] => 1 [body] => 2 [br] => 67 [code] => 43 [div] => 124 [form] => 13 [h1] => 1 [h2] => 1 [h3] => 7 [h4] => 16 [h5] => 40 [h6] => 23 [head] => 1 [html] => 2 [iframe] => 12 [img] => 6 [input] => 37 [li] => 165 [meta] => 2 [ol] => 23 [option] => 15 [pre] => 2 [script] => 3 [select] => 5 [span] => 8 [style] => 1 [table] => 2 [td] => 8 [textarea] => 1 [title] => 1 [tr] => 4 [tt] => 493 [ul] => 22 [100] => 5 )
La expresión regular no resuelve nuestro propósito del todo. Piensa que sucederá si el texto tiene comentarios HTML. Piensa que sucederá si contiene algo como <?echo 'Hola'; ?>. Idem si contiene PHP que a su vez contiene cadenas HTML.

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));
    

El lunes 12 de ene estaré cansado El martes 13 de ene estaré aburrido El viernes 16 de ene estaré contento
Dado array('1'=>'Primero','2'=>'Segundo','3'=>'Tercero'), modificarlo alterando sólo el segundo índice, de modo que quede: array('1'=>'Primero','22'=>'Segundo','3'=>'Tercero').
Existen multitud de funciones de manipulación de arrays. Antes de usarlas, lee antentamente su descripción, pues muchas de ellas están concebidas para manejar sólo los valores, alteran los índices (keys) causando resultados indeseados. Por ejemplo, array_multisort o array_merge, si los índices de las matrices son numéricos, los reasignan, y esto, a veces, es indeseado.
Funciones. ^
PHP permite al programador definir funciones, que tienen el aspecto:
  
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"); 

amarillo sobre fondo negro azul sobre fondo naranja
Por otra parte existen la posibilidad de definir una función sin parámetros y luego invocarla con un número variable de parámetros. Como lo considero de escaso interés, para entender cómo, consultar el manual. Obsérvese que un efecto similar puede obtenerse pasando un parámetro de tipo array.

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"

La variable sí 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(); 

Una Dos
Las funciones admiten, por supuesto, recursión:
  
function factorial($x) {
    if (
$x == 0)
      return 
1;
    else
      return 
$x*factorial($x-1);
  }
  echo 
"Factorial de 3 = ".factorial(3); 
Factorial de 3 = 6
Observa que no se puede llamar a una función desde dentro de una cadena.
PCRE. ^
Aunque no es un tema propio de PHP, su utilidad es tan grande que voy a dedicarle un apartado. PCRE son las expresiones regulares de Perl. La implementación de la librera PCRE (PHP debe estar compilado con ella) es más eficiente que la propia librería de expresiones regulares de PHP, y más potente.
Expresiones regulares. ^
En este apartado no voy a hacer una descripción completa de las expresiones regulares, para eso ya hay mucho manuales, pero ser requiere paciencia para leerlos. Voya tratar de explicar lo mínimo y poner ejemplos útiles.

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:

  • de agrupamiento: ( y ),
  • de alternación: | y
  • de cuantificación: ? (significa que el anterior elemento puede estar o no presente) y * (el anterior elemento puede estar cero o más veces); también suele disponerse (en PCRE por ejemplo) de + (el anterior elemento puede estar una o más veces).

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:

  • ^pan$ o ^agua$ como queremos.
  • ^pan o agua$, es lo que va a representar habitualmente porque | tiene menor pioridad que ^ y $.
  • o para Microsoft (ATL): ^pa(n|a)gua$
Para eliminar la duda en los dos primeros casos, deberíamos escribir ^(pan|agua)$, que funcionará en casi todos las implementaciones de expresiones regulares, excepto para Microsoft, donde deberemos emplear: ^((pan)|(agua))$. En cada lenguaje de programación será necesario conocer exactamente la implementación de las expresiones regulares. En este texto, a partir de aquí nos ceñiremos a PCRE, en la implementación actual de PHP 5. Volviendo al ejemplo, obsérvese la necesidad de ^ y $, porque simplemente escribir pan|agua representaría a infinitas cadenas que contuvieran pan o agua.

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:

  • /(.*?)^\r?\n(.*)/s aplicada sobre un correo coincide los () con la la cabecera y el cuerpo.
  • /(^|.*/)..-[^/]*$/ aplicada sobre un path completo de archivo representa a un archivo que en la parte del nombre tiene dos caracteres cualesquiera seguidos de un -. Es decir algo como /var/xy-hola o ../zz-tu.php. Obsérvese la necesidad de -[^/]*$ para especificar claramente que debe suceder en el nombre del archivo y no en el directorio, y (^|.*/) asegura que los dos caracteres están al principio del nombre.
  • +^\s*//!\s*\n\s*\$(.*?)(\s*)=(\s*)(.*?)\s*;\s*$+ está delimitada por el signo + por comodidad. Esta expresión coincide con código PHP: la cadena //! sola en una línea, seguida de una asignación PHP, que no debe compartir línea con otras instrucciones, por ejempo:  //!
    $variable="una cadena".
              
    "otra cadena"
                 
  • |<h[0-9][^>]*>.*?</h[0-9]>|s coincide con cabeceras HTML. Obsérvese que fallaría en estos casos: <h3 id=">">Hola</h3> y <h3>Hola</h4>.
    Buscar solución.
  • /__\((('.*?[^\]')|(".*?[^\]"))(,[^)]*)?\)/s. Intenta buscar cadenas de código PHP de la forma: __('Hola') y de la forma __("Hola"), con la posibilidad de llevar un segundo argumento, algo como __('Hola','es').
    Obsérvese que esta expresión se escribiría en PHP como: '/__\(((\'.*?[^\\\]\')|(".*?[^\\\]"))(,[^)]*)?\)/s'.
    Buscar un caso en el que falle.
Expresiones más avanzadas. ^
En una expresión, podemos usar referencias hacia atrás del tipo \1, que significa que debe coincidir con lo el primer grupo marcado con (). Por curiosidad, observa que esto va más allá de las expresiones regulares, pues introduce memoria.
Así, para delimitar las cabeceras HTML, podemos mejor usar: |<h([0-9])\s?.*>[^<]*?</h\\1>| que es la solución al ejercicio anteriormente propuesto, aceptando una cabecera de la forma <h1 id=">">Holita</h1>.
Saltar

Mucho más avanzado es el uso de subpatrones de la forma (?loquesea). Su utilidad es muy diversa:

  • Cambiar las opciones: (a(?i)b|c) significa qua apartir de la letra a, la expresión se aplica tanto a mayúsculas como a minúsculas.
  • Eliminar un patron de la cuenta: ((Hola) (mundo)) produce \1 como Hola mundo, \2 como Hola y \3 como mundo. En cambio ((?:Hola) (mundo)) sólo produce \1 como Hola mundo y \2 como mundo.
  • Aserciones. Una aserción no produce coincidencia. Por ejemplo \b es una aserción ya explicada. Pueden construirse aserciones hacia adelante positivas con (?=), negativas con (?!), y si son hacia atrás, con (?<=) y (?<!). Si, por ejemplo, quiero buscar patrones de la forma %xy:% pero que xy no puedan ser las letras ab, uso: %..(?<!ab):%, que significa: un %, seguido de dos caracteres, seguido de : pero que no esté precidido por la pareja ab, seguido de %.
  • Subpatrones condicionales de la forma (?(condición)patrón-si) o (?(condición)patrón-si|patrón-no). Si la condifción se cumple, se usa el patrón-si y si no coincide se usa el patrón-no.
    Piensa una aplicación de los condicionales.
Funciones. ^
La función preg_match analiza una cadena contra una expresión regular y devuelve el número de veces que la encuentra, y opcionalmente un array con las coincidencias:
  preg_match
('%(<(\w+).*?>)([^<]*)(</\2.*?>)%',
    
file_get_contents($_SERVER['SCRIPT_FILENAME']),$mat);
  
print_r($mat);
      
Array ( [0] => <title>php</title> [1] => <title> [2] => title [3] => php [4] => </title> )

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);
      

Array ( [0] => Array ( [0] => <title>php</title> [1] => <script type="text/javascript" src="common/style.js"></script> [2] => <script type="text/javascript">selStyl('');</script> ) [1] => Array ( [0] => <title> [1] => <script type="text/javascript" src="common/style.js"> [2] => <script type="text/javascript"> ) [2] => Array ( [0] => title [1] => script [2] => script ) [3] => Array ( [0] => php [1] => [2] => selStyl(''); ) [4] => Array ( [0] => </title> [1] => </script> [2] => </script> ) )

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);
      

Array ( [0] => Array ( [0] => <title>php</title> [1] => <title> [2] => title [3] => php [4] => </title> ) [1] => Array ( [0] => <script type="text/javascript" src="common/style.js"></script> [1] => <script type="text/javascript" src="common/style.js"> [2] => script [3] => [4] => </script> ) [2] => Array ( [0] => <script type="text/javascript">selStyl('');</script> [1] => <script type="text/javascript"> [2] => script [3] => selStyl(''); [4] => </script> ) )

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:
 
// %es%: Esto es un comentario
 // %en%: This is a comment
 // %fr%: Ici un commentaire
 
Podemos eliminar todos los comentarios excepto uno con la sentencia:
  $lg
="es";
  echo 
preg_replace(array("#^[ \t]*//[ \t]+%..(?<!$lg)%:[ \t].*\n#m",
                          
"#^([ \t]*//[ \t]+)%$lg%:[ \t]+#m"),
                    array(
"","\\1"),$y);


 
// Esto es un comentario
 
Simplificar la sentencia cambiando el orden.
Saltar

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?");

Hola campe=f3n,=0a=bfqu=e9 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); 

Array ( [0] => a12a33a.b.c [1] => a [2] => 12a33a. [3] => b [4] => . [5] => c )
Lo que pretendía era sacar las expresiones 2 y 4 lo más cortas posibles y no lo son. En cambio:  preg_match('+.*(a)(.*?)(b)(.*?)(c)+s','hola12a33a.b.c',$m); print_r($m); 
Array ( [0] => hola12a33a.b.c [1] => a [2] => . [3] => b [4] => . [5] => c )
Sí que produce el resultado esperado.

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);

Interacción con el web. ^

Un programa PHP puede ser cargado desde el servidor apache vía el mecanismo CGI o de forma nativa, cuando PHP está integrado en apache. El comportamiento de PHP para el programador es el mismo, por lo que la decisión de cómo invocar PHP queda en manos del administrador del Web en función de sus necesidades. El programador simplemente tiene que saber cómo invocar el intérprete PHP y esto se consigue simplemente vía nomenclatura: basta con dotar de la extensión .php al nombre del archivo para que el servidor web sepa que tiene que pasarlo por el intérprete PHP.
Variables predefinidas. ^
PHP dispone de una serie de variables predefinidas que permiten interaccionar cómodamente con el entorno web. Estas variables se denominan superglobales, en tanto que son globales, pero no requieren definirse como tales dentro de las funciones. ¡Ojo! no pueden ser usadas con $$. Veamos las más importantes:
  • $_SERVER contiene las variables que el servidor define, algunas muy interesnates como $_SERVER['SCRIPT_NAME'], que es la URL parcial de nuestro script o $_SERVER['HTTP_HOST'] que es el nombre de nuestro servidor:
      
    echo "Mi URL: http://{$_SERVER['HTTP_HOST']}{$_SERVER['SCRIPT_NAME']}<br>";
      echo 
    "Tu IP: {$_SERVER['REMOTE_ADDR']} o {$_SERVER['HTTP_X_FORWARDED_FOR']}"
    Mi URL: http://doc.nisu.org/web.php
    Tu IP: 38.103.63.57 o
  • $_GET y $_POST contienen lo que el script recibe por GET o por POST respectivamente. Cuando hablemos de los formularios veremos numerosos ejemplos.
  • $_REQUEST es la combinación de $_GET y $_POST.
  • $_ENV es el entorno del programa.
  • $_SESSION: variables de sesión, la trataremos como un caso especial.
La instalación. ^
PHP dispone de una extensa colección de parámetros de instalación. Estos parámetros se determinan en el archivo php.ini (y según instalaciones, en los archivos del directorio php.d). En tiempo de ejecución, pueden determinarse mediante la función ini_get y algunas, en función de privilegios o tipo de variable, modificarse con ini_set. En general podemos confiar en que la instalación en que ejecutamos nuestro script es la estándar, pero si esperamos que nuestro script funcione en múltiples plataformas, debemos ser extremadamente cuidadosos.

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.

Formularios. ^
Los formularios son el elemento básico de interacción con el usuario en la programación web clásica. Un formulario tiene el aspecto:
  <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:
Escribe:
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:

  • El formulario puede enviarse pulsando intro. En ese caso, el navegador enviará los mismos campos. Pero, si hay más de un botón submit (puede ser interesante para realizar operaciones distintas) se enviará el primero de ellos. Si hay varios botones submit, y se pulsa uno de ellos, el navegador sólo envía el pulsado.
  • El array $_POST contiene los campos del formulario que PHP recibe del navegador. Si se enviaran mediante el método GET, estarían en el array $_GET. Existe un array $_REQUEST que equivale a la unión de ambos y es muy correcto emplearlo.
  • El atributo value de un campo contiene el valor que se envía. Si en el HTML ya hay un valor pre-cargado, el navegador nos lo muestra. Hay que ser cuidadoso en que no contenga el carácter " para no generar un HTML incorrecto.
  • Si varios campos tiene el mismo nombre, o hay un campo de tipo select multiple, el navegador enviará el mismo nombre de campo varias veces, con sus respectivos valores. PHP sólo cogerá el último, a no ser que tomemos preacauciones, como explicaré a continuación.
El siguiente ejemplo resume lo explicado:
  <form method="post" action="procform.php">
    Escribe: <input name="prueba" value="pre&quot;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>
Escribe:

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&quot;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>

Escribe:

Obsérvese lo antiestético del formulario.

El aprendizaje del procesado de formularios se completa necesariamente leyendo aquí .

Subida de archivos. ^
Un formulario puede incluir campos de tipo file, mediante los cuales el usuario podrá enviar ("subir") archivos locales al servidor:
  <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"); 

El tamaño máximo de archivo en este servidor es 2000M El POST más grande posible es 2000M

Probemos el ejemplo:

Elige el archivo que quieres enviar: y

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";
  }

Este ejemplo tiene al menos 3 errores, encuéntralos. Uno es una confusión. Otro se produce al subir más de un archivo. ¿Sería conveniente usar $cmp para resolverlo? El tercero sucede si varios usuarios suben a la vez.
Saltar
Las barras de progreso al enviar archivos pueden construirse usando Macromedia flash y empleando su propio POST, no el del navegador. Con el propio navegador, podríamos hacerlo con ajax, o incluso más sencillo, pero realmente no es posible por culpa de PHP: la ejecución del script PHP no comienza hasta que el archivo ha llegado. Para superar este problema, hay quien introduce un script en Perl intermedio que es quien recibe el archivo (es el action del formulario), construye la barra de progreso, la envía al cliente y al finalizar la recepción del archivo lo envía al script PHP. Otra forma de hacerlo es mediante una extensión de PHP disponibe para PHP 5.
Crea un formulario para enviar varios ficheros, pero usa [] en el nombre de los campos y observa como repercute en el array $_FILES.
Campos de entrada. ^
En los ejemplos anteriores hemos visto cómo usar campos input en los formularios. No obstante, veamos este sencillísimo ejemplo: <?
  $v
=$_GET['test'] or $v="'";
  echo 
'<form>'.
       
"<input name=test value=\"$v\">".
       
'<input type=submit value=Continuar>'.
       
'</form>';
?>

Pulsa Continuar repetidas veces:

PHP escapa la comilla ', de modo que a $v se le asigna \'. Este es el comportamiento por defecto de PHP para evitar problemas de seguridad. Un problema similar, pero peor aún, aparece si tecleamos una comilla doble " y pulsamos Continuar: La comilla doble " es escapada, pero además como es el delimitador del atributo value, desaparece del campo input (y aún gracias que el navegador no se arma un lío al ver 3 comillas dobles).

Para resolver esto, veamos este ejemplo: <?
  $v
=$_GET['test'] or $v="'\"";
  if (
get_magic_quotes_gpc())
    
$v=stripslashes($v);
  
// aquí podríamos hacer algo con el valor correcto de $v
  // en algunos casos deberíamos usar addslashes
  
echo '<form>'.
       
'<input name=test value="'.htmlspecialchars($v,ENT_COMPAT).'">'.
       
'<input type=submit value=Continuar>'.
       
'</form>';
?>

Si PHP tiene activado el escapado automático (nos informa get_magic_quotes_gpc()), eliminamos las antibarras com stripslashes. Ahora tenemos $v con el valor que deseamos que tenga. Si ahora, por ejemplo tuvieramos que insertar el valor de $v en una base de datos, deberíamos de escaparlo de nuevo, con la función addslashes o mysql_escape_string.

Después cuando queremos mostrar de nuevo el valor de $v en el campo input, debemos prepararla para compatibilizarla con HTML, para lo cual empleamos htmlspecialchars o htmlentities.

Sesiones. ^
Descripción.
La sesión php se almacena normalmente en un archivo temporal en el que se almacena serializada la información que se mantiene en la sesión. Las sesiones funcionan preferentemente con cookies: la cookie indica el nombre de la sesión que coincide con el del archivo donde se guardan los datos. Este nombre es suficientemente largo y alearotio. La sesión se inicia con:
  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.
Para almacenar datos, debe usarse exclusivamente el array $_SESSION, no debe usarse session_register que sólo existe por compatibilidad. Por ejemplo:
  $_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>';
?>
Saltar
Uso avanzado.
Voy a enfocar este apartado en forma de FAQ. Para más información, leer el manual.
  • ¿Cuánto duran las sesiones?.
    Los archivos de sesion se borran periódicamente si están en la ubicación estándar. Normalmente un cron borra las que llevan cierto tiempo sin usarse (gc_maxlifetime), pero además está el límite cache_expire y la pareja gc_divisor+gc_probability. Si estos valores se quieren cambiar para un script en concreto es necesario cambiar el path para no interferir con otros scripts, usando session_save_path.
  • ¿Por qué cuando uso sesiones no puedo abrir simultáneamente varias instancias de una página?. ^
    El uso de sesiones establece una región crítica: dos programas no pueden estar modificando la sesión al mismo tiempo, se crearían incoherencias. La llamada a session_start abre el archivo de sessión, lo lee y los bloquea hasta que se acaba el programa. Si queremos desbloquearlo, podemos llamar a session_write_close que escribe, cierra y desbloquea la sesión. Por ejemplo en una página de descargas lentas que requiera autenticación, si se usan sesiones, puede cerrarse la sesión justo antes de la descarga:
      
    if (!$_SESSION['autenticado']) {
        ... 
    autenticar ...
      }
      else {
        
    // autenticado
        
    session_write_close();
        
    readfile("Fichero descarga");
      }
        
  • ¿Cómo puedo usar una sessión existente?. ^
    Puede usarse el parámetro PHPSESSID= en la URL. Esto puede crear problemas, por ejemplo porque sólo cambia la sesión en esa invocación. Podemos controlarlo nosotros, según la situación. Por ejemplo tengo las páginas http://una.com y http://otra.com en un mismo servidor y quiero compartir la sesión: puedo poner un enlace en la primera que apunte a la otra según:
      
    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();
      }
        
Un ejemplo más de uso avanzado de sesiones puede verse aquí.

Acceso a base de datos. ^

El uso de PHP con base de datos es típico, describiré el uso básico de MySQL; otros servidores son muy similares.
Fundamentos.
Lo primero es conectar con el servidor MySQL, para lo que se requiere un usuario y una contraseña. Después se debe seleccionar la base de datos:
  $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.
Una consulta típica puede ser:
  $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