TigerZF
🌐Español

F.2. Carga de clases

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.

F.2.1. ¿Cómo puedo optimizar mi include_path?

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.

F.2.1.1. Use rutas absolutas

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().

F.2.1.2. Reduzca el número de rutas de inclusión que define

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.

F.2.1.3. Defina el include_path de Zend Framework lo antes posible

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.

F.2.1.4. Defina el directorio actual al final, o no lo defina en absoluto

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));

F.2.2. ¿Cómo puedo eliminar las sentencias require_once innecesarias?

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?

F.2.2.1. Elimine las llamadas a require_once con find y sed

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();

F.2.3. ¿Cómo puedo acelerar la carga de plugins?

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: helpers

  • Zend_Dojo: view helpers, elementos de formulario y decoradores

  • Zend_File_Transfer: adaptadores

  • Zend_Filter_Inflector: filtros (usados por el helper de acción ViewRenderer y Zend_Layout)

  • Zend_Filter_Input: filtros y validadores

  • Zend_Form: elementos, validadores, filtros, decoradores, captcha y adaptadores de transferencia de archivos

  • Zend_Paginator: adaptadores

  • Zend_View: helpers, filtros

¿Cómo puede reducir el número de dichas llamadas realizadas?

F.2.3.1. Use la caché de archivos de inclusión del PluginLoader

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.