Ver Mensaje Individual
  #1  
Antiguo 08-03-2008
Avatar de dec
dec dec is offline
Moderador
 
Registrado: dic 2004
Ubicación: Alcobendas, Madrid, España
Posts: 13.107
Reputación: 34
dec Tiene un aura espectaculardec Tiene un aura espectacular
Auto carga de clases en PHP 5

Hola,

Creo haber encontrado una forma más o menos curiosa y elegante (y no tengo abuela) de implementar la función mágica "__autoload()" de PHP 5. Quisiera compartirla con vosotros, y, por supuesto, recibir alguna idea, crítica o sugerencia. Explicaré un poco por encima lo que significa la función "__autoload()" en PHP 5. Lamentaré aburrir a quien ya sepa esto.

Anteriormente a la existencia de esta función, cuando uno quería utilizar una clase en PHP, tenía antes que requerir su "script", es decir, el archivo donde se implementara la clase. Esto, que, con dos o tres clases, puede no ser problema alguno, cuando las clases empiezan a ser algunas más, obliga a conformar "ristras" de "requires", cuyos "elementos" además tienen que guardar cierto orden.

Es decir, pongamos por caso:

Código PHP:
require('bd.class.php');
require(
'input.class.php');
require(
'request.class.php');
require(
'response.class.php');
require(
'frontend.class.php');


$frontEnd = new FrontEnd(); 
Para utilizar la clase "FrontEnd()" y, puesto que suponemos necesarias las demás, hemos tenido que ir requiriendo una a una cada clase, y esta operación había de repetirse en cada uno de los "scripts" en que fuera menester, lo que significaba aumentar la cantidad de código requerido y los posibles problemas derivados del mismo.

La función "__autoload()" viene a solucionar esta problemática. Se considera una función "mágica", porque, es el propio PHP el encargado de ejecutarla, si es que está implementada, lógicamente. En este caso, PHP ejecutará la función "__autoload()" cada vez que se requiera una clase, es decir:

Código PHP:

// Aquí se ejecutará la función "__autoload()"

$frontEnd = new FrontEnd(); 
En realidad no tengo claro que se ejecute donde digo, pero, la idea es que PHP ejecutará la función cuando vea que se precisa la clase "FrontEnd", en este caso. Y pasará a dicha función, como parámetro, el nombre de la clase que se requiere. A partir de ahí es tarea de uno requerir la clase "de verdad", para que pueda ser utilizada.

Ahora bien, aunque la función "__autoload()" es nuestra amiga y quiere ayudarnos, ya he dicho que su implementación depende uno, y que esta se puede llevar a cabo de distintas formas, atendiendo a las necesidades de cada aplicación, evidentemente. Yo quiero hablar ahora sobre la implementación de la función "__autoload()" en el gestor de bitácoras Gesbit, que algunos ya conocéis.

En Gesbit sigo la siguiente regla: todas las clases propias se encuentran en un directorio para tal efecto, y, todas las clases de librerías (o bibliotecas) de terceros, se encuentran en subdirectorios del directorio de clases "principal" mencionado. Lo de menos es porqué hago esto, aunque, claro está, la idea subyacente (siempre quise decir esto) se ve como positiva.

Lo que nos importa ahora, conociendo lo dicho, es cómo implementar la función "__autoload()" de PHP teniendo esto en cuenta. El asunto pasa por buscar la clase que PHP está requiriendo, pasada como parámetro a la función, intentar encontrar la clase que se necesita, y, de hacerlo, requerirla sin más, para que PHP pueda seguir procesando nuestro "script".

Así pues, uno se plantea algo muy similar a lo siguiente:

Código PHP:
define('GB_AUTOLOAD_CLASS_EXT''.class.php');
define('GB_AUTOLOAD_CONST_EXT''.const.php');
 
function 
__autoload($className){
  
$className strtolower($className);
  
$classFile $className.GB_AUTOLOAD_CLASS_EXT;
  
$constFile $className.GB_AUTOLOAD_CONST_EXT;
  if(
file_exists(GB_CLASSES_DIR.$classFile)){    
    require(
GB_CLASSES_DIR.$classFile);
    if(
file_exists(GB_CLASSES_DIR.$constFile)){
      require(
GB_CLASSES_DIR.$constFile);
    }
  }elseif(
file_exists(GB_KSES_DIR.$classFile)){
    require(
GB_KSES_DIR.$classFile);
  }elseif(
file_exists(GB_PHPASS_DIR.$classFile)){
    require(
GB_PHPASS_DIR.$classFile);
  }elseif(
file_exists(GB_SNOOPY_DIR.$classFile)){
    require(
GB_SNOOPY_DIR.$classFile);        
  }elseif(
file_exists(GB_PHPGETTEXT_DIR.$classFile)){
    require(
GB_PHPGETTEXT_DIR.$classFile);  
  }elseif(
file_exists(GB_IXMLRPC_DIR.$classFile)){
    require(
GB_IXMLRPC_DIR.$classFile);
  }

He quitado los comentarios al código, pero, la idea se entiende bien, creo yo: se trata de conformar el nombre del "script" de la clase, que, en este caso, tendrá siempre una determinada extensión. Como particularidad, hago notar que se busca también un archivo que contendrá "constantes" para usarse en la clase a requerir, y, si este existe, se incluye también.

Pero, fíjate lo que ocurre con las clases de terceros. Al estar cada una de ellas en directorios diferentes, en el código anterior se trataba de encontrar el "script" de la clase en cuestión en todos los directorios posibles. Si se encontraba en alguno de ellos, ya no se miraba más allá, se requería la clase, y aquí paz y después gloria. Pero tiene un inconveniente, al menos.

Tal y como lo ves, no cambiaba el código por los "require" de PHP 4, es decir, creo que la idea subyacente (otra vez) no está mal, y que la implementación de la función "__autoload()" puede servir, en un primer momento. De hecho ha servido para Gesbit durante los últimos meses. Ahora bien, ¿qué pasa cuando se han de añadir más clases de terceros?

Efectivamente, que la función "__autoload()" crecerá y crecerá, conforme se vayan añadiendo más clases de terceros, cada una de ellas en sus propios directorios. Pues bien. Hoy me volví a plantear el problema, y, aunque no di en principio con una solución, creo que al cabo sí que he conseguido algo que, además de que mantiene el rendimiento del "script" o incluso lo mejora, creo que "escala" mejor.

El código en cuestión, es este:

Código PHP:
$libDirs = array(
  
GB_KSES_DIR,
  
GB_PHPASS_DIR,
  
GB_SNOOPY_DIR,
  
GB_IXMLRPC_DIR,
  
GB_CLASSES_DIR,
  
GB_PHPGETTEXT_DIR
);
 
function 
__autoload($className){
  
$className strtolower($className);
  
$classFile $className.'.class.php';
  
$constFile $className.'.const.php';
  foreach(
$GLOBALS['libDirs'] as $dir){
    if(
file_exists($dir.$classFile)){    
      require(
$dir.$classFile);
      if(
file_exists($dir.$constFile)){
        require(
$dir.$constFile);
      }
      break;
    }
  }

Es decir, primero preparamos en un "array" todos los directorios de todas las librerías (o bibliotecas) de terceros, este "array" será el único al que haya que añadir elementos, según se necesiten más clases. Y, por su parte, la función "__autoload()", hace exactamente lo mismo de antes (sigue requiriendo el archivo de "constantes", que, en realidad, es propio de Gesbit, quiero decir, tú podrías muy bien no necesitarlo) y hace lo que se espera de ella exactamente igual o mejor, como he dicho.

En fin. Una vez que he terminado de escribir esto me parece un poco tonto publicarlo aquí. Puede que incluso lo haga en mi bitácora también. Digo que me parece un poco tonto, porque, visto en perspectiva, la cosa parece bastante clara. Es como si vieras este último código y dijeras, ¡pues claro! ¿Es que no lo hacías así? Pero, evidentemente, no lo hacía así. He mostrado (en el primer bloque de código) cómo lo hacía.

Y no sólo eso, sino que podría enlazar a una entrada en la bitácora de Gesbit donde, precisamente, llegué a plantearme una solución mucho más drástica y de la que me hubiera arrepentido: meter todas las clases en un mismo directorio... con el consiguiente lío, puesto que algunas librerías (o bibliotecas) de terceros incorporan más de una clase, y otras pueden incorporar también otros archivos, etc.

En definitiva, que, aunque tal vez ahora me parece evidente, quizás alguien pueda encontrar de utilidad este texto. Yo así lo espero.
__________________
David Esperalta
www.decsoftutils.com

Última edición por dec fecha: 08-03-2008 a las 05:06:15.
Responder Con Cita