Cualquiera que alguna vez haya realizado un análisis de perfil de una aplicación de Zend
Framework reconocerá de inmediato que la carga de clases es relativamente costosa en Zend
Framework. Entre la enorme cantidad de archivos de clase que necesitan ser
cargados para muchos componentes, y el uso de plugins que no tienen una relación 1:1
entre el nombre de su clase y el sistema de archivos, las diversas
llamadas a include_once() y
require_once() pueden resultar problemáticas. Este capítulo pretende ofrecer
algunas soluciones concretas a estos problemas.
Una optimización trivial que puede realizar para aumentar la velocidad de la carga de clases es prestar mucha atención a su include_path. En particular, debe hacer cuatro cosas: usar rutas absolutas (o rutas relativas a rutas absolutas), reducir el número de rutas de inclusión que define, colocar el include_path de Zend Framework lo antes posible, e incluir la ruta del directorio actual solo al final de su include_path.
Aunque esto pueda parecer una microoptimización, el hecho es que si no lo hace, obtendrá muy poco beneficio de la caché realpath de PHP, y como resultado, la caché de opcodes no rendirá tan bien como podría esperar.
Hay dos formas sencillas de garantizarlo. Primero, puede codificar
las rutas directamente en su php.ini, httpd.conf,
o .htaccess. Segundo,
puede usar la función realpath() de PHP al
establecer su include_path:
$paths = array(
realpath(dirname(__FILE__) . '/../library'),
'.',
);
set_include_path(implode(PATH_SEPARATOR, $paths);
Puede usar rutas relativas -- siempre que sean relativas a una ruta absoluta:
define('APPLICATION_PATH', realpath(dirname(__FILE__)));
$paths = array(
APPLICATION_PATH . '/../library'),
'.',
);
set_include_path(implode(PATH_SEPARATOR, $paths);
Sin embargo, aun así, generalmente es una tarea trivial simplemente pasar
la ruta a realpath().
Las rutas de inclusión se examinan en el orden en que aparecen en el include_path. Obviamente, esto significa que obtendrá un resultado más rápido si el archivo se encuentra en la primera exploración en lugar de en la última. Por tanto, una mejora bastante obvia es simplemente reducir el número de rutas en su include_path a solo lo que necesita. Revise cada include_path que haya definido y determine si realmente tiene alguna funcionalidad en esa ruta que se use en su aplicación; si no, elimínela.
Otra optimización es combinar rutas. Por ejemplo, Zend Framework sigue las convenciones de nomenclatura de PEAR; por tanto, si usa bibliotecas PEAR (o bibliotecas de otro framework o biblioteca de componentes que siga el estándar de codificación de PEAR), intente colocar todas estas bibliotecas en el mismo include_path. Esto a menudo puede lograrse con algo tan simple como crear un enlace simbólico de una o más bibliotecas en un directorio común.
Continuando con la sugerencia anterior, otra optimización obvia es definir el include_path de Zend Framework lo antes posible en su include_path. En la mayoría de los casos, debería ser la primera ruta de la lista. Esto garantiza que los archivos incluidos desde Zend Framework se encuentren en la primera exploración.
La mayoría de los ejemplos de include_path muestran el uso del directorio actual, o '.'. Esto resulta conveniente para garantizar que los scripts en el mismo directorio que el archivo que los requiere puedan cargarse. Sin embargo, estos mismos ejemplos suelen mostrar este elemento de ruta como el primer elemento en el include_path -- lo que significa que el árbol de directorios actual siempre se explora primero. En la mayoría de los casos, con aplicaciones de Zend Framework, esto no es deseable, y la ruta puede colocarse de forma segura como el último elemento de la lista.
Ejemplo F.1. Ejemplo: include_path optimizado
Juntemos todas estas sugerencias. Nuestra suposición será que
está usando una o más bibliotecas PEAR junto con
Zend Framework -- quizás las bibliotecas PHPUnit y Archive_Tar
-- y que ocasionalmente necesita incluir
archivos relativos al archivo actual.
Primero, crearemos un directorio library en nuestro proyecto. Dentro de ese
directorio, enlazaremos simbólicamente el directorio library/Zend
de nuestro Zend Framework, así como los directorios necesarios de nuestra instalación de
PEAR:
library
Archive/
PEAR/
PHPUnit/
Zend/
Esto nos permite añadir nuestro propio código de biblioteca si es necesario, manteniendo intactas las bibliotecas compartidas.
A continuación, optaremos por crear nuestro include_path de forma programática
dentro de nuestro archivo public/index.php. Esto nos permite mover
nuestro código en el sistema de archivos, sin necesidad de editar el
include_path cada vez.
Tomaremos ideas de cada una de las sugerencias anteriores: usaremos
rutas absolutas, determinadas mediante realpath();
incluiremos la ruta de inclusión de Zend Framework al principio; ya
hemos consolidado los include_paths; y colocaremos el directorio
actual como la última ruta. De hecho, lo estamos haciendo muy bien
aquí -- terminaremos con solo dos rutas.
$paths = array(
realpath(dirname(__FILE__) . '/../library'),
'.'
);
set_include_path(implode(PATH_SEPARATOR, $paths));
La carga diferida (lazy loading) es una técnica de optimización diseñada para postergar la operación costosa de cargar un archivo de clase hasta el último momento posible -- es decir, cuando se instancia un objeto de esa clase, se llama a un método estático de clase, o se referencia una constante de clase o una propiedad estática. PHP soporta esto mediante autoloading, lo que le permite definir una o más funciones de retorno (callbacks) para ejecutar con el fin de asignar un nombre de clase a un archivo.
Sin embargo, la mayoría de los beneficios que puede obtener del autoloading se anulan si
el código de su biblioteca sigue realizando llamadas a require_once() --
que es precisamente el caso de Zend Framework. Entonces, la pregunta es: ¿cómo
puede eliminar esas llamadas a require_once() para maximizar
el rendimiento del autoloader?
Una forma sencilla de eliminar las llamadas a require_once() es usar
las utilidades de UNIX 'find' y 'sed' en conjunto para comentar
cada llamada. Intente ejecutar las siguientes sentencias (donde '%'
indica el prompt del shell):
% cd path/to/ZendFramework/library % find . -name '*.php' -not -wholename '*/Loader/Autoloader.php' \ -not -wholename '*/Application.php' -print0 | \ xargs -0 sed --regexp-extended --in-place 's/(require_once)/\/\/ \1/g'
Esta línea de comandos (dividida en dos líneas para facilitar la lectura) recorre
cada archivo PHP y le indica que reemplace cada instancia de
'require_once' por '// require_once', comentando así
efectivamente cada una de esas sentencias. (Conserva selectivamente
las llamadas a require_once() dentro de
Zend_Application y
Zend_Loader_Autoloader, ya que estas clases fallarán sin
ellas.)
Este comando podría añadirse trivialmente a un proceso de compilación o
lanzamiento automatizado, ayudando a mejorar el rendimiento en su aplicación
de producción. Sin embargo, debe tenerse en cuenta que, si usa esta
técnica, debe utilizar autoloading;
puede hacerlo desde su archivo "public/index.php" con el
siguiente código:
require_once 'Zend/Loader/Autoloader.php'; Zend_Loader_Autoloader::getInstance();
Muchos componentes tienen plugins, que le permiten crear sus propias clases para usar con el componente, así como sobrescribir los plugins estándar existentes incluidos con Zend Framework. Esto proporciona una flexibilidad importante al framework, pero a un precio: la carga de plugins es una tarea bastante costosa.
El cargador de plugins le permite registrar pares de prefijo de clase / ruta, permitiéndole especificar archivos de clase en rutas no estándar. Cada prefijo puede tener múltiples rutas asociadas. Internamente, el cargador de plugins recorre cada prefijo, y luego cada ruta asociada a él, comprobando si el archivo existe y es legible en esa ruta. Luego lo carga y comprueba que la clase que busca esté disponible. Como puede imaginar, esto puede provocar muchas llamadas stat al sistema de archivos.
Multiplique esto por el número de componentes que usan el PluginLoader, y tendrá una idea del alcance de este problema. En el momento de escribir esto, los siguientes componentes hacían uso del PluginLoader:
Zend_Controller_Action_HelperBroker: helpersZend_Dojo: view helpers, elementos de formulario y decoradoresZend_File_Transfer: adaptadoresZend_Filter_Inflector: filtros (usados por el helper de acción ViewRenderer yZend_Layout)Zend_Filter_Input: filtros y validadoresZend_Form: elementos, validadores, filtros, decoradores, captcha y adaptadores de transferencia de archivosZend_Paginator: adaptadoresZend_View: helpers, filtros
¿Cómo puede reducir el número de dichas llamadas realizadas?
Zend Framework 1.7.0 añade una caché de archivos de inclusión al
PluginLoader. Esta funcionalidad escribe llamadas a "include_once()"
en un archivo, que luego puede incluir en su bootstrap. Aunque esto
introduce llamadas adicionales a include_once() en su código, también
garantiza que el PluginLoader retorne lo antes posible.
La documentación del PluginLoader incluye un ejemplo completo de su uso.