Una aplicación web consiste en muchas clases PHP y cada clase generalmente reside en un archivo separado. Esto crea la necesidad de incluir los archivos.
Por ejemplo, vamos a asumir que tenemos el archivo llamado Application.php que
contiene la definición para la clase \Zend\Mvc\Application
de la sección anterior.
Antes de que podamos crear una instancia de la clase Application
en cualquier lugar
de nuestro código debemos incluir el contenido del archivo Application.php (podemos
hacer esto con la ayuda de la sentencia require_once
, pasándole la ruta completa
del archivo):
<?php
require_once "/path/to/zendframework/zend-mvc/src/Application.php";
use Zend\Mvc\Application;
$application = new Application();
Como la aplicación crece en tamaña puede ser difícil incluir cada nuevo archivo. Zend Framework 3 consiste en cientos de archivos y puede ser muy difícil cargar la biblioteca entera y todas sus dependencias. Además, cuando se ejecuta el código resultante el interprete PHP consumirá tiempo de CPU para procesar cada archivo incluido, aun cuando no se crea una instancia de su clase.
Para corregir este problema se introdujo en PHP una característica que permite la
carga automática de clases (class autoloading).
La función de PHP spl_autoload_register()
permite registrar una función autoloader.
En sitios webs complejos podemos crear varias funciones autoloader que se
encadenan en una pila.
Durante la ejecución de un script si el interprete de PHP encuentra el nombre de una clase que no se ha definido aún, se llama una por una a todas las funciones autoloader registradas hasta encontrar a la función autoloader que tiene la clase o hasta que se alcanza un error «not fount». Esto permite una lazy loading, el interprete PHP procesa la definición de la clase solo en el momento de su invocación, cuando es realmente necesario.
Para dar una idea de como es una función autoloader, mostraremos una implementación simplificada de una función autoloader:
<?php
// Autoloader function.
function autoloadFunc($className)
{
// Class map static array.
static $classMap = [
'\\Zend\\Mvc\\Application' => '/path/to/zendframework/zend-mvc/src/Zend/Mvc/Application.php',
'\\Application\\Module' => '/path/to/app/dir/module/Application/Module.php',
//...
];
// Check if such a class name presents in the class map.
if(isset(static::$classMap[$className])) {
$fileName = static::$classMap[$className];
// Check if file exists and is readable.
if (is_readable($fileName)) {
// Include the file.
require $fileName;
}
}
}
// Register our autoloader function.
spl_autoload_register("autoloadFunc");
En el ejemplo de arriba definimos la función autoloader autoloadFunc()
, que
llamaremos de ahora en adelante clase map autoloader.
La clase map autoloader usa la clase map para hacer una correspondencia entre el nombre
de la clase y la ruta absoluta al archivo PHP que contiene la clase. La clase map
es un típico arreglo de PHP que contiene llaves y valores. Para determinar la ruta
del archivo para cada nombre de clase, la clase map autoloader necesita traer el valor
desde el arreglo $classMap
. Es obvio que la clase map autoloader trabaja muy rápido.
Sin embargo, su desventaja es que tenemos que mantener a la clase map actualizada, lo que
es necesario cada vez que agregamos una nueva clase a nuestro programa.
A causa de que cada biblioteca usa su propia nomenclatura para el código y sus propias convenciones para la organización de los archivos, tendremos que registrar una función autoloader diferente y personalizada por cada biblioteca que usemos, lo que es bastante molesto (y de hecho un trabajo innecesario). Para resolver este problema, el estándar PSR-4 fue introducido.
PSR significa en ingles «PHP Standards Recommendation».
El estándar PSR-4 define la estructura del código recomendada que una aplicación o un biblioteca debe seguir para garantizar la interoperabilidad entre autoloaders. En dos palabras el estándar dice que:
Los namespaces de clases deben ser organizados de la siguiente manera:
\<Vendor Name>\(<Namespace>)*\<Class Name>
Los namespaces pueden anidar tantos niveles como se desee pero el Vendor Name debe ser el namespace de primer nivel.
Los namespace deben corresponderse con la estructura de directorios. Cada separador
de namespace ('\') es convertido al separador de directorio especifico de cada
sistema operativo, usando la constante DIRECTORY_SEPARATOR
, cuando se carga
desde el sistema de archivos.
Al nombre de la clase se agrega el sufijo de extensión .php cuando se carga el archivo desde el sistema de archivos.
Por ejemplo, para la clase Zend\Mvc\Application
tendremos la siguiente estructura
de directorios:
/path/to/zendframework/zend-mvc/src
/Zend
/Mvc
Application.php
La desventaja de esto es que es necesario colocar el código dentro de varios directorios anidados (Zend y Mvc).
Para corregir esto PSR-4 permite que definamos una serie continua de uno o más namespace
o sub-namespaces iniciales que correspondan a una «carpeta base». Por ejemplo, si tenemos
el nombre de clase fully qualified: Zend\Mvc\Application
, y definimos que la serie
Zend\Mvc
corresponde a la carpeta "/path/to/zendframework/zend-mvc/src" podemos
organizar los archivos de la siguiente manera:
/path/to/zendframework/zend-mvc/src
Application.php
Para que el código esté conforme al estándar PSR-4, podemos escribir y registrar un autoloader que llamaremos autoloader «standard»:
<?php
// "Standard" autoloader function.
function standardAutoloadFunc($className)
{
// Replace the namespace prefix with base directory.
$prefix = '\\Zend\\Mvc';
$baseDir = '/path/to/zendframework/zend-mvc/src/';
if (substr($className, 0, strlen($prefix)) == $prefix) {
$className = substr($className, strlen($prefix)+1);
$className = $baseDir . $className;
}
// Replace namespace separators in class name with directory separators.
$className = str_replace('\\', DIRECTORY_SEPARATOR, $className);
// Add the .php extension.
$fileName = $className . ".php";
// Check if file exists and is readable.
if (is_readable($fileName)) {
// Include the file.
require $fileName;
}
}
// Register the autoloader function.
spl_autoload_register("standardAutoloadFunc");
El autoloader estándar funciona de la siguiente manera. Asumiendo que el namespace
de la clase se corresponde uno-a-uno a la estructura de directorios, la función
calcula la ruta hasta el archivo PHP transformando las barras invertidas (separadores
de namesapaces) a barras (separadores de ruta) y concatena la ruta resultante a la ruta
absoluta del directorio donde la biblioteca está ubicada. Luego, la función revisa
si el archivo PHP existe y si es así incluye el archivo usando la sentencia require
.
Es obvio que el autoloader estándar funciona más lento que la clase map autoloader. Sin embargo, su ventaja es que no necesitamos mantener ninguna clase map, lo que es muy conveniente cuando desarrollamos código nuevo y agregamos nuevas clases a la aplicación.
Zend Framework 3 se ajusta al estándar PSR-4, haciendo posible usar el mecanismo de autoloading estándar con todos sus componentes. Es también compatible con otras bibliotecas que se ajustan a PSR-4 como Doctrine o Symfony.
Composer puede generar las funciones de autoloader (tanto la clase map autoloaders
como el estándar de autoloaders PSR-4) para el código que se instala con él.
Zend Framework 3 usa la implementación que hace Composer del «autoloader». Cuando
se instala un paquete usando Composer se crea automáticamente el archivo APP_DIR/vendor/autoload.php,
que usa la función de PHP spl_autoload_register()
para registrar un autoloader.
De esta manera todas las clases de PHP ubicadas en el directorio APP_DIR/vendor
se cargan correctamente.
Para autocargar las clases de PHP ubicadas en nuestro propios módulos (por ejemplo, el
módulo Application
) tendremos que especificar la llave autoload
en nuestro archivo
composer.json
.
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/"
}
},
Luego la única cosa que necesitamos hacer es incluir este archivo en nuestro script
de entrada index.php
:
// Composer autoloading
include __DIR__ . '/../vendor/autoload.php';
El archivo autoload.php se genera cada vez que se instala un paquete usando Composer. Además, podemos ejecutar el comando
dump-autoload
para que Composer genere el archivo autoload.php.
php composer.phar dump-autoload
En la aplicación Zend Skeleton podemos ver como el estándar PSR-4 se aplica en la
práctica. Para el módulo por defecto de nuestro sitio web, el módulo Application
,
las clases PHP que se registran en el autoloader estándar se guardan en el directorio
APP_DIR/module/Application/src
(«src» es la abreviatura de «source»).
Llamaremos a la carpeta
src
: carpeta fuente del módulo.
Por ejemplo, vamos a ver el archivo IndexController.php
del módulo Application
(figura 3.2).
Como podemos ver este contiene la clase 1 IndexController
que pertenece
al namespace Application\Controller
. Para seguir el estándar PSR-4 y usar el autoloader
estándar con esta clase PHP tenemos que colocarla dentro de la carpeta Controller
que está
dentro del directorio fuente del módulo.
1) La clase IndexController
es el controlador por defecto para la aplicación
skeleton. Hablaremos luego sobre los controladores en el capítulo
Model-View-Controller.