Hemen zaude: Hasiera Comunidad Documentación Guías Manual PHP

Manual PHP

Este manual da un repaso al lenguaje PHP. Desde una introducción al lenguaje en sí hasta un primer vistazo a AJAX, pasando por acceso a bases de datos MySQL, manipulación de imágenes, generación de gráficos, PEAR y PECL, etc.

PHP

Sobre este documento

Copyright 2007 Ales Zabala Alava <shagi@gisa-elkartea.org>

Es posible copiar, distribuir y/o modificar éste documento bajo los términos de la FDL (Free Documentation License), versión 1.2 o posterior.

Este documento está basado en diferentes manuales, aunque principalmente se ha utilizado de base el manual de PHP. La documentación de cada módulo y extensión diferentes se han cogido de las webs de cada proyecto. Para consultar la documentación original y ver sus respectivas licencias consultar sección Bibliografia y Licencias.

Introducción

PHP (acrónimo de "PHP: Hypertext Preprocessor") es un lenguaje de "código abierto" interpretado, de alto nivel, embebido en páginas HTML y ejecutado en el servidor. Es muy fácil de aprender, pero a su vez ofrece muchas características avanzadas.

Ejemplo:

<html>
 <head>
  <title>Ejemplo de PHP</title>
 </head>
 <body>
 <?php echo "<p>Hola Mundo</p>"; ?>
</body>
</html>

Proceso de publicación web, caches incluidas

  1. El usuario introduce la dirección web en su navegador: http://dominio.com/index.php
  2. El navegador comprueba si tiene la página en caché. Si la tiene la muestra directamente en vez de pedirla de nuevo al servidor.
  3. Si no está en esa cache, la petición sale del ordenador, y pasa por los proxy-cache que encuentre por el camino (el de la red local, el del ISP, el del servidor donde está la web, etc.).
  4. Si ningún proxy-cache tiene la página, la petición llega al servidor web dominio.com.
  5. El servidor web localiza la página correspondiente a /index.php. Debido a su configuración, detecta que tiene código php.
  6. El fichero index.php pasa por el procesador de php, que genera un fichero en (x)html.
  7. El servidor publica el fichero (x)html generado por php

Hay que destacar que el contenido del fichero index.php hay que entenderlo como un fichero de código fuente, que ha de ser compilado. El hecho de que contenga código (x)html es una peculiaridad de php (y de otros lenguajes de programación parecidos).

Cuando pedimos que una web se recargue, se envía una cabecera http especial que avisa a las diferentes caches que invaliden su página cachea y pidan la web de nuevo. Que los proxy-cache hagan caso de esta cabecera http no es definitivo, pero es habitual.

Referencia del lenguaje

Sintaxis básica

A la hora de procesar un fichero php deja el contenido tal cual, sólo procesa el contenido que esta dentro de algún delimitador de escape:

  • <?php y ?> (recomendado)
  • <script language="php"> y </script>
  • <? y ?>
  • <% y %>

Los dos últimos puede que no funcionen, dependiendo de la configuración del servidor. Si pretendemos publicar nuestro código no deberíamos utilizar los delimitadores cortos, ya que no tenemos garantía de que vayan a funcionar.

Las instrucciones se separan con ;. Todas las instrucciones deben terminar con este carácter con la etiqueta de fin de bloque (?>).

Los comentarios son los mismos que en C y en los intérpretes de comandos de UNIX:

  • # comentan hasta fin de línea
  • // comentan hasta fin de línea
  • /*...*/ comenta el bloque interior

Tipos de variables

Los tipos de las variables no se declaran por el programador, se deciden en tiempo de compilación dependiendo del contexto.

Para ver el contenido de una variable: var_dump($variable)

Para comprobar el tipo actual de una variable se pueden utilizar las funciones is_tipo($variable). Por ejemplo:

// Si este valor es un entere, incrementarlo en cuatro
if (is_int($int)) {
    $int += 4;
}

// Si $bool es una cadena, imprimirla
if (is_string($bool)) {
    echo "Cadena: $bool";
}

Para forzar un determinado tipo de variable se puede hacer casting o utilizar la función settype():

$foo = 10;   // $foo es un entero
$bar = (boolean) $foo;   // $bar es un booleano

$foo = "5bar"; // string
$bar = True;   // boolean
settype($foo, "integer"); // $foo es ahora 5   (integer)
settype($bar, "string");  // $bar es ahora "1" (string)

Los diferentes tipos de variables son:

  • Escalares:
    Booleanos
    True y False

    A la hora de convertir una variable a boolean (para cuando está dentro de una comprobación de if , por ejemplo) se evalúan a False

    :
    • El integer 0 (cero)
    • El float 0.0 (cero)
    • Los strings "" y "0"
    • Los arrays de cero elementos
    • El tipo especial NULL
    Enteros
    (...,-2,-1,0,1,2,...)
          $a = 1234; // numero decimal
          $a = -123; // un numero negativo
          $a = 0123; // numero octal (equivalente al 83 decimal)
          $a = 0x1A; // numero hexadecimal (equivalente al 26 decimal)
    

    El tamaño de depende de la plataforma, normalmente cercano a 2 billones (32 bits con signo). En caso de overflow el número entero se pasará a float.

    La división entre dos números enteros siempre devolverá un float. Al hacer casting a entero se redondea hacia abajo.

    Números de punto flotante:
    1.234, 1.2e3, 7E-10

    El tamaño depende de la plataforma lo más normal son 14 dígitos decimales (64 bits).

    Hay que tener cuidado con el margen de error, no se recomienda comparar dos números float para saber si son iguales. Para eso están las funciones matemáticas de precisión arbitraria.

    Cadenas
    En php cada uno de los caracteres de una cadena es un byte, por lo que no tiene soporte nativo de unicode. Si necesitamos unicode tenemos que utilizar utf8_encode() y utf8_decode().

    Las cadenas se pueden especificar de tres modos diferentes: Con comillas simples, con comillas dobles y mediante HEREDOC.

    • 'hola': Para introducir una comilla simple hay que escaparla: 'O\'Connor'
    • "hola": Para introducir una comilla doble hay que escaparla: <span "lang=\"eu\">Kaixo</span>"
    • HEREDOC: igual que las comillas dobles. Útil para no tener que escapar las comillas dobles.
          $var = << FINDETEXTO
          Hola esto es un texto
          muy largo
          en varias líneas con un <a href="index.php">enlace</a>
          FINDETEXTO //findetexto tiene que ir al principio de la línea
    
    Tanto las cadenas delimitadas por comillas dobles como mediante HEREDOC procesan el contenido de la cadena. Los \t se convierten en tabuladores, los \n se convierten en retornos de carro, y las variables se interpretan:
          $nombre='Paco';
          echo "Tu nombre es: $nombre"; # imprime 'Tu nombre es: Paco'
    
          $frutas = array('fresa' => 'roja', 'platano' => 'amarillo');
          echo "Un pl&aacute;tano es {$frutas['platano']}.";
    
          $cerveza = 'Keler';
          echo "quiero tres {$cerveza}s"; # imprime "quiero tres Kelers"
    
    Para unir cadenas:
          $numeros = 'uno, ' . 'dos'; # $números ahora es 'uno, dos'
          $cerveza .= ' fr&iacute;a'; # $cerveza ahora es 'Keler fr&iacute;a'
    
    Para conseguir un carácter:
          echo $cerveza{0} # imprime 'K'
    
    Para modificarlo:
          $cerveza{2}='o' # Ahora $cerveza es 'Keoer'
    
  • Compuestos:
    Matrices:
    En php no existen los arrays típicos, son estructuras donde se relacionan índices con valores.
          $dos = array('padre'=>'Jokin', 'madre'=>'Edurne', 1=>'uno')
          $uno = array('cero','uno',2,3) # Si no se especifican indices se
                                         # asignan automáticamente: 0, 1, 2,...
          $tres = array(array(1,2),array(3,4) #Una matriz de dos dimensiones
    
    Para conseguir un elemento:
            $variable[0]
            $variable['padre']
    
    Para borrar:
            unset($variable['madre'])
    
    Para añadir más elementos:
            $variable[11]='once' # si el indice 11 ya existía se reemplaza.
            $variable[]='nuevo y al final' # indice = el indice más alto + 1
    
    Objetos: (ya los veremos más adelante)
          class foo {
              function hacer_foo() {
                echo "Haciendo foo.";
              }
          }
    
          $bar = new foo;
          $bar->hacer_foo();
    
  • Especiales:
    Recurso
    Para almacenar recursos externos a php. Muy habituales programando sin orientación a objetos.
          $corrector = aspell_new()
          $resultados = mysql_query($sql)
    
    NULL
    Para cuando una variable no ha sido definida, o eliminada con unset(), o asignada a NULL.
          $variable = NULL;
    

Variables

Se representan con un dolar seguido de el nombre de la variable. Se distingue entre mayúsculas y minúsculas. Las variables tienen que empezar por letra o subrayado, y continuar con letras, números o subrayado.

$variable = 'valor'
$$variable = '1' # igual que $valor='1'

$copia = $variable
&$referencia = $variable

$foo = 'Bob';                // Asigna el valor 'Bob' a $foo
$bar = &$foo;                // Referencia $foo v&iacute;a $bar.
$bar = "Mi nombre es $bar";  // Modifica $bar...
echo $foo;                   // $foo tambi&eacute;n se modifica.
echo $bar;

Variables superglobales

Se les llama así porque son variables definidas antes de que se ejecute el script, que se hacen globales automáticamente. Contienen datos variables del servidor.

$GLOBALS
Contiene una referencia a cada variable disponible en el espectro de las variables del script. Las llaves de esta matriz son los nombres de las variables globales.
$_SERVER
Variables definidas por el servidor web ó directamente relacionadas con el entorno en don el script se esta ejecutando.
$_GET
Variables proporcionadas al script por medio de HTTP GET.
$_POST
Variables proporcionadas al script por medio de HTTP POST.
$_COOKIE
Variables proporcionadas al script por medio de HTTP cookies.
$_FILES
Variables proporcionadas al script por medio de la subida de ficheros via HTTP.
$_ENV
Variables proporcionadas al script por medio del entorno.
$_REQUEST
Variables proporcionadas al script por medio de cuaquier mecanismo de entrada del usuario y por lo tanto no se puede confiar en ellas. Por ejemplo contiene tanto los valores de $_POST como los de $_GET
$_SESSION
Variables registradas en la sesión del script.

Obteniendo datos de formularios

El siguiente formulario XHTML:

  <form action="foo.php" method="post">
    Nombre: <input type="text" name="nombre" />
    Email: <input type="text" name="email" />
    <input type="submit" name="submit" value=" Enviar " />
  </form>

Pone accesibles dos variables nuevas en foo.php a través de la variable superglobal $_POST: $_POST['nombre'] y $_POST['email']. En este caso son variables simples, a partir de php4 podemos enviar variables más complejas:

  <form action="foo.php" method="post">
      Nombre: <input type="text" name="personal[name]" />
      Email: <input type="text" name="personal[email]" />
      Cervezas:
      <select multiple name="cervezas[]">
          <option value="rubia">Rubia</option>
          <option value="tostada">Tostada</option>
          <option value="negra">Negra</option>
      </select>
      <input type="submit" name="submit" value=" Enviar " />
  </form>

En este caso $_POST['personal'] será un array con los índices name y email, mientras que $_POST['cervezas'] será un array que contendrá la lista de cervezas elegidas por el usuario.

Constantes

No pueden cambiar durante la ejecución del script.

Solo pueden albergar valores escalares.

El ámbito de las constantes es siempre global.

define("CONSTANTE", "Hola mundo.");
echo CONSTANTE; //"Hola mundo."

Operadores

Operadores de Aritmética

Ejemplo Nombre Resultado
-$a Negación El opuesto de $a.
$a + $b Adición Suma de $a y $b.
$a - $b Substracción Diferencia entre $a y $b.
$a * $b Multiplicación Producto de $a y $b.
$a / $b División Cociente de $a y $b.
$a % $b Módulo Resto de $a dividido por $b.

Operadores de Asignación

El principal es el operador =.

Se puede combinar = con los operadores aritméticos: +=, -=, *=, /=, %=:

  $cont = 1;
  $cont += 3; # $contador ahora vale 4

Operadores Bit a Bit

Ejemplo Nombre Resultado
$a&$b Y Los bits que están activos tanto en $a como en $b son activados.
$a|$b O Los bits que están activos ya sea en $a o en $b son activados.
$a^$b O exclusivo (Xor) Los bitos que estén activos en $a o $b, pero no en ambos, son activados.
~$a No Los bits que estén activos en $a son desactivados, y vice-versa.
$a<<$b Desplazamiento a izquierda Desplaza los bits de $a, $b pasos a la izquierda (cada paso quiere decir "multiplicar por dos")
$a>>$b Desplazamiento a derecha Desplaza los bits de $a, $b pasos a la derecha (cada paso quiere decir "dividir por dos")

Operadores de Comparación

Ejemplo Nombre Resultado
$a == $b Igual TRUE si $a es igual a $b.
$a === $b Idéntico TRUE si $a es igual a $b, y son del mismo tipo. (A partir de PHP 4)
$a != $b Diferente TRUE si $a no es igual a $b.
$a <> $b Diferente TRUE si $a no es igual a $b.
$a !== $b No idénticos TRUE si $a no es igual a $b, o si no son del mismo tipo. (A partir de PHP 4)
$a < $b Menor que TRUE si $a es escrictamente menor que $b.
$a > $b Mayor que TRUE si $a es estrictamente mayor que $b.
$a <= $b Menor o igual que TRUE si $a es menor o igual que $b.
$a >= $b Mayor o igual que TRUE si $a es mayor o igual que $b.

Operadores de control de errores

Poner el carácter @ antes de una expresión hace que los mensajes de error generados por la expresión sean ignorados. El mensaje de error se guardarán en la variable $php_errormsg

  $valor = @$cache[$llave]; #No produce error si el índice $llave no existe

Operadores de ejecución

El texto que pongamos entre comillas invertidas se intentará ejecutar en la shell y se devolverá la salida, por lo que se pued asignar a una variable:

  $salida = `ls -al`;
  echo "<pre>$salida</pre>";

Operadores de Incremento/Decremento

Ejemplo Nombre Efecto
++$a Pre-incremento Incrementa $a en uno, y luego devuelve $a.
$a++ Post-incremento Devuelve $a, y luego incrementa $a en uno.
--$a Pre-decremento Decrementa $a en uno, luego devuelve $a.
$a-- Post-decremento Devuelve $a, luego decrementa $a en uno.

Operadores de Lógica

Ejemplo Nombre Resultado
$a and $b Y TRUE si tanto $a como $b son TRUE.
$a or $b O TRUE si cualquiera de $a o $b es TRUE.
$a xor $b O exclusivo (Xor) TRUE si $a o $b es TRUE, pero no ambos.
! $a No TRUE si $a no es TRUE.
$a && $b Y TRUE si tanto $a como $b son TRUE.
$a||$b O TRUE si cualquiera de $a o $b es TRUE.

Operadores de Matrices

Ejemplo Nombre Resultado
$a + $b Unión Unión de $a y $b. Los índices de $a NO se sobreescriben por $b
$a == $b Igualdad TRUE si $a y $b tienen las mismas parejas llave/valor.
$a === $b Identidad TRUE si $a y $b tienen las mismas parejas llave/valor en el mismo orden y de los mismos tipos.
$a != $b No-igualdad TRUE si $a no es igual a $b.
$a <> $b No-igualdad TRUE si $a no es igual a $b.
$a !== $b No-identidad TRUE si $a no es idéntico a $b.

Operadores de Tipo

Sólo existe el operador instanceof para saber una variable es instacia de una clase determinada:

  class A { }
  class B { }

  $cosa = new A;

  if ($cosa instanceof A) { #En este if sí que entramos
      echo 'A';
  }
  if ($cosa instanceof B) { #En este no
      echo 'B';
  }

Estructuras de control

If, else, elseif, switch

    if ($a > $b) {
        print "a es mayor que b";
    } elseif ($a == $b) {
        print "a es igual que b";
    } else {
        print "a es mayor que b";
    }

    switch ($i) {
        case 0:
      print "i equals 0";
      break;
        case 1:
      print "i equals 1";
      break;
        case 2:
      print "i equals 2";
      break;
    }

While, Do while

    $i = 1;
    while ($i <= 10):
        print $i;
        $i++;
    endwhile;

    $i = 0;
    do {
        print $i;
        $i++;
    } while ($i>5);

For, foreach

    for ($i = 1; $i <= 10; $i++) {
         print $i;
    }


    $arr = array("one", "two", "three");
    foreach ($arr as $value) {
        echo "Value: $value\n";
    }
    foreach( $arr as $key => $value ) {
       echo "Key: $key; Valor: $value\n";
    }

Break, continue

    $i = 0;
    while (++$i) {
        switch ($i) {
        case 5:
          echo "At 5\n";
          break 1;  /* Exit only the switch. */
        case 10:
          echo "Al 10; saliendo<br>\n";
          break 2;  /* Exit the switch and the while. */
        default:
      break;
        }
    }

    $i = 0;
    while ($i++ < 5) {
        echo "Outer";
        while (1) {
      echo "Middle";
      while (1) {
          echo "Inner";
          continue 3;
      }
      echo "Esto nunca se imprime.";
        }
        echo "Y esto tampoco.";
    }

Require, include, require_once, include_once

require() y include() son idénticas en todos los aspectos excepto en el modo de actuar ante un error. include() produce un Warning mientras que require() produce un Error Fatal.

Las versiones _once solo se ejecutan la primera vez que se les llama.

    require('fichero.php');
    include('fichero.php');
    require_once('fichero.php');
    include_once('fichero.php');

Funciones

function foo ($arg_1, $arg_2, ..., $arg_n)
{
    #cuerpo de la funcion
    return $retval;
}

Las funciones pueden declararse en cualquier lugar del código.

No está soportada la redefinición de funciones.

Sólo pueden devolver un elemento (aunque pueden devolver listas).

Se pueden especificar valores por defecto. El valor por defecto tiene que ser una constante, y los parametros con valor por defecto tienen que ir al final.

function hola ($arg_1="mundo")
{
    #cuerpo de la funcion
    echo "Hola {$arg_1}!;
}

Para pasar un valores por referencia:

function add_some_extra(&$string)
{
    $string .= ' y algo m&aacute;s.';
}
$str = 'Esto es una cadena, ';
add_some_extra($str);
echo $str;    // Saca 'Esto es una cadena, y algo m&aacute;s.'

Para pasar un número variable de parámetros se pueden utilizar las funciones func_num_args(), func_get_arg(), y func_get_args():

function foo()
{
     $num_args = func_num_args();
     echo "Numero de argumentos: $num_args<br />\n";
     if ($num_args >= 2) {
     echo "El segundo argumento es: " . func_get_arg(1) . "<br />\n";
     }
}

foo (1, 2, 3);

Se pueden utilizar funciones variables:

function foo()
{
    echo "In foo()<br>\n";
}

function bar($arg = '')
{
    echo "In bar(); argument was '$arg'.<br>\n";
}

// This is a wrapper function around echo
function echoit($string)
{
    echo $string;
}

$func = 'foo';
$func();        // This calls foo()

$func = 'bar';
$func('test');  // This calls bar()

$func = 'echoit';
$func('test');  // This calls echoit()

PHP ofrece muchas funciones internas, algunas que están siempre disponibles (como phpinfo()), otras necesitarán que php esté compilado con soporte para ellas (como mysql_connect()).

Objetos

Un poco de teoría

(Basado en la wikipedia: http://es.wikipedia.org/wiki/Programaci%C3%B3n_orientada_a_objetos) La Programación Orientada a Objetos es un paradigma de programación que define los programas en términos de "clases de objetos". Los objetos conbinan estado (datos), comportamiento (métodos) e identidad (se pueden diferenciar entre ellos). Un objeto contiene información necesaria para definirlo (los atributos) y mecanismos de interacción con otros objetos (los métodos).

En vez de pensar en términos de procedimientos y funciones, tendremos que pensar en 'cosas' que interactúan entre ellas. Con las funciones realizamos ciertas acciones sobre los datos con intención de conseguir datos nuevos. Con los objetos crearemos ciertos entes que interactuarán entre ellos por medio de sus propios métodos.

La programación orientada a objetos tiene las siguientes características:

Abstracción
Cada objeto puede realizar su trabajo sin necesidad de revelar cómo lo realiza. Ejemplo: El objeto coche se podrá acelerar o frenar, pero no tenemos porqué saber cómo realiza la aceleración (eléctrico o de gasolina) o como frena (frenos o paracaídas).
Encapsulamiento
Un objeto contiene todos los datos y métodos necesarios para trabajar con una entidad. Ejemplo: El objeto coche tendrá información (atributos) sobre su velocidad, su matrícula, número de ocupantes, etc. y métodos para acelerar, frenar, cambiar matrícula, subir y bajar gente, etc.
Principio de ocultación
Los datos se ocultan, permitiendo su modificación únicamente mediante los métodos. De este modo se garantiza la integridad de los datos. Ejemplo: La velocidad del objeto coche solo se puede cambiar mediante los métodos acelerar y frenar; estos métodos no permitirán cambios antinaturales de velocidad (de 0 a 100 en 0 segundos) ni velocidades por encima de las permitidas en un coche (match 3).
Polimorfismo
Diferentes objetos pueden tener métodos del mismo nombre que realicen tareas equivales para cada objeto. Ejemplo: El objeto coche y el objeto moto tendrán el método acelerar.
Herencia
Las clases se pueden organizar en jerarquías. Cada objeto heredará todos los métodos y atributos de las clases a las que pertenece. Se pueden añadir métodos o atributos nuevos, o cambiar el comportamiento de los métodos. Ejemplo: Tanto el objeto coche como el objeto moto heredarán sus características de la clase vehiculo.

POO en PHP

Sintaxis de las clases:

  class vehículo {
      // declaración de atributos
      public $velocidad = 0; //valor por defecto
      public $velocidadMAX = 0; //valor por defecto

      // declaración de métodos
      public function acelerar($velocidad_nueva) {
          if ($velocidad_nueva > $this-velocidadMAX) {
            $this->velocidad=$this->velocidadMAX
          }
          else {
            $this->velocidad=$velocidad_nueva
          }
          echo "Velocidad actual: {$this->velocidad} Km/s"
      }
  }

Para crear nuevas instancias de las clases (objetos):

    $auto = new vehículo();
    $auto->acelerar(100);

En php5 no está soportada la herencia múltiple, sólo se puede heredar de una clase. Se puede acceder a los métodos de la clase padre con parent.

  class coche extends vehículo {
      // Redefinimos el atributo velocidadMAX
      public $velocidadMAX = 140;

  }

Si el objeto necesita ser inicializado y/o destruído de forma especial, se definen el constructor __construct() y el destructor __destruct().

  class coche extends vehículo {
      // Redefinimos el atributo velocidadMAX
      public $velocidadMAX = 140;

      // Añadimos atributo matrícula
      public $matricula;

      function __construct($matricula) {
        $this->matricula=$matricula;
      }
  }

  $coche1 = new coche("SS-0000-A");

Se puede controlar la visibilidad de los métodos y los atributos:

public
El elemento se puede acceder desde cualquier parte.
protected
El elemento sólo se puede acceder desde la propia clase y sus herederas.
private
El elemento solo se puede acceder desde la propia clase.

En los atributos es obligatorio especificar visibilidad, en los métodos es public por defecto.

  class NombreDeClase {
    public $variable = "me pueden leer todos";
    protected $protegida ="me pueden leer los de esta clase y sus hijos";
    private $secreto="solo se me lee desde esta clase";

    //por defecto las funciones son public
    function __construct(parametros) {
      //Los '::' son necesarios para acceder a la versión
      //vieja de la función que ha sido sobreescrita;
      parent::__construct(parametros_del_padre);
    }

    protected function funcion1($prefijo,$nombre) {
      echo "Saludos! $prefijo $nombre";
    }

    function __destruct() {
      echo "Adiós mundo cruel!";
    }
  }

Cuando una clase se define como abstracta varias de sus funciones se dejan sin implementar.

    abstract class claseAbstracta {
      abstract protected function funcionabstracta();
      abstract protected function funcionabstracta2($nombre);
      public function saludo() {
        echo "hola";
      }
    }

Cuando una clase se define como interfaz, solo se definen los nombre de las funciones. Los objetos pueden implementar más de una interfaz. Es obligatorio implementar todos los métodos de la interfaz.

La principal ventaja de las interfaces es que permiten definir qué comportamiento tiene que tener un objeto determinado. Por ejemplo:

  interface modo_de_desplazamiento {
    public function acelerar();
    public function frenar();
  }

  interface transporta_carga {
    public function cargar();
    public function descargar();
  }

  interface extensible {
    public function incorporar();
    public function remover();
  }

  class coche implements modo_de_desplazamiento, transporta_carga {
    //...
  }

  class moto implements modo_de_desplazamiento {
    //...
  }

  class tren implements modo_de_desplazamiento, transporta_carga, extensible {
    //...
  }

Es posible sobrecargar métodos y variables, pero es bastante rebuscado: http://www.php.net/manual/es/language.oop5.overloading.php

Los objetos se pueden utilizar en un foreach. Por defecto recorrerá todos los atributos accesibles:

  class MyClass
  {
      public $var1 = 'value 1';
      public $var2 = 'value 2';
      public $var3 = 'value 3';

      protected $protected = 'protected var';
      private   $private   = 'private var';

      function iterateVisible() {
         echo "MyClass::iterateVisible:\n";
         foreach($this as $key => $value) {
             print "$key => $value\n";
         }
      }
  }

  $class = new MyClass();

  foreach($class as $key => $value) {
      print "$key => $value\n";
  }
  echo "\n";


  $class->iterateVisible();

  //Resultado:
  //var1 => value 1
  //var2 => value 2
  //var3 => value 3
  //MyClass::iterateVisible:
  //var1 => value 1
  //var2 => value 2
  //var3 => value 3
  //protected => protected var
  //private => private var

Podemos controlar este comportamiento implementando la interfaz Iterator:

  interface Iterator {
      public function rewind();
      //Prepara la clase para recorrerla desde el principio
      //(index=0, por ejemplo)

      public function current();
      //Devuelve el valor actual

      public function key();
      //Devuelve el indice actual

      public function next();
      //Pasa al siguiente y devuelve su valor

      public function valid();
      //True si existe un valor actual
  }

En php están definidos los patrones Factory y Singleton: http://www.php.net/manual/es/language.oop5.patterns.php

A todos los métodos que empiecen por dos marcas de subrayado __ se les llama métodos mágicos y se consideran reservados por php. Nos servirán para cambiar el comportamiento por defecto de nuestros objetos:

__construct y __destruct
Para inicializar y finalizar un objeto
__call, __set, __get, __isset y __unset
Para sobrecarga de métodos y atributos
__sleep y __wakeup
Para serialización de objetos
__toString
Para cuando se haga un echo del objeto
__clone
Para cuando queramos obtener una copia del objeto
__autoload
Para la carga automática de módulos

Las funciones definidas como 'final' no podrán ser sobreescritas por los hijos. Las clases definidas como 'final' no podrán ser extendidas.

  final class coche {
    //...
  }

Cuando se comparan dos objetos diferentes se consideran iguales (==) si tienen los mismos atributos con los mismos valores y son instancias de la misma clase. Se consideran idénticos (===) si son la misma instancia.

Excepciones

El tratamiento de excepciones es muy parecido a otros lenguajes. Cuando se lanza una excepción la siguiente línea de código no se ejecuta y se busca el primer bloque de captura de excepciones. Si no se captura la excepción en ningún momento php lanza un error fatal.

try {
    $error = 'Siempre lanzar error';
    throw new Exception($error);

    // El código después de una excepción no se ejecuta nunca
    echo 'Never executed';

} catch (Exception $e) {
    echo 'Capturada exception: ',  $e->getMessage(), "\n";
}

// Continuar ejecución
echo 'Hello World';

MySQL

Crear una conexión

$enlace = mysql_connect('host_mysql', 'usuario_mysql', 'contrasenya_mysql')
  or die('No pudo conectarse : ' . mysql_error());

Seleccionar una base de datos

mysql_select_db('mi_base_de_datos') or die('No pudo seleccionarse la BD.');

Ejecutar un comando SQL

$consulta  = 'SELECT * FROM mi_tabla';
$resultado = mysql_query($consulta) or die('La consulta fall&oacute;: ' . mysql_error());
  dni_bueno = mysql_real_escape_string ($DNI, $enlace)

Obtener las líneas de respuesta

// Impresion de resultados en HTML
echo "<table>\n";
while ($linea = mysql_fetch_array($resultado, MYSQL_ASSOC)) {
    echo "\t<tr>\n";
    foreach ($linea as $valor_col) {
        echo "\t\t<td>$valor_col</td>\n";
    }
    echo "\t</tr>\n";
}
echo "</table>\n";

Todo junto:

// Conexion, seleccion de base de datos
$enlace = mysql_connect('host_mysql', 'usuario_mysql', 'contrasenya_mysql')
    or die('No pudo conectarse : ' . mysql_error());
echo 'Conexi&oacute;n exitosa';
mysql_select_db('mi_base_de_datos') or die('No pudo seleccionarse la BD.');

// Realizar una consulta SQL
$consulta  = 'SELECT * FROM mi_tabla';
$resultado = mysql_query($consulta) or die('La consulta fall&oacute;: ' . mysql_error());

// Impresion de resultados en HTML
echo "<table>\n";
while ($linea = mysql_fetch_array($resultado, MYSQL_ASSOC)) {
    echo "\t<tr>\n";
    foreach ($linea as $valor_col) {
        echo "\t\t<td>$valor_col</td>\n";
    }
    echo "\t</tr>\n";
}
echo "</table>\n";

// Liberar conjunto de resultados
mysql_free_result($resultado);

// Cerrar la conexion
mysql_close($enlace);

No es necesario cerrar la conexión, se cierra sola al acabar el script.

Es habitual guardar en un fichero aparte los datos necesarios para realizar la conexión, y en otro el código necesario para establecer la conexión.

Algunas funciones útiles:

string mysql_real_escape_string ( string cadena_no_escapada [, resource id_enlace] )
Escapa todos los carácteres de la cadena para que sea seguro utilizarla en una consulta sql (ayuda a evitar los ataques SQL Injection)
    //Ejemplo de inyección SQL
    // Consultar la base de datos para verificar si hay una coincidencia de usuario
    $consulta = "SELECT * FROM usuarios WHERE usuario='{$_POST['username']}' AND password='{$_POST['password']}'";
    mysql_query($consulta);
    // No revisamos $_POST['password'], podria ser cualquier cosa que el usuario
    // quiera! Por ejemplo:
    $_POST['username'] = 'aidan';
    $_POST['password'] = "' OR ''='";
    // Esto quiere decir que la consulta enviada a MySQL seria:
    echo $consulta;
    //SELECT * FROM usuarios WHERE usuario='aidan' AND password='' OR ''=''

    //SOLUCIÓN
    function comillas_inteligentes($valor) {
        // Retirar las barras
        if (get_magic_quotes_gpc()) {
            $valor = stripslashes($valor);
        }

        // Colocar comillas si no es entero
        if (!is_numeric($valor)) {
            $valor = "'" . mysql_real_escape_string($valor) . "'";
        }
        return $valor;
    }

    // Realizar una consulta segura
    $consulta = sprintf("SELECT * FROM usuarios WHERE usuario=%s AND password=%s",
                comillas_inteligentes($_POST['username']),
                comillas_inteligentes($_POST['password']));
    mysql_query($consulta);
cadena mysql_error ( [int identificador_de_enlace] ) y int mysql_errno ( [int identificador_de_enlace] )
Para obtener el mensaje y el número de error de MySQL cuando una consulta falla. Si no ha habido error devuelve una cadena vacía.
    $link = mysql_connect("localhost", "mysql_user", "mysql_password");

    if (!mysql_select_db("nonexistentdb", $link)) {
        echo mysql_errno($link) . ": " . mysql_error($link). "\n";
    }

    mysql_select_db("kossu", $link);
    if (!mysql_query("SELECT * FROM nonexistenttable", $link)) {
        echo mysql_errno($link) . ": " . mysql_error($link) . "\n";
    }
    //esto produciría:
    //1049: Unknown database 'nonexistentdb'
    //1146: Table 'kossu.nonexistenttable' doesn't exist
int mysql_insert_id ( [int identificador_de_enlace] )
Para obtener el último id generado para una columna autonumérica. Si la columna autonumérica es de tipo bigint es mejor utilizar la función mysql LAST_INSERT_ID().
    mysql_query("INSERT INTO mytable (product) values ('kossu')");
    printf("Last inserted record has id %d\n", mysql_insert_id());
int mysql_affected_rows ( [int identificador_de_enlace] )
Devuelve el número de líneas afectadas en las consultas INSERT, UPDATE y DELETE.
    mysql_query('DELETE FROM mytable WHERE id < 10');
    printf("Records deleted: %d\n", mysql_affected_rows());
int mysql_num_rows ( int id_resultado )
Devuelve el número de líneas del resultado de un SELECT.
    $result = mysql_query("SELECT * FROM table1", $link);
    $num_rows = mysql_num_rows($result);

Sesiones

Con las sesiones podemos 'recordar' datos sobre un cliente. A cada cliente se le genera un identificador de sesión, que se propagará mediante una cookie o mediante la url. Si se utilizan sesiones mediante cookies la sesión tiene que empezar antes de mostrar nada en pantalla.

session_start();
if (!isset($_SESSION['count'])) {
    $_SESSION['count'] = 0;
} else {
    $_SESSION['count']++;
}

//para borrar una variable de la sesión:
unset($_SESSION['count']);

//para borrar todas las variables de la sesión:
$_SESSION = array();
session_destroy();

Formularios y ficheros

Se tienen que enviar mediante el metodo post, y el tipo de codificación tiene que ser multipart/form-data:

<form enctype="multipart/form-data" action="destino.php" method="post">
	<label for="fichero">Enviar imagen:</label>
	<input id="fichero" name="fichero" type="file" />
	<input type="submit" value="Enviar Imagen" />
</form>

Variables en el script destino:

$_FILES['fichero']['name']
El nombre original del fichero en la máquina cliente.
$_FILES['fichero']['type']
El tipo mime del fichero (si el navegador lo proporciona). Un ejemplo podría ser "image/png". (mirar /etc/mime.types)
$_FILES['fichero']['size']
El tamaño en bytes del fichero recibido.
$_FILES['fichero']['tmp_name']
El nombre del fichero temporal que se utiliza para almacenar en el servidor el archivo recibido.

Funciones:

bool is_uploaded_file ( string nombre_archivo )
Para verificar que el archivo ha sido subido al servidor por post. No queremos que alguien nos pida nuestro /etc/passwd...
    if (is_uploaded_file($_FILES['fichero']['tmp_name'])) {
       echo "El archivo ". $_FILES['fichero']['name'] ." fue cargado satisfactoriamente.\n";
       echo "Mostrando su contenido\n";
       readfile($_FILES['fichero']['tmp_name']);
    } else {
       echo "Posible ataque de carga de archivo: ";
       echo "nombre de archivo '". $_FILES['fichero']['tmp_name'] . "'.";
    }
bool move_uploaded_file ( string nombre_archivo, string destino )
Para guardarlo en el sistema de fiheros. Antes hace la misma comprobación que is_uploaded_file()
    move_uploaded_file($_FILES['fichero']['tmp_name'], "/path/del/servidor/para/el/fichero");

Autenticación mediante HTTP Basic

La autenticación se hace mediante HTTP. El navegador sacará una ventana para que introduzcamos el usuario y la contraseña. Mientras no se cierre el navegador podremos entrar en la zona privada.

No es un sistema muy seguro, ya que la contraseña se envía en texto plano (codificado en base64). Para conseguir un sitio seguro además hay que configurar SSL en el servidor (direcciones https).

if (!isset($_SERVER['PHP_AUTH_USER'])) {
	header('WWW-Authenticate: Basic realm="My Realm"');
	header('HTTP/1.0 401 Unauthorized');
	echo 'Tienes que ser un usuario registrado para poder entrar.';
	exit;
} else {
	echo "<p>Hola {$_SERVER['PHP_AUTH_USER']}.</p>";
	echo "<p>Has metido la contraseña {$_SERVER['PHP_AUTH_PW']}.</p>";
}

BasicAuthenticator es un ejemplo de implementación de autenticación con HTTP Basic. Su uso:

require_once('BasicAuthenticator.php');
$auth = new BasicAuthenticator("Galeria");
$auth->authenticate();

Imágenes con GD

El funcionamiento general del tratamiento de imágenes es siempre parecido:

  • Convertir la imagen a un formato que comprenda GD
  • Manipular la imagen
  • Convertir el formato de GD al formato que nos interese.

El formato que utiliza GD no tiene compresión, por lo que tendremos que andarnos con ojo con el consumo de memoria.

Las imágenes se tratan como si fueran matrices de píxeles.

Manipular imágenes

http://www.php.net/manual/es/ref.image.php

Obtener el tamaño de una imagen
    array getimagesize ( string nombre_archivo )

    list($ancho, $altura, $tipo, $atr) = getimagesize("img/bandera.jpg");
El array nos dará información tanto del tamaño como del tipo de imagen:
  • array[0]->Ancho
  • array[1]->Alto
  • array[2]->Tipo MIME (/etc/mime.types)
  • array[3]->string 'height="yyy" width="xxx"'
Crear un lienzo en blanco de anchura x altura para una imagen nueva
    resource imagecreatetruecolor ( int anchura, int altura )

> <

dd>
Crear un lienzo con el contenido de una imagen
  resource imagecreatefromgif ( string nombre_archivo )
  resource imagecreatefromjpeg ( string nombre_archivo )
  resource imagecreatefrompng ( string nombre_archivo )
Además de estas, hay otras funciones parecidas para otros formatos. Todas se utilizan de manera parecida.

> <

dd>
Liberar la memoria utilizada por la imagen im
    bool imagedestroy ( resource im )
Como las imágenes suelen ocupar bastante memoria, es interesante ir destruyéndolas a medida que no nos hacen falta.

> <

dd>
Copiar en una imagen una porción de otra
    bool imagecopy ( resource dst_im, resource src_im,
                     int dst_x, int dst_y,
                     int src_x, int src_y,
                     int src_w, int src_h )
Copia en src_im, en las coordenadas (dst_x,dst_y) la porción de la imagen fuente definida por las coordenadas (src_x,src_y) y por el ancho y alto src_w y src_h respectivamente.

> <

dd>
Fundir dós imágenes
    bool imagecopymerge ( resource dst_im, resource src_im,
                          int dst_x, int dst_y,
                          int src_x, int src_y,
                          int src_w, int src_h,
                          int pct )
Igual que imagecopy, pero se realiza un fundido entre las dos imágenes. Si pct es 0 no se realiza ninguna acción, si pct es 100 funciona idéntico a imagecopy.

> <

dd>
Rotar la imagen
    resource imagerotate ( resource src_im, float angle,
                           int bgd_color [, int ignore_transparent] )
Rota la imagen src_im con el ángulo angle, rellenando los huecos nuevos con el color bgd_color. Si ignore_transparent diferente de cero se ignoran los colores transparentes.

> <

dd>
Copiar una porción de una imagen en otra, redimensionándola si es necesario.
    bool imagecopyresized ( resource dst_im, resource src_im,
                            int dstX, int dstY,
                            int srcX, int srcY,
                            int dstW, int dstH,
                            int srcW, int srcH )
Copia la imagen src_im en dst_im. Si el tamaño de destino es diferente del de origren la imagen se redimensiona.

> La función <code>imagecopyresampled<

code>, con los mismos parámetros, además de redimensionar suaviza los píxeles mediante interpolación, consiguiendo mejor calidad.

> <

dd>
Espejar imágenes:
PHP no tiene una función especial para espejar imágenes, pero es sencilla de programar:
    function imageflip($image, $mode) {
      $w = imagesx($image);
      $h = imagesy($image);
      $flipped = imagecreate($w, $h);
      if ($mode) {
        for ($y = 0; $y < $h; $y++) {
          imagecopy($flipped, $image, 0, $y, 0, $h - $y - 1, $w, 1);
        }
      } else {
        for ($x = 0; $x < $w; $x++) {
          imagecopy($flipped, $image, $x, 0, $w - $x - 1, 0, 1, $h);
        }
      }
      return $flipped;
    }

Creación de imágenes

Aunque bastante menos habitual, es posible que necesitemos crear imágenes desde cero. Generalmente será más interesante utilizar algún módulo ya desarrollado para que haga ese trabajo para nosotros: JpGraph para crear gráficos, PEAR:Image_Barcode para crear códigos de barras, PEAR:Text_CAPTCHA para captchas en formularios, etc.

Definir colores que se utilizarán en la imagen
    int imagecolorallocate ( resource im, int red, int green, int blue )
    int imagecolorallocatealpha ( resource image, int red, int green, int blue, int alpha )
El primer color que definamos será el que se utilize para el fondo. Las funciones devuelven el color de manera que podremos utilizarlo en otras funciones. El valor alpha va desde el 0 (completamente opaco) hasta 127 (completamente transparente).

> <

dd>
Cargar una fuente para poder escribir en la imagen
    int imageloadfont ( string archivo )     
Sólo carga fuentes de mapas de bits

> <

dd>
Escribir un caracter o una string en horizontal o en vertical
    bool imagechar ( int im, int font, int x, int y, string c, int col )
    bool imagecharup ( resource im, int font, int x, int y, string c, int col )
    int imagestring ( int im, int font, int x, int y, string s, int col )
    int imagestringup ( int im, int font, int x, int y, string s, int col )
Escribe el primer carácter de la string en la posición (x,y) con el color col. Las versiones acabadas en up escriben en vertical. La fuente tiene que ser una cargada con imageloadfont. Las coordenadas (0,0) implican el píxel de arriba a la izquierda.

> <

dd>
Escribir en un ángulo determinado con fuentes TrueType
    array imagettftext ( resource imagen, float tamanyo, float angulo,
                         int x, int y, int color,
                         string archivo_fuente, string texto )
En este caso las coordenas (0,0) implican el píxel de abajo a la izquierda. Necesita tener instalada la biblioteca FreeType.

> <

dd>
Definir el color del trazo
    bool imagesetthickness ( resource image, int thickness )
Se utiliza al dibujar rectángulos, polígonos, eclipses, etc.

> <

dd>
Definir el formato del trazo
    bool imagesetstyle ( resource image, array style )
style es un array de píxels. Los píxels se pueden obtener mediante imagecoloallocate. Las líneas, polígonos, etc. que utilicemos usarán style como patrón.

> <

dd>
Dibujar lineas
    bool imageline ( resource im, int x1, int y1, int x2, int y2, int col )
Dibuja una línea desde las coordenadas (x1,y1) hasta las coordenadas (x2,y2) con el color col.

> Mejor mirar en la documentación de php: <a href="http:

/www.php.net/manual/es/ref.image.php">http://www.php.net/manual/es/ref.image.php

> <

dd>
Otras cosas que se pueden hacer
  • Rellenar con un color o con un patrón (imagen que se copiará en mosaico)
  • Obtener el color de un píxel determinado
  • Inclinar fuentes para formar cursivas
  • Utilizar fuentes de tipo PostScript Type1
  • Dibujar otras formas: Rectángulos, polígonos, arcos, elipses,... tanto rellenas como sin rellenar.
  • ...

Guardar o publicar las imágenes

Una vez que tenemos la imagen tal y como la queremos mostrar, tendremos que convertirla a un formato que pueda mostrarse en el navegador. Tenemos dos opciones, mostrarla directamente en pantalla o guardarla en un fichero al que luego accederemos con la etiqueta (x)html img.

En ambos casos se utilizan las mismas funciones. Existe una función para cada tipo de fichero. Si no se especifica fichero de destino se envía directamente a pantalla. Como ejemplo, para PNG o JPEG:

Exportar la imagen a PNG
bool imagepng ( resource image [, string filename] )
Exportar la imagen a JPEG. La calidad va de 0 (peor) a 100 (mejor).
bool imagejpeg ( resource image [, string filename [, int quality]] )

Para mostrar la imagen directamente en pantalla primero hay que enviar una cabecera http para especificar el tipo MIME del fichero. Como todas las cabeceras, tiene que enviarse antes que cualquier contenido.

  header("Content-type: image/png");

Fecha y Hora

En php las fechas se trabajan mejor con marcas de tiempo. Una marca de tiempo establece la cantidad de segundos que han pasado desde un momento determinado (en PHP5, desde el 1 de enero de 1900). La ventaja de utilizar marcas de tiempo es que se pueden sumar o restar fácilmente (para saber cuanto tiempo ha pasado entre dos fechas, para calcular la fecha de dento de 3 días, etc.)

Obtener la marca de tiemp actual
  int time ( void )
Devuelve un número entero que representa la cantidad de segundos que han pasado desde el Epoch de Unix.

> <

dd>
Convertir un string en una marca de tiempo
  int strtotime ( string hora [, int ahora] )
Está pensado para el idioma inglés, por lo que hay que tener cuidado con las fechas (en ingles el 15 de febrero del 2007 es 02/15/2007). Las fechas según el estándar iso (igual que en euskara) se leen bien: 2007/02/15

> El parametro <code>ahora<

code> permite establecer fechas relativas a un tiempo. Si no se especifica se asume la fecha actual. La string hora tiene que ser una fecha relativa con la
sintaxis de fechas de GNU. Ejemplos:
  $tiempo = strtotime ("2007-02-15")
  $tiempo = strtotime ("2007/02/15")
  $tiempo = strtotime ("now"), "\n";
  $tiempo = strtotime ("10 September 2000"), "\n";
  $tiempo = strtotime ("+1 day"), "\n";
  $tiempo = strtotime ("+1 week"), "\n";
  $tiempo = strtotime ("+1 week 2 days 4 hours 2 seconds"), "\n";
  $tiempo = strtotime ("next Thursday"), "\n";
  $tiempo = strtotime ("last Monday"), "\n";

> <

dd>
Obtener la marca de tiempo de una fecha específica
  int mktime ( [int hora [, int minuto [, int segundo [, int mes [, int dia [, int año ]]]]]] )
Los parametros que no se especifiquen se obtienen del momento actual. Es muy flexible, corrige fechas automáticamente. Todos estos ejemplos dan la marca de tiempo de 1998-01-01:
  $fecha = mktime(0, 0, 0, 12, 32, 1997)
  $fecha = mktime(0, 0, 0, 13, 1, 1997)
  $fecha = mktime(0, 0, 0, 1, 1, 1998)
  $fecha = mktime(0, 0, 0, 1, 1, 98)

> <

dd>
Mostrar una marca de tiempo con una string
  string date ( string formato [, int marca_de_tiempo] )
  string strftime ( string formato [, int marca_de_tiempo] )
Las dos sirven para lo mismo. La segunda traduce el día de la semana o el nombre del mes al idioma actual (el elegido para gettext).

> La string <code>formato<

code> es una plantilla en la que se especifica cómo queremos que se muestren las fechas. En la documentación de php de la función date y de la función strftime se pueden ver los valores posibles. Ejemplos:

> <pre> $tiempo=mktime(13,25,20,3,5,2007) date("Y

m/d",$tiempo) == "2007/05/03" date("H:i:s",$tiempo) == "13:25:20" date("d-m-Y A",$tiempo) == "03/05/2007 Wednesday" strftime("d-m-Y A",$tiempo) == "03/05/2007 Jueves"

> <

dd>
Comprobar si una fecha es válida
  bool checkdate ( int mes, int dia, int año )
Devuelve True si la fecha dia-mes-año es válida. Tiene en cuenta los años bisiestos. Ejemplos:
  checkdate(12,31,1999) = True
  checkdate(2,29,2008) = True
  checkdate(2,29,2007) = False

PEAR y PECL

PHP Extension and Application Repository

PEAR es:

  • una librería structurada de código fuente abierto para usuarios de php.
  • Un sistema de distribución de código y mantenimiento de paquetes
  • Una guía de estilo estandar para código escrito en PHP
  • La PECL (PHP Extension Community Library)

El código de PEAR está dividido en paquetes. Cada paquete es un proyecto independiente, con su propio grupo de desarrollo. Los paquetes se puede instalar fácilmente mediante el instalador de PEAR. Puede haber dependencias entre paquetes.

Existe un instalador de PEAR que descarga, instala, actualiza y desinstala paquetes automáticamente. Todos los paquetes se descargan de un servidor central (pear.php.net). Existen otros repositorios de pear mantenidos por terceras personas a los que también se puede acceder mediante el instalador; a cada uno de los diferentes repositorios se le llama un canal (channel).

Para saber qué paquetes existen, leer su documentación, acceder a la página oficial de cada paquete, etc. está la web http://pear.php.net/packages.php.

PECL es un proyecto separado para distribuir extensiones de PHP (código programado en C, no en PHP). Tiene un sistema de gestión de paquetes parecido a PEAR y se puede utilizar el instalador pecl.

Para instalar PEAR, en sistemas Debian GNU/Linux:

aptitude install php-pear

> <p> <code>pear<

code> es muy sencillo de utilizar, parecido a apt-get, pero sin gestionar automáticamente las dependencias. No tiene manual, pero tiene la ayuda integrada:

$ pear help
Commands:
build                  Build an Extension From C Source
bundle                 Unpacks a Pecl Package
channel-add            Add a Channel
channel-alias          Specify an alias to a channel name
channel-delete         Remove a Channel From the List
channel-discover       Initialize a Channel from its server
channel-info           Retrieve Information on a Channel
channel-update         Update an Existing Channel
clear-cache            Clear Web Services Cache
config-create          Create a Default configuration file
config-get             Show One Setting
config-help            Show Information About Setting
config-set             Change Setting
config-show            Show All Settings
convert                Convert a package.xml 1.0 to package.xml 2.0 format
cvsdiff                Run a "cvs diff" for all files in a package
cvstag                 Set CVS Release Tag
download               Download Package
download-all           Downloads each available package from the default channel
info                   Display information about a package
install                Install Package
list                   List Installed Packages In The Default Channel
list-all               List All Packages
list-channels          List Available Channels
list-files             List Files In Installed Package
list-upgrades          List Available Upgrades
login                  Connects and authenticates to remote server
logout                 Logs out from the remote server
makerpm                Builds an RPM spec file from a PEAR package
package                Build Package
package-dependencies   Show package dependencies
package-validate       Validate Package Consistency
pickle                 Build PECL Package
remote-info            Information About Remote Packages
remote-list            List Remote Packages
run-scripts            Run Post-Install Scripts bundled with a package
run-tests              Run Regression Tests
search                 Search remote package database
shell-test             Shell Script Test
sign                   Sign a package distribution file
uninstall              Un-install Package
update-channels        Update the Channel List
upgrade                Upgrade Package
upgrade-all            Upgrade All Packages
Usage: pear [options] command [command-options] <parameters>
Type "pear help options" to list all options.
Type "pear help shortcuts" to list all command shortcuts.
Type "pear help <command>" to get the help for the specified command.

Correo

Función integrada en PHP

Para que funcione la función interna de correo de php tiene que estar configurado el servicio smtp (sendmail o uno que funcione con los mismos parámetros: exim, postfix, etc.) del servidor. Si queremos utilizar un servidor smtp externo habrá que utilizar el módulo PEAR::Mail

  bool mail ( string para,
              string asunto,
              string mensaje
              [, string cabeceras_adicionales
              [, string parametros_adicionales]] )

Devuelve true si el correo fue aceptado para enviarse. Con cabeceras_adicionales se pueden especificar cabeceras de correo; si hay más de una cabecera habrá que separarlas con CRLF ("\r\n"). Un listado de cabeceras se puede obtener de http://people.dsv.su.se/~jpalme/ietf/mail-headers/mail-headers.html

El parámetro parametros_adicionales define los parámetros que se le pasarán al comando sendmail.

Ejemplo:

  $receptores = "Ales Zabala <shagi@gisa-elkartea.org>, pisoni@gisa-elkartea.org"
  $asunto = "Queremos migrar a Debian GNU/Linux"
  $mensaje= <<FINDEMAIL
  Hola!

  Por fín nos habeís convencido. Vamos a migrar a Linux!

  En cuanto podais vamos a tomar unas cervezas y hablamos del
  tema
  FINDEMAIL
  $cabeceras="Bcc: secretaria@empresa.es\r\nRepply-To: contacto@empresa.es"
  mail($receptores,$asunto,$mensaje,$cabeceras)

> <a id="toc27" name="toc27"

>

PEAR::Mail

Para enviar correos utilizando un servidor smtp externo. Es necesario instalar los paquetes Mail y Net_SMTP

  pear install Mail
  pear install Net_SMTP

> <p> El paquete <i>Mail<

i> soporta diferentes backends para enviar correo, por lo que para utilizarlo habrá que seguir siempre dos pasos:

  1. Crear una nueva instancia del objeto Mail-backend apropiado
  2. Enviar correo con la función send() del objeto

Soporta tres backends diferentes:

mail
Utilizando la función mail() integrada en PHP
sendmail
Mediante el comando sendmail
smtp
Enviando directamente el correo a un servidor SMTP

> <h4>Creando el objeto apropiado con la factoría<

h4>

Hay que utilizar la función factory(). Hay que utilizarla estáticamente (aunque sea de dentro de un objeto, no lo instanciamos primero).

    object &factory (string $backend [, array $params = array()])

La string backend tiene que ser una entre:

  • mail
  • sendmail
  • smtp

Según el backend que utilicemos tendremos que pasarle diferentes opciones. Las opciones irán todas dentro de un array. Miraremos sólo las de smtp, por ser las más interesantes:

$params["host"]
El servidor al que nos vamos a conectar. Por defecto localhost.
$params["port"]
Puerto en el que está escuchando el servidor. Por defecto 25.
$params["auth"]
Si se utilizará autenticación smtp. Por defecto False.
$params["username"]
Usuario para conectarse al servidor.
$params["password"]
Contraseña para conectarse al servidor.
$params["localhost"]
Nombre de la máquina con la que nos vamos a presentar (parámetro del comando HELO o EHLO). Por defecto localhost.
$params["timeout"]
Timeout de la conexión SMTP.
$params["verp"]
Utilizar VERP (Variable envelope return paths) o no. Por defecto False. VERP es un sistema para poder identificar el emisor de un correo aunque este haya sido redirigido múltiples veces. Útil para listas de correo.
$params["debug"]
Modo debug. Por defecto False.
$params["persist"]
Hacer persistente la conexión SMTP entre diferentes comandos send(). Por defecto False.

> <h4>Enviando el correo<

h4>

El correo se envía con el método send() del objeto que hayamos conseguido con la factoría.

    mixed send (mixed $recipients, array $headers, string $body)

recipients es un array o un string con la lista de destinatarios separada por comas.

headers es un array asociativo de cabeceras de correo. El índice del array es el nombre de la cabecera y el contenido su valor.

body es un string con el cuerpo del mensaje.

Todo junto

    require_once('Mail.php');

    $recipients = 'shagi@gisa-elkartea.org';

    $headers['To']      = 'shagi@gisa-elkartea.org';
    $headers['From']    = 'shagi@arkham-ii.dyndns.org';
    $headers['Subject'] = 'Test message';

    $body = 'Test message';

    $params["host"] = "arkham-ii.dyndns.org";
    $params["port"] = 25;
    $params["auth"] = False;
    $params["localhost"] = "refugio";
    $params["persist"] = False;

    // Create the mail object using the Mail::factory method
    $mail_object =& Mail::factory('smtp', $params);

    $mail_object->send($recipients, $headers, $body);

> <a id="toc28" name="toc28"

>

Enviando adjuntos

Para enviar adjuntos hay que componer el cuerpo del mensaje con MIME. Mediante MIME podemos enviar diferentes tipos de contenido en un único mensaje. Para ello hay que especificar el tipo de contenido es multipart/mixedy el identificador que se utilizará para separar entre diferentes contenidos. Después cada contenido podrá tener sus propias cabeceras para especificar su tipo.

Como ejemplo, vamos a ver una función que envía varios ficheros adjuntos mediante la función mail() integrada en PHP.

  /*
  Codigo obtenido de http://www.theukwebdesigncompany.com/articles/php-file-attachments.php
  El unico parametro complicado es filename:
    $filename = array(0=>array('file'=>'/complete/path/to/myfile.jpg',
                                 'mimetype'=>'image/jpeg',
                                 'filename'=>'myfile.jpg'));
  */
  function mail_attached($to, $from, $subject,
                         $message, $filename, $headers = '') {
    $unique_sep = md5(uniqid(time()));
    $headers .= "From: $from\n";
    $headers .= "MIME-Version: 1.0\nContent-Type: multipart/mixed;boundary=\"$unique_sep\";\n";
    $headers .= "charset=\"iso-8859-1\"\nContent-Transfer-Encoding: 7bit\n\n";
    $headers .= "--$unique_sep\n";

    $headers .= "Content-Type: text/plain; charset=\"iso-8859-1\"\n";
    $headers .= "Content-Transfer-Encoding: 7bit\n\n";
    $headers .= $message."\n\n";

    if(is_array($filename)) {
      foreach($filename as $val) {
        if(file_exists($val['file'])) {
          $headers .= "--$unique_sep\n";
          $headers .= "Content-Type: {$val['mimetype']}; name=\"{$val['filename']}\"\n";
          $headers .= "Content-Transfer-Encoding: base64\n";
          $headers .= "Content-Disposition: attachment\n\n";
          $filedata = implode(file($val['file']), '');
          $headers .= chunk_split(base64_encode($filedata));
        } else {
          echo "Error: File doesn't exist.<BR>";
        }
      }
    } else {
      echo "Error: Invalid parameter passed.<BR>";
    }
    $headers .= "--$unique_sep--\n";
    if(!mail($to, $subject, $message, $headers)) {
      echo "Error: mail() function failed!<BR>";
    }
  }

> <p> Como alternativa, podemos utilizar la clase <a href="http:

/phpmailer.sourceforge.net/">PHPMailer

  require("class.phpmailer.php");

  $mail = new PHPMailer();

  $mail->IsSMTP();                                   // send via SMTP
  $mail->Host     = "smtp1.site.com;smtp2.site.com"; // SMTP servers
  $mail->SMTPAuth = true;     // turn on SMTP authentication
  $mail->Username = "jswan";  // SMTP username
  $mail->Password = "secret"; // SMTP password

  $mail->From     = "from@email.com";
  $mail->FromName = "Mailer";
  $mail->AddAddress("josh@site.com","Josh Adams"); 
  $mail->AddAddress("ellen@site.com");               // optional name
  $mail->AddReplyTo("info@site.com","Information");

  $mail->WordWrap = 50;                              // set word wrap
  $mail->AddAttachment("/var/tmp/file.tar.gz");      // attachment
  $mail->AddAttachment("/tmp/image.jpg", "new.jpg"); 
  $mail->IsHTML(true);                               // send as HTML

  $mail->Subject  =  "Here is the subject";
  $mail->Body     =  "This is the <b>HTML body</b>";
  $mail->AltBody  =  "This is the text-only body";

  if(!$mail->Send())
  {
     echo "Message was not sent <p>";
     echo "Mailer Error: " . $mail->ErrorInfo;
     exit;
  }

  echo "Message has been sent";

JpGraph

JpGraph es una librería de PHP para dibujar gráficos en 2D. Hay dos ramas de desarrollo diferentes: la 1.x está pensada para PHP4 y la 2.x para PHP5. La rama 1.x no funciona en PHP5 y viceversa.

La documentación no está disponible directamente desde la web, está incluída con el código fuente. Me he basado en la versión 2.2 de JpGraph: jpgraph-2.2/docs/html/toc.html

Está liberado con doble licencia:

  • QPL 1.0 (Licencia libre QT) para uso no-comercial y didáctico y para software libre.
  • Professional License para uso comercial.

En el apt de Debian GNU/Linux está disponible la versión para PHP4. Para instalar a mano:

  • Asegurarse de que en PHP hay soporte para GD (a ser posible la versión 2).
  • Asegurarse de que en PHP hay soporte para FreeType (1 o 2) y editar el fichero jpg-config.inc.php para especificar el directorio donde están las fuentes TTF.
  • Instalar los ficheros de jpgraph básicos: jpgraph.php y jpg-config.inc.php.
  • Instalar los ficheros que necesitemos para los tipos de gráfico que nos interesen (Por ejemplo jpgraph_line.php y jpgraph_pie.php). Lo habitual es instalar todos.

El formato por defecto de JpGraph es PNG, que es el que recomiendan por dar mejores resultados de calidad y compresión. También se pueden utilizar GIF y JPEG.

Para evitar las diferentes caches de manera sencilla recomiendan que el script devuelva la imagen directamente, y que se le pase un parametro inutil que sea diferente cada vez:

echo '<img src="myimagescript.php?dummy='.time().'">';

> <p> Está diseñado completamente mediante programación orientada a objetos. El funcionamiento básico es el siguiente: <

p>
  • Crear el gráfico
  • Especificar la escala horizontal y vertical
  • Decoracion
    • margenes
    • sombras
    • rejilla
    • título
    • imagen de fondo
    • ...
  • Crear un plot
  • Añadir el plot al gráfico
  • Renderizar el gráfico

El gráfico se puede renderizar a tres destinos diferentes:

A pantalla
El comportamiento por defecto.

> <

dd>
A un fichero
Hay que pasarle un parámetro más a la función Stroke:

> <code>$graph-&gt; Stroke( "

usr/home/peter/images/result2002.png");

> <

dd>
A una variable
Se puede obtener el resource GD del gráfico para poder hacerle un tratamiento posterior:

> <code>$handle = $graph-&gt;Stroke(_IMG_HANDLER );<

code>

> <p> Para especificar colores: <

p>
Por nombre
Hay un montón de nombres de colores: jpgraph-2.2/docs/html/482Availablenamedcolors.html#4_8_2. Se pueden conseguir variantes más claras o más oscuras del color:

> <pre> SetColor ("khaki"); SetColor ("khaki:0.5" );

/ A darker version of "khaki" SetColor("yellow:1.2"); // A slightly lighter version of "yellow"

> <

dd>
Como triplete RGB o por su valor exadecimal
    SetColor(array(65, 100,176));
    SetColor ("#A16BFF");
Con transparencia
    SetColor( "red@0.4"); // Rojo con un 40% de transparencia

Un ejemplo completo

En este ejemplo mostraremos un gráfico lineal en el que se mostrará la cantidad de gente que ha acudido a una cita por cada día del mes.

Se puede ver qué hace cada función en la referencia que biene con el código fuente jpgraph-2.2/docs/ref/index.html

  header("image/png");

  require('jpgraph/jpgraph.php');
  require('jpgraph/jpgraph_line.php');

  //Datos del grafico
  $data = array(89,72,27,65,69,6,55,44,74,39,29,34,12,100,40,62,35,28,26,5,4,71,16,66,62,45,92,74,30,38,96);
  //Fechas para mostrar en el eje X
  $fechas = array();
  $dia = mktime(0,0,0,1,1,2007); //uno de enero del 2007
  for ($x=0;$x<31;$x++) {
    $fechas[]=date("d/m/Y", $dia);
    $dia = strtotime("+1 day", $dia);
  }

  //Crear el grafico
  $graph = new Graph(600,400);

  //Especificar la escala
  $graph->SetScale("textlin",0,$max,0,0);

  //Mostrar un tick grande cada 5 valores, y uno pequeño cada unidad
  $graph->yscale->ticks->Set(5,1);
  //Mostrar Tanto el primer tick como el ultimo
  $graph->yscale->ticks->SupressFirst(False);
  $graph->yscale->ticks->SupressLast(False);

  //El margen del grafico y su color
  $graph->img->SetMargin(40,40,40,100);
  $graph->SetMarginColor('white');


  $graph->SetShadow();

  $graph->ygrid->Show(true,true);
  $graph->xgrid->Show(true,false);

  // Crear el plot lineal
  $lineplot=new LinePlot($data);

  // Añadir el plot al grafico
  $graph->Add($lineplot);

  //El titulo del grafico
  $graph->title->Set(_("Citas diarias")." - ".date(_('d/m/Y'),time()));

  //Los tipos de letra que se utilizaran
  $graph->title->SetFont(FF_FONT1,FS_BOLD);
  $graph->yaxis->title->SetFont(FF_FONT1,FS_BOLD);
  $graph->xaxis->title->SetFont(FF_FONT1,FS_BOLD);

  //El color del eje Y
  $graph->yaxis->SetColor("blue");

  //Los valores del eje X son las fechas, giradas 90 grados.
  $graph->xaxis->SetTickLabels($fechas);
  $graph->xaxis->SetLabelAngle(90);

  //mostrar el grafico en pantalla
  $graph->Stroke();

El resultado:

Un ejemplo con pasteles

En este caso veremos un ejemplo con el mínimo código necesario. El gráfico mostrará el porcentaje de imágenes subidas por persona.

header('image/png');
require('jpgraph/jpgraph.php');
require('jpgraph/jpgraph_pie.php');

//construccion de los arrays de los ejes x e y
$citas_por_persona = array('ane'=>5,'benito'=>2,'diego'=>3);
foreach ($citas_por_persona as $persona=>$numero) {
	$data[] = $numero;
	$nombres[] = "$persona - $numero";
}

//Creamos un grafico vacio
$graph = new PieGraph(600,400);

//El titulo del grafico (en este caso con fuentes de mapas de bit, no se
//pueden poner acentos)
$graph->title->Set(_("Imagenes por persona")." - ".date(_('Y/m/d'),time()));

//Creamos el plot de tipo tarta
$p1 = new PiePlot($data);

//Especificamos las etiquetas de cada porcion de la tarta
$p1->SetLegends($nombres);

//Añadirmos el plot al grafico
$graph->Add($p1);

//mostramos el grafico en pantalla
$graph->Stroke();

El resultado:

Logs

Guardar un registro de acciones de una aplicación web permite:

  • Detectar Ataques
  • Detectar Errores comunes
  • Notificación unificada de varias aplicaciones
  • Análisis forense

Si además guardamos el registro en un formato estándar podremos utilizar herramientas de análisis de logs con nuestra aplicación:

  • Obtención de informes
  • Notificación automática de eventos
  • Estadísticas de uso de los clientes

> <p> En PEAR hay un módulo pensado para guardar registros: <b>PEAR::Log<

b>. Como los gestores de logs de otros lenguajes, permite guardar los registros en diferentes medios (backends). Esto significa que utilizaremos una función simple en la aplicación ($logger->log('Mensaje', Prioridad);) que se encargará de almacenar los logs en el formato apropiado y en el destino que le digamos: un fichero, una base de datos, aviso por correo, syslog, etc.

Los registros se clasifican en prioridades. La aplicación luego se configurará para que muestre sólo los mensajes de unas prioridades determinadas. De este modo podemos tener en la aplicación notificaciones de debug (Conexión a la base de datos establecida), de información (Nuevo usuario registrado en la web), de error (No se puede conectar a la base de datos), etc. Dependieno de la configuración se mostrarán más o menos mensajes.

El uso de básico de PEAR::Log:

  • Crear un objeto de tipo Log
  • Definir las prioridades que se registrarán (opcional)
  • Registar eventos utilizando la función log() o alguna de sus alternativas

Crear un objeto de tipo Log

Las instancias de Log no se crean con el método habitual ($log = new Log(...)). Se utiliza la función singleton(). Esta función recibe como parámetro el tipo de log que queremos y nos devuelve una instancia de ese tipo. En vez de crear siempre instancias nuevas, si ya existía de antes una instancia de esas características devolverá una referencia a ella.

Recibe entre uno y cinco parámetros:

$handler
El tipo de log que se utilizará. Por ejemplo: 'console', 'file', 'syslog', etc.
$name = ''
El nombre con el que se identificará a los logs generados. Su utilidad depende del tipo de log que escojamos. Por ejemplo: 'cssgallery'
$ident = ''
String que se incluirá en todos los registros.
$conf = array()
Array en el que se le pasará la configuración necesaria para el tipo de log que hayamos elegido.
$level = PEAR_LOG_DEBUG
Prioridad mínima que se registrará con en el log.

> <p> Ejemplos: <

p>
  //guardar los registros al syslog
  $logger = &Log::singleton('syslog', LOG_LOCAL0, 'ident');

  //avisar de por correo
  $conf = array('subject' => 'Important Log Events');
  $logger = &Log::singleton('mail', 'webmaster@example.com', 'ident', $conf);

  //guardar los registros en un fichero
  $conf = array('mode' => 0600, 'timeFormat' => '%X %x');
  $logger = &Log::singleton('file', 'out.log', 'ident', $conf);

> <a id="toc34" name="toc34"

>

Definir las prioridades que se registrarán

En PEAR::Log hay definidas ocho prioridades diferentes:

PEAR_LOG_EMERG
La aplicación ha caído y es inusable.
PEAR_LOG_ALERT
Es necesaria una acción inmediata
PEAR_LOG_CRIT
Error crítico
PEAR_LOG_ERR
Error
PEAR_LOG_WARNING
Errores poco importantes (warnings)
PEAR_LOG_NOTICE
No es un error, pero es significativo
PEAR_LOG_INFO
Información
PEAR_LOG_DEBUG
Mensajes de depuración

Para especificar qué tipos de mensajes queremos registrar se puede definir una máscara de prioridades. Cada prioridad tiene su propia máscara y se pueden conbinar diferentes máscaras para obtener una que agrupe las prioridades que nos interesan. También están definidas las máscaras PEAR_LOG_ALL y PEAR_LOG_NONE para comenzar con una máscara que agrupe a todas las prioridades o a ninguna.

Primero hay que crear las máscaras necesarias con la función MASK(), luego hay que convinarlas mediante operaciones de bit a bit y finalmente hay que asignarselas al objeto Log con la función setMask():

  //avisar sólo de notificaciones y mensajes de depuracion
  $mask = Log::MASK(PEAR_LOG_NOTICE) | Log::MASK(PEAR_LOG_DEBUG);

  //avisar de todos los mensajes menos los de informacion
  $mask = Log::MASK(PEAR_LOG_ALL) ^ Log::MASK(PEAR_LOG_INFO);

  //establecer la mascara nueva
  $logger->setMask($mask);

> <p> Si lo que queremos es que se muestren sólo los mensajes de una prioridad determinada o mayor, se puede utilizar la función <code>UPTO()<

code>:

  //avisar de todos los logs de prioridad INFO o superior
  $mask = Log::UPTO(PEAR_LOG_INFO);
  $logger->setMask($mask);

> <a id="toc35" name="toc35"

>

Registrar eventos

Los eventos se notifican mediante la función log(). Esta función recibe como parámetro el mensaje (mejor como una string, pero no es necesario) y la prioridad (por defecto PEAR_LOG_INFO).

También existen funciones específicas para cada tipo de prioridad:

emerg()
Prioridad PEAR_LOG_EMERG
alert()
Prioridad PEAR_LOG_ALER
crit()
Prioridad PEAR_LOG_CRIT
err()
Prioridad PEAR_LOG_ERR
warning()
Prioridad PEAR_LOG_WARNING
notice()
Prioridad PEAR_LOG_NOTICE
info()
Prioridad PEAR_LOG_INFO
debug()
Prioridad PEAR_LOG_DEBUG

Algunos Backends

Display

Muestra los mensajes de error en el navegador.

Parámetros:

error_prepend
String que se incluirá al principio del mensaje.
error_append
String que se incluirá al final del mensaje.

Ejemplo:

    $conf = array('error_prepend' => '<div class="error"><tt>',
                  'error_append'  => '</tt></div>');
    $logger = &Log::singleton('display', '', '', $conf, PEAR_LOG_DEBUG);
    for ($i = 0; $i < 10; $i++) {
        $logger->log("Log entry $i");
    }

> <h4>File<

h4>

Almacena los mensajes en un fichero.

Parámetros:

append = True
Añadir las entradas a un fichero existente o sobreescribirlo.
mode = 0644
Permisos con los que se generará el fichero.
eol = DEPENDE DEL SO
Secuencia de carácteres para fin de línea.
lineFormat = "%1$s %2$s [%3$s] %4$s"
Formato en el que se guardará el mensaje. %1 es la fecha, %2 el $ident, %3 la prioridad y %4 el mensaje.

Ejemplo:

  $conf = array('mode' => 0600, 'timeFormat' => '%X %x');
  $logger = &Log::singleton('file', 'out.log', 'ident', $conf);
  for ($i = 0; $i < 10; $i++) {
      $logger->log("Log entry $i");
  }

> <h4>Mail<

h4>

Envia los mensajes con la función mail() de PHP.

Parámetros:

from
Valor de la cabecera From:
subject
Asunto del correo
preamble
Texto que se insertará al principio del mensaje

Ejemplo:

  $conf = array('subject' => 'Important Log Events');
  $logger = &Log::singleton('mail', 'webmaster@example.com', 'ident', $conf);
  for ($i = 0; $i < 10; $i++) {
      $logger->log("Log entry $i");
  }

> <h4>Syslog<

h4>

Envía los mensajes al syslog. Como $name hay que especificar la facility de syslog que se utilizará (generalmente LOG_LOCAL0 a //LOG_LOCAL7).

Ejemplo:

    $logger = &Log::singleton('syslog', LOG_LOCAL0, 'ident');
    for ($i = 0; $i < 10; $i++) {
        $logger->log("Log entry $i");
    }

> <h4>Composite<

h4>

Para poder enviar un mensaje a más de un tipo de log. Primero se crean los logs necesarios de la manera habitual y después se añaden al objeto Composite.

Ejemplo:

    //enviar los mensajes al navegador
    $display = &Log::singleton('display', '', 'TEST');
    //enviar los mensajes a un fichero
    $file = &Log::singleton('file', 'out.log', 'TEST');

    //crear un composite
    $composite = &Log::singleton('composite');
    //añadir los log $display y $file
    $composite->addChild($display);
    $composite->addChild($file);

    //enviar un mensaje a ambos logs
    $composite->log('This event will be logged to both handlers.');

> <a id="toc37" name="toc37"

>

Registrar los logs de PHP

  function errorHandler($code, $message, $file, $line)
  {
      global $logger;

      /* Map the PHP error to a Log priority. */
      switch ($code) {
      case E_WARNING:
      case E_USER_WARNING:
          $priority = PEAR_LOG_WARNING;
          break;
      case E_NOTICE:
      case E_USER_NOTICE:
          $priority = PEAR_LOG_NOTICE;
          break;
      case E_ERROR:
      case E_USER_ERROR:
          $priority = PEAR_LOG_ERR;
          break;
      default:
          $priotity = PEAR_LOG_INFO;
      }

      $logger->log($message . ' in ' . $file . ' at line ' . $line,
                   $priority);
  }

  set_error_handler('errorHandler');

xajax

Xajax es una librería de php que permite crear aplicaciones web que utilicen AJAX sin necesidad de saber casi nada de JavaScript, ya que xajax se encarga de generar todo el JavaScript necesario. Está licenciada con la LGPL

Características:

Más información:

Página oficial
http://www.xajaxproject.org/
Documentación
http://wiki.xajaxproject.org/
Referencia
http://wiki.xajaxproject.org/Documentation
Ejemplos
En el código fuente de xajax se incluye una carpeta examples con varios ejemplos. Más abajo veremos dos de ellos: hola mundo y signup.

Funcionamiento básico

Incluir la clase xajax y crear una instancia nueva
require_once("xajax.inc.php");
$xajax = new xajax();
Registrar la función a la que se llamará mediante xajax y escribirla
$xajax->registerFunction("miFuncion");

function miFuncion($arg)
{
    // realizar algo basado en $arg, como una consulta a una
    // base de datos, y volcar el valor a una variable como $nuevoContenido
    
    // Inicializar el objeto xajaxResponse
    $objRespuesta = new xajaxResponse();
    
    // añadir un comando a la respuesta para asignar al atributo innerHTML
    // del elemento con id="IdDeAlgunElemento" qualquier cosa que sea $nuevoContenido
    $objRespuesta ->addAssign("IdDeAlgunElemento","innerHTML", $nuevoContenido);
    //si queremos cambiar una propiedad css:
    $objRespuesta ->addAssign("IdDeAlgunElemento","style.color", "red");
    
    //devuelve la respuesta XML generada por el objeto xajaxResponse
    return $objRespuesta ;
}
Antes de que haya salida al navegado, hacer que xajax se encarge de las peticiones
$xajax->processRequests();
Llamar a la función mediante javascript
<div id="IdDeAlgunElemento">Aqui se metera la respuesta de la funcion</div>
<button onclick="xajax_miFuncion("AlgunArgumento");">

La clase xajaxResponse

La auténtica potencia de xajax está en el objeto xajaxResponse. Se encarga de escribir el código javascript necesario para gestionar la respuesta y modificar el código html. Ofrece varias funciones diferentes:

addAsign
Reemplaza el contenido de un elemento
$objResponse->addAssign("contentDiv", "innerHTML", "Some Text");
addAppend
Añade contenido a un elemento al final
$objResponse->addAppend("contentDiv", "innerHTML", "Some New Text");
addPrepend
Añade contenido a un elemento al principio
$objResponse->addPrepend("contentDiv", "innerHTML", "Some Starting Text");
addClear
Borra el contenido de un elemento
$objResponse->addClear("contentDiv", "innerHTML");
addAlert
Ejecuta el comando javascript alert()
$objResponse->addAlert("This is important information");
addRedirect
Redirige a otra web
$objResponse->addRedirect("http://www.xajaxproject.org");

> <

dd>
etc. etc. etc.
Se puede mirar la referencia de xajaxResponse en http://wiki.xajaxproject.org/Documentation:xajaxResponse.inc.php

Ejemplos

Hola mundo

<?php
// helloworld.php demonstrates a very basic xajax implementation
// using xajax version 0.1 beta4
// http://xajax.sourceforge.net

require ('../xajax.inc.php');

function helloWorld($isCaps)
{
	if ($isCaps)
		$text = "HELLO WORLD!";
	else
		$text = "Hello World!";
		
	$objResponse = new xajaxResponse();
	$objResponse->addAssign("div1","innerHTML",$text);
	
	return $objResponse;
}

function setColor($sColor)
{
	$objResponse = new xajaxResponse();
	$objResponse->addAssign("div1","style.color", $sColor);
	
	return $objResponse;
}

// Instantiate the xajax object.  No parameters defaults requestURI to this page, method to POST, and debug to off
$xajax = new xajax(); 

//$xajax->debugOn(); // Uncomment this line to turn debugging on

// Specify the PHP functions to wrap. The JavaScript wrappers will be named xajax_functionname
$xajax->registerFunction("helloWorld");
$xajax->registerFunction("setColor");

// Process any requests.  Because our requestURI is the same as our html page,
// this must be called before any headers or HTML output have been sent
$xajax->processRequests();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>xajax example</title>
	<?php $xajax->printJavascript('../'); // output the xajax javascript. This must be called between the head tags ?>
</head>
<body style="text-align:center;">
	<div id="div1" name="div1">&#160;</div>
	<br/>
	
	<button onclick="xajax_helloWorld(0)" >Click Me</button>
	<button onclick="xajax_helloWorld(1)" >CLICK ME</button>
	<select id="colorselect" name="colorselect" onchange="xajax_setColor(document.getElementById('colorselect').value);">
		<option value="black" selected="selected">Black</option>
		<option value="red">Red</option>
		<option value="green">Green</option>
		<option value="blue">Blue</option>
	</select>
	<script type="text/javascript">
	xajax_helloWorld(0); // call the helloWorld function to populate the div on load
	xajax_setColor(document.getElementById('colorselect').value); // call the setColor function on load
	</script>
</body>
</html>

Modificar un formulario mediante consulta a base de datos

<?php
//incluímos la clase ajax
require_once ('xajax/xajax.inc.php');

//instanciamos el objeto de la clase xajax
$xajax = new xajax();


$enlace = mysql_connect('localhost', 'curso', 'curso')
	  or die('No pudo conectarse : ' . mysql_error());

mysql_select_db('curso') or die('No pudo seleccionarse la BD.');


function sus_libros($entrada){
	$consulta  = "SELECT id,titulo FROM libros WHERE propietario='$entrada'";
	$resultado = mysql_query($consulta) or die('La consulta fall&oacute;: ' . mysql_error());

	$salida = "";
	while ($linea = mysql_fetch_array($resultado)) {
		$salida .= "<option value=\"{$linea[0]}\">{$linea[1]}</option>";
	}

	//instanciamos el objeto para generar la respuesta con ajax
	$respuesta = new xajaxResponse();
	//escribimos en la capa con id="respuesta" el texto que aparece en $salida
	$respuesta->addAssign("libros","innerHTML",$salida);

	//tenemos que devolver la instanciación del objeto xajaxResponse
	return $respuesta;
}

//asociamos la función creada anteriormente al objeto xajax
$xajax->registerFunction("sus_libros");

//El objeto xajax tiene que procesar cualquier petición
$xajax->processRequests();
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
  <title>Libros de cada uno</title>
<?php
  //En el <head> indicamos al objeto xajax se encargue de generar el javascript necesario
  $xajax->printJavascript("xajax/");
?>
</head>

<body>
  <form name="formulario">
    <select name="usuario" onchange="xajax_sus_libros(this.value);">
      <option value="-1">Elige una persona</option>
      <option value="1">Fulano</option>
      <option value="2">Mengano</option>
      <option value="3">Zutano</option>
    </select>
    <select name="libros" id="libros">
    </select>
  </form>

</body>
</html> 

Comprobar los valores de un formulario

signup.common.php
<?php
// signup.php, signup.common.php, signup.server.php
// demonstrate a a simple implementation of a multipage signup form
// using xajax version 0.2
// http://xajaxproject.org

require_once ("../../xajax.inc.php");

session_start();

$xajax = new xajax("signup.server.php");
$xajax->registerFunction("processForm");
?>
signup.php
<?php
// signup.php, signup.common.php, signup.server.php
// demonstrate a a simple implementation of a multipage signup form
// using xajax version 0.2
// http://xajaxproject.org

require_once('signup.common.php');
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
	<head>
		<?php $xajax->printJavascript('../../'); ?>
		<style type="text/css">
		#formWrapper{
			color: rgb(255,255,255);
			background-color: rgb(149,67,97);
			width: 200px;
		}
		#title{
			text-align: center;
			background-color: rgb(0,0,0);
		}
		#formDiv{
			padding: 25px;
		}
		.submitDiv{
			margin-top: 10px;
			text-align: center;
		}
		</style>
		<script type="text/javascript">
		function submitSignup()
		{
			xajax.$('submitButton').disabled=true;
			xajax.$('submitButton').value="please wait...";
			xajax_processForm(xajax.getFormValues("signupForm"));
			return false;
		}
		</script>
	</head>
	<body>
		<div id="formWrapper">
		
			<div id="title">Create a New Account</div>
			
			<div id="formDiv">
				<form id="signupForm" action="javascript:void(null);" onsubmit="submitSignup();">
					<div>Username:</div><div><input type="text" name="username" /></div>
					<div>Password:</div><div><input type="password" name="newPass1" /></div>
					<div>Confirm Password:</div><div><input type="password" name="newPass2" /></div>
					<div class="submitDiv"><input id="submitButton" type="submit" value="continue ->"/></div>
				</form>
			</div>
			
		</div>
		
		<div id="outputDiv">
		</div>
	</body>
</html>
signup.server.php
<?php
// signup.php, signup.common.php, signup.server.php
// demonstrate a a simple implementation of a multipage signup form
// using xajax version 0.2
// http://xajaxproject.org

require_once ("signup.common.php");

function processForm($aFormValues)
{
	if (array_key_exists("username",$aFormValues))
	{
		return processAccountData($aFormValues);
	}
	else if (array_key_exists("firstName",$aFormValues))
	{
		return processPersonalData($aFormValues);
	}
}

function processAccountData($aFormValues)
{
	$objResponse = new xajaxResponse();
	
	$bError = false;
	
	if (trim($aFormValues['username']) == "")
	{
		$objResponse->addAlert("Please enter a username.");
		$bError = true;
	}
	if (trim($aFormValues['newPass1']) == "")
	{
		$objResponse->addAlert("You may not have a blank password.");
		$bError = true;
	}
	if ($aFormValues['newPass1'] != $aFormValues['newPass2'])
	{
		$objResponse->addAlert("Passwords do not match.  Try again.");
		$bError = true;
	}

	if (!$bError)
	{
		$_SESSION = array();
		$_SESSION['newaccount']['username'] = trim($aFormValues['username']);
		$_SESSION['newaccount']['password'] = trim($aFormValues['newPass1']);
		
		$sForm = "<form id=\"signupForm\" action=\"javascript:void(null);\" onsubmit=\"submitSignup();\">";
		$sForm .="<div>First Name:</div><div><input type=\"text\" name=\"firstName\" /></div>";
		$sForm .="<div>Last Name:</div><div><input type=\"text\" name=\"lastName\" /></div>";
		$sForm .="<div>Email:</div><div><input type=\"text\" name=\"email\" /></div>";
		$sForm .="<div class=\"submitDiv\"><input id=\"submitButton\" type=\"submit\" value=\"done\"/></div>";
		$sForm .="</form>";
		$objResponse->addAssign("formDiv","innerHTML",$sForm);
		$objResponse->addAssign("formWrapper","style.backgroundColor", "rgb(67,149,97)");
		$objResponse->addAssign("outputDiv","innerHTML","\$_SESSION:<pre>".var_export($_SESSION,true)."</pre>");
	}
	else
	{
		$objResponse->addAssign("submitButton","value","continue ->");
		$objResponse->addAssign("submitButton","disabled",false);
	}
	
	return $objResponse;
}

function processPersonalData($aFormValues)
{
	$objResponse = new xajaxResponse();
	
	$bError = false;
	if (trim($aFormValues['firstName']) == "")
	{
		$objResponse->addAlert("Please enter your first name.");
		$bError = true;
	}
	if (trim($aFormValues['lastName']) == "")
	{
		$objResponse->addAlert("Please enter your last name.");
		$bError = true;
	}
	if (!eregi("^[a-zA-Z0-9]+[_a-zA-Z0-9-]*(\.[_a-z0-9-]+)*@[a-z??????0-9]+(-[a-z??????0-9]+)*(\.[a-z??????0-9-]+)*(\.[a-z]{2,4})$", $aFormValues['email']))
	{
		$objResponse->addAlert("Please enter a valid email address.");
		$bError = true;
	}

	if (!$bError)
	{
		$_SESSION['newaccount']['firstname'] = $aFormValues['firstName'];
		$_SESSION['newaccount']['lastname'] = $aFormValues['lastName'];
		$_SESSION['newaccount']['email'] = $aFormValues['email'];
		
		$objResponse->addAssign("formDiv","style.textAlign","center");
		$sForm = "Account created.<br />Thank you.";
		$objResponse->addAssign("formDiv","innerHTML",$sForm);
		$objResponse->addAssign("formWrapper","style.backgroundColor", "rgb(67,97,149)");
		$objResponse->addAssign("outputDiv","innerHTML","\$_SESSION:<pre>".var_export($_SESSION,true)."</pre>");
	}
	else
	{
		$objResponse->addAssign("submitButton","value","done");
		$objResponse->addAssign("submitButton","disabled",false);
	}
	
	return $objResponse;
}

$xajax->processRequests();
?>

Seguridad

PHP es un lenguaje de programación muy versátil: permite acceder y modificar archivos, abrir conexiones de red, acceder a bases de datos, ejecutar comandos, etc. Todas estas capacidades pueden volverse en nuestra contra en una aplicación insegura: posibilidad de mostrar el contenido de ficheros protegidos, injección de código SQL y JavaScript, utilizar nuestras máquinas como zombies, etc.

Con la configuración apropiada y con unas prácticas de programación adecuadas se pueden evitar la gran mayoría de problemas de seguridad.

Instalado como módulo de Apache

Cuando PHP está instalado como módulo de Apache, se ejecuta como el usuario de Apache (www-data en Debian GNU/Linux) por lo que obtendrá todos los permisos que tenga ese usuario. Si ese usuario puede acceder a la base de datos automáticamente (por ejemplo con bases de datos sqlite o bdb) un script de PHP podría modificar entradas en la base de datos sin necesidad de autenticarse. Para forzar la autenticación se puede utilizar la autenticación de apache (con ficheros .htaccess, por ejemplo) o incluir código de autenticación en los scripts de php.

Un usuario de Apache con demasiados permisos (por ejemplo ejecutándolo como root) pondría en riesgo todo el sistema; por ejemplo se podría ejecutar rm -rf / o shutdown -h now.

PHP tiene la opcion de configuracion open_basedir con la que podremos especificar qué directorios se pueden abrir desde php. Por defecto permite acceder a cualquier fichero.

open_basedir = /var/www/cssgallery/:/usr/share/php/

> <a id="toc44" name="toc44"

>

Seguridad de sistema de ficheros

El acceso a los ficheros se controla con la seguridad de sistema de ficheros del sitema. Esto significa que si cualquier usuario del sistema puede acceder a la impresora un scrip de PHP podría enviar miles de trabajos de impresión, que puede leer el contenido de /etc/passwd, que puede escribir en /tmp (y posiblemente llenar el disco duro...), etc.

También hay que tener en cuenta que todos los usuarios de la web tienen los mismos permisos en el sistema de ficheros (los de www-data) por lo que todos podrán acceder a los mismos ficheros.

Un posible ataque de sistema de ficheros:

// eliminar un archivo del directorio personal del usuario
$nombre_usuario = $_POST['nombre_enviado_por_el_usuario'];
$directorio     = "/home/$nombre_usuario";

$archivo_a_eliminar = "$archivo_de_usuario";

unlink ("$directorio/$archivo_de_usuario");

echo "El archivo $archivo_a_eliminar ha sido eliminado";

//Supongamos que decimos que nuestro usuario es '../etc' y nuestro fichero
//'passwd'

//O si tenemos los permisos correctamente configurados, todavía se podría poner
//como usuario a '../otrousuario' y borrar los ficheros de otra persona...

Solucion: Siempre que vayamos a construir una dirección de ficheros en un script de PHP hay que comprobar que no se utilizan carácteres peligrosos (como el .. o permitir que pongan direcciones absolutas que empiecen por /).

Seguridad de bases de datos

Hoy en día todas las aplicaciones web utilizan bases de datos. Generalmente suelen tener información a la que es necesario proibir el acceso. PHP no controla la seguridad de estas bases de datos, como es natural, por lo que tendremos que aseguranos de que la base de datos está bien configurada y de que las consultas que le hagamos no son maliciosas.

La seguridad de una base de datos comienza con su creación y sus usuarios. No es buena idea conectarse a la base de datos como administrador, ni como algún usuario que tenga control completo de la base de datos. Se pueden crear usuarios que sólo tengan permiso de lectura (sólo ejecutar select) o que sólo tengan acceso a ciertas tablas, etc.

En nuestros scripts de PHP podemos utilizar diferentes usuarios de base de datos dependiendo de lo que queramos conseguir: La parte pública de la web podría utilizar un usuario que sólo tenga acceso a las tablas públicas y además como sólo lectura, y la parte privada podría tener acceso para modificar las tablas, por ejemplo.

También es interesante utilizar los mecanismos de la base de datos: vistas, reglas, triggers, etc. De este modo, además de aprobecharnos de la seguridad de la propia base de datos podremos tener parte del camino hecho cuando nos haga falta programar otra interfaz (por ejemplo una apliación en GTK en vez de web).

La conexión a la base de datos también merece una ojeada. Si la base de datos va a estar en el mismo servidor que la aplicación web, no tiene sentido permitir conexiones de red desde la base de datos para direcciones IP diferentes de localhost. Si la base de datos estará en otro servidor, será interesante cifrar las conexiones.

El contenido de la base de datos puede ser accedido por otros métodos además de la interfaz web. Tendremos que cuidar cómo están almacenados. Por ejemplo, las contraseñas de usuarios podrían no estar almacenadas en la base de datos, guardando únicamente la salida de la función md5():

echo "password: ";
echo md5('password');
//escribe en pantalla: "password: 5f4dcc3b5aa765d61d8327deb882cf99"

$sql = "SELECT password FROM usuarios WHERE usuario = 'jon'";
$res = mysql_query($sql);
$linea = mysql_fetch_array($res);
if (linea['password'] == md5($pass)) {
  echo "has entrado";
} else {
  echo "contraseña incorrecta";
}

> <p> En la documentación de PHP se pueden ver varios ejemplos de ataques de injección de SQL: <a href="http:

/www.php.net/manual/es/security.database.sql-injection.php">http://www.php.net/manual/es/security.database.sql-injection.php

Reporte de errores y depuración

Toda la información que es útil para el desarrollador es también útil para el que quiera reventar una web. Cuando una web pase a un entorno de producción, hay que tener cuidado de limpiar la información de depuración:

  • Quitar todos los echo(), printf() etc. que hayamos puesto para depurar errores.
  • Configurar para que PHP no muestre los errores en el navegador con error_reporting(0); o con la directiva de configuración display_errors = False.
  • Desinstalar los módulos de depuración de PHP (como por ejemplo dbg).
  • Borrar todos los ficheros prueba.php, test.php, phpinfo.php, etc. que hayamos creado para comprobaciones puntuales.
  • ...

El peligro de Register Globals

La directiva register_globals estuvo activada por defecto hasta PHP 4.2.0. Ahora está desactivada por razones de seguridad.

Register Globals permitía que los datos enviados mediante un formulario estuvieran accesibles directamente como variables. Partiendo de este formulario:

<form action="test.php">
  <input type="text" name="usuario" />
</form>

El fichero test.php podía acceder a lo que el usuario hubiera especificado accediendo directamente a la variable $usuario:

<p>Hola <?php echo $usuario ?>!</p>

> <p> Como se puede ver en el siguiente ejemplo, esto puede resultar en un fallo de seguridad: <

p>
// definir $autorizado = true solo si el usuario ha sido autenticado
if (usuario_autenticado()) {
    $autorizado = true;
}

// Ya que no inicializamos $autorizado como false, esta podria estar
// definida a traves de register_globals, como en el caso de GET
// auth.php?autorizado=1

// De modo que cualquier persona podria verse como autenticada!
if ($autorizado) {
    include "/datos/muy/importantes.php";
}

> <a id="toc48" name="toc48"

>

Los datos introducidos por el usuario

No nos podemos fiar de los datos introducidos por el usuario. Incluso aunque la aplicación vaya a utilizarse en un entorno de confianza absoluta (no me lo creo, pero bueno) puede haber diferentes razones por las que las entradas del usuario son incorrectas:

  • No se ha leído lo que pone en la web y ha escrito lo que CREE que hay que poner.
  • Se ha equivocado al teclear.
  • El usuario, su mascota, su mochila, o lo que sea se ha apoyado en el teclado escribiendo texto sin sentido (como la leyenda urbana del administrador de sistemas y las tetas grandes...)
  • Un usuario está intentando entrar como si fuera otro (Pacooo, mírame a ver si tengo correo nuevoooo!)
  • ...

En general siempre que utilicemos lo que haya tecleado un usuario para realizar alguna acción (o sea, casi siempre...) habrá que validar el texto introducido para comprobar que no es perjudicial.

Tampoco está de más guardar un log con las actividades de los usuarios para cuando algo hay ido mal. Si el sistema dejó de funcionar el sábado por la noche, será interesante mirar qué hicieron los usuarios el sábado por la tarde...

Ocultando PHP

Lo primero que tenemos que tener en cuenta es que está más que demostrado que la seguridad por ocultación no garantiza nada. La información se puede filtrar de muchas maneras (desde trabajos de ingeniería inversa o ingeniería social hasta trabajadores cabreados).

Sin embargo, no cuesta mucho trabajo y puede aumentar un poco la seguridad del sistema.

Con Apache se puede ocultar el tipo de fichero de las páginas web:

# Hacer que el codigo PHP parezca como otro tipo de codigo
AddType application/x-httpd-php .asp .py .pl

# Hacer que el codigo PHP parezca como de tipos desconocidos
AddType application/x-httpd-php .bop .foo .133t

# Hacer que todo el codigo PHP luzca como HTML
AddType application/x-httpd-php .htm .html

> <p> También se puede ocultar la información que dá Apache: <

p>
# Minimize 'Server' header information
ServerTokens Prod
# Disable server signature on server generated pages
ServerSignature Off

> <a id="toc50" name="toc50"

>

Varias ideas a tener en cuenta

  • NO EXISTE la aplicación totalmente segura
  • Un exceso de controles de seguridad puede ser perjudicial: Los usuarios estarán incomodos e intentarán saltarse las protecciones, y el código se complicará demasiado.
  • Un sistema es apenas tan bueno como el eslabón más débil de una cadena.
  • Nunca hay que asumir qué es lo que van a hacer los usuarios. Un gato encima del teclado puede rellenar un formulario, un 'listo' puede intentar manipular las cookies en vez de utilizar las herramientas de configuración, algún cracker puede estar jugeteando con nuestra web, etc.

> <a id="toc51" name="toc51"

>

Bibliografia y Licencias

Manual de PHP
Enlace
http://www.php.net/docs.php
Licencia
OPL: http://www.opencontent.org/openpub/
Autores
http://www.php.net/manual/es/preface.php#contributors
JpGraph
Enlace
Documentación no disponible diréctamente en internet. Se puede obtener descargándose JpGrap desde http://www.aditus.nu/jpgraph/jpdownload.php
Licencia
Licencial de la documentación no especificada. El código fuente tiene licencia dual: QPL 1.0 para uso no-comercial, de software libre o educacional y JpGraph Professional License para uso comercial.
Autores
Aditus Consulting (http://www.aditus.nu/jpgraph/about.php)
PEAR::Log
Enlace
http://www.csh.rit.edu/~jon/projects/pear/Log/guide.html
Licencia
Licencial de la documentación no especificada. El código fuente tiene licencia PHP License
Autores
Jon Parise
PEAR::Mail
Enlace
http://pear.php.net/manual/en/package.mail.mail.php
Licencia
Licencia de la documentación no especificada. El código fuente tiene licencia PHP License / BSD
Autores
No especificados. Los del código fuente se pueden ver en http://pear.php.net/package/Mail
PHPMailer
Enlace
http://phpmailer.sourceforge.net/
Licencia
Licencial de la documentación no especificada. El código fuente tiene licencia LGPL
Autores
No especificados. Los del código fuente se pueden ver en http://phpmailer.sourceforge.net/
xajax
Enlace
http://wiki.xajaxproject.org/Main_Page
Licencia
GNU Free Documentation License 1.2
Autores
Es una wiki, no parece tener autores principales.