Cuando hablamos de programación orientada a objetos comentamos, en este artículo, que hacer un
include()
de cada clase que necesite nuestro script no es la mejor manera de incorporar clases a nuestro código. Esto se debe a una razón muy clara: la ruta de una clase puede cambiar en la organización jerárquica de nuestro diseño. Con el tiempo, los desarrollos evolucionan, y la organización jerárquica puede ampliarse o variar sustancialmente. Si tenemos una clase incorporada con include()
o require()
a nuestros scripts, es muy fácil que tengamos que cambiar las rutas de inclusión en tantos sitios que, en la práctica, eso resulte una pérdida de tiempo y una fuente de errores.
La solución es crear un proceso de autocarga (cosa que PHP nos permite hacer muy cómoda y fácilmente), de forma que todas las clases se carguen correctamente, en donde sea necesario su uso, estén dónde estén. Y, como en un proyecto bien concebido, todos los procesos son realmente clases, si necesitamos reestructurar la jerarquía de directorios no vamos a tener ningún problema en hacerlo, con un trabajo mínimo, y sin posibilidad de que se produzcan errores por esta causa. Vamos a ello.
CREANDO UNA ESTRUCTURA
Vamos a empezar partiendo de una estructura que nos sirva para ver como funciona la autocarga de clases (y de otros scripts, si a eso vamos) partiendo de un ejemplo que tenemos en uno de los artículos de programación orientada a objetos. En concreto, es el de este artículo, que usa un script principal, tres clases y una interface. Así tenemos de todo un poco para ver cómo sacarle realmente partido a la autocarga. La estructura del proyecto la vemos completa en la imagen a la derecha. Observa que, además del script principal (
index.php
) y los directorios y clases, en el directorio raíz del proyecto hay un script llamado autocarga.php
, que es el que vamos a usar para este artículo.
Como puedes ver, aparte del script autocarga.php
el resto del proyecto tiene la misma estructura de directorios y archivos que en el artículo que te he citado hace un momento. Si lo cargas en el navegador (con http://localhost/autocarga
) verás que te da unos resultados que se corresponden con el código de la aplicación, como veremos al entrar en detalles.
EL ARCHIVO DE AUTOCARGA
Tradicionalmente, para realizar la autocarga de archivos se empleaba, en PHP, la función __autoload()
. Sin embargo, según leemos en la documentación oficial, desde la varsión 5.3 del lenguaje los fabricantes están considerando declararla obsoleta, por lo que es posible que, en un futuro próximo, deje de funcionar. Por esta razón, nosotros no vamos a emplearla. En su lugar vamos a usar, como nos recomienda la documentación, la función spl_autoload_register()
. Esta función recibe, como argumento, un nombre de una función que debemos definir nosotros, y que es la que, realmente, se va a encargar de la autocarga. Esta manera de trabajar de PHP, basada en delegar en el programador la tarea de realizar las funciones necesarias a su criterio, es cada vez más habitual en PHP, y debemos acostumbrarnos a ella.
El script para la autocarga de las clases, llamado autoload.php
, es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
<?php /* Definimos dos constantes para referirnos al directorio raíz y al carácter de separación de directorios de PHP. De este modo, no es relevante cual sea la raíz de nuestro proyecto y, por otra parte, como PHP puede variar su carácter de separación de directorios, dependiendo del sistema operativo, y la versión del lenguaje, evitamos confilctos. */ define ('ROOT', dirname(__FILE__)); define ('DS', DIRECTORY_SEPARATOR); /* La función spl_autoload_register() nos permite registrar una función que se ocupará de la autocarga, y que podemos llamar como deseemos. En este ejemplo, la lhe llamado autoload, porque es un nombre relevante, indicador de lo que hace la función, pero la podría haber dado cualquier otro nombre. */ spl_autoload_register('autoload'); /* A continuación creamos la función de autocarga con el nombre que le hemos dado en spl_autoload_register(). Esta función recibe un argumento que será el nombre de la clase a cargar (realemente, el nombre del script que contenga dicha clase, sin la extensión php, que se le deberá añadir "dentro" de la función.). La operativa de esta función puede ser un poco arcana al principio, ya que es una función a la que nosotros no invocaremos nunca. Será PHP, a través del funcionamiento de spl_autoload_register(), quien se encargará de invocarla, de forma transparente a nosotros. */ function autoload($script) { /* Modificamos el nombre del script para añadirle, como prefijo, el nombre del directorio donde están todos los scripts a importar (src, en este ejemplo) y el separador de directorios. Después le añadimos el nombre del directorio raíz al principio, y la extensión .php al final. */ $script = ROOT.DS.'src'.DS.str_replace("\\", DS, $script).'.php'; /* Hacemos que la función cargue el script con la clase o contenidos que hemos creado. */ include_once ($script); } ?> |
A pesar de los comentarios incluidos en el código, es muy posible que no entiendas cómo funciona esto. De hecho, ni siquiera funciona aún, porque tenemos que añadir, aún, algunas modificaciones al resto de archivos del proyecto.
LOS NAMESPACES
Todo lo relativo a la autocarga de ficheros en un proyecto está basado en los namespaces en PHP. Puedes leer lo básico sobre esta cuestión en estos artículos, pero es en este dónde aprenderemos a sacarle partido a esta herramienta.
Los namespaces (o espacios de nombres, por la terminología en español), son contextos que se definen para indicar el ámbito dónde «vive» una clase, u otro script del proyecto. Se definen con el nombre del directorio donde está alojada esa clase, para indicárselo al archivo de autocarga. Por ejemplo, observa el script CrearTelevisorClass.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?php /* Definimos el espacio de nombres de este archivo. */ namespace classes; /* Invocamos el uso de la interface, refiriéndola por su propio espacio de nombres. */ use interfaces\interfaceTelevisor; class CrearTelevisorClass implements interfaceTelevisor { private static $color = "negro"; private $pulgadas; private $pantalla; public function __construct($color, $pulgadas, $pantalla) { $this->color = $color; $this->pulgadas = $pulgadas; $this->pantalla = $pantalla; } public static function setColor($color) { CrearTelevisorClass::$color = $color; } public function setPulgadas($pulgadas) { $this->pulgadas = $pulgadas; } public function setPantalla($pantalla) { $this->pantalla = $pantalla; } public static function getColor() { return CrearTelevisorClass::$color; } public function getPulgadas() { return $this->pulgadas; } public function getPantalla() { return $this->pantalla; } } ?> |
Hemos quitado toda la documentación relativa al funcionamiento interno de la clase, ya que esta no es relevante aquí. Observa, en cambio, las líneas resaltadas al principio. En primer lugar le indicamos al script cual es su espacio de nombres. Ponemos sólo el nombre del directorio classes
, ya que el resto de la ruta, incluido el directorio src
, es agregado en el archivo autocarga.php
.
Además, le indicamos que debe usar «lo que encuentre» en el directorio interfaces
, con el nombre interfaceTelevisor
. Observa que al nombre del script de la interface tampoco le añadimos la extensión .php
, porque también es incluida durante la autocarga. El script interfaceTelevisor.php
queda, pues, como ves a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?php /* Declaramos el espacio de nombres de este script, indicando, como tal, el nombre del directorio en el que se encuentra. */ namespace interfaces; interface interfaceTelevisor{ public static function setColor($color); public function setPulgadas($pulgadas); public function setPantalla($pantalla); public static function getColor(); public function getPulgadas(); public function getPantalla(); } ?> |
Una vez más, fíjate que le hemos indicado cuál es su espacio de nombres. En este caso,, no empleamos la sentencia use, porque este script será utilizado por el anterior, pero no hace uso, a su vez, de ningún otro.
En la clase TelevisorOperativoClass
debemos definir el espacio de nombres en el que se encuentra, y también la clase de la que hace uso para heredar de ella. El script TelevisorOperativoClass.php
queda, por tanto, así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
<?php /* Indicamos el espacio de nombres de este script. */ namespace classes; /* Le indicamos que cargue el script con la clase que necesita para heredar, refiriendo el espacio de nombres de esta, y el nombre del script, sin la extensión .php */ use classes\CrearTelevisorClass; class TelevisorOperativoClass extends CrearTelevisorClass { private $encendido; private $volumen; private $canal; public function __construct($color, $pulgadas, $pantalla, $encendido = false) { parent::__construct($color, $pulgadas, $pantalla); $this->encendido = $encendido; $this->volumen = 0; $this->canal = 1; } public function apagar() { $this->encendido = false; echo "El televisor está apagado.<br>"; } public function encender() { $this->encendido = true; echo "El televisor está encendido.<br>"; } public function subirVolumen() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede subir el volumen.<br>"; return; } if ($this->volumen == 10) { echo "El volumen ya está al máximo. No se puede subir más.<br>"; return; } $this->volumen ++; echo "El volumen está en nivel ".$this->volumen.".<br>"; } public function bajarVolumen() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede bajar el volumen.<br>"; return; } if ($this->volumen == 0) { echo "El volumen ya está al mínimo (en silencio). No se puede bajar más.<br>"; return; } $this->volumen --; echo "El volumen está en nivel ".$this->volumen.".<br>"; } public function subirCanal() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede subir el canal.<br>"; return; } if ($this->canal == 35) { $this->canal = 1; } else { $this->canal ++; } echo "El canal actual es ".$this->canal.".<br>"; } public function bajarCanal() { if ($this->encendido === false) { echo "Con el televisor apagado no se puede bajar el canal.<br>"; return; } if ($this->canal == 1) { $this->canal = 35; } else { $this->canal --; } echo "El canal actual es ".$this->canal.".<br>"; } } ?> |
Una vez más, quédate con la copla de las líneas resaltadas, que en la próxima sección de este artículo entenderás su uso.
Ahora observa el script TelevisorConVideo.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php /* Indicamos el nombre del espacio de nombres de este script, para que pueda ser llamado desde otros. */ namespace classes; /* A su vez, le indicamos que debe cargar el script TelevisorOperativoClass, para poder usarlo en este contexto. */ use classes\TelevisorOperativoClass; class TelevisorConVideoClass extends TelevisorOperativoClass { private $videoConectado; public function __construct($color, $pulgadas, $pantalla) { parent::__construct($color, $pulgadas, $pantalla); $this->videoConectado = false; } public function conectarVideo() { echo "El vídeo está conectado.<br>"; } public function desconectarVideo() { echo "El vídeo está desconectado.<br>"; } } ?> |
Y AQUÍ VIENE LA MAGIA
Aquí es donde cobra sentido todo lo que hemos hecho hasta ahora. Observa el script index.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?php /* Cargamos el autocargador que hemos preparado. */ require_once('autoload.php'); /* Le indicamos que use la clase TelevisorConVideoClass, referenciándola con una abreviación (TCV), para que el código quede más compato. */ use classes\TelevisorConVideoClass as TCV; $miTele = new TCV("negro", "42", "OLED"); echo "<pre>"; var_dump($miTele); echo "</pre>"; echo "El color original del televisor es ".TCV::getColor().".<br>"; TCV::setColor("rojo"); echo "El nuevo color del televisor es ".TCV::getColor().".<br>"; $miTele->subirVolumen(); $miTele->encender(); $miTele->subirVolumen(); $miTele->bajarCanal(); $miTele->conectarVideo(); ?> |
Observa que lo primero que hacemos es cargar el script que se encarga del autoloading de las clases. Cómo te he comentado antes, la función autoload
que hemos definido en el mismo se ejecuta de forma transparente. El argumento que recibe, en cada caso, viene definido por el uso de los distintos namespaces. Es por eso que los hemos declarado en cada clase que debe ser autocargada. Es la instrucción use
, en cada caso, la que le dice a la función de autocarga que namespace debe cargar. Y todo esto ocurre de forma transparente a nosotros (por eso hablamos de «magia»).
Prueba el script, y verás que funciona perfectamente.
REESTRUCTURANDO EL PROYECTO
Una de las ventajas de emplear este sistema (aparte de conseguir un código más limpio, legible y optimizado) es la facilidad que tenemos para reestructurar todo el proyecto, con un mínimo de cambios en el código. Imagina que ahora quieres cambiar el nombre de tu directorio src
a source
, por ejemplo. Sólo tienes que hacer dicho cambio, e ir al fichero autoload.php
. Sustituye la línea siguiente:
$script = ROOT.DS.'src'.DS.str_replace("\\", DS, $script).'.php';
por
$script = ROOT.DS.'source'.DS.str_replace("\\", DS, $script).'.php';
y ya lo tienes. Ya está todo funcionando, igual que antes, con la nueva estructura.
CONCLUYENDO
El uso de las autocargas puede asustar un poco al principio. Por una parte, el concepto de espacios de nombres es un poco etéreo, y el hecho de que haya una función que se ejecuta automáticamente, sin ser específicamente invocada, quizá lo es aún más. Sin embargo, el pequeño esfuerzo que supone aprender a pensar en este modo, se ve recompensado con creces con los beneficios que obtenemos con esta técnica. Deberías usar los scripts de este artículo (que te dejo en este enlace) para experimentar con ellos. No te importe romperlos, siempre puedes descargar los originales de nuevo. Es practicando, y rompiendo y arreglando código, como se llega a dominar la programación.