Drupal 7, hooks, y rendimiento de un sitio hecho en Drupal

Solemos decir que Drupal es lento cuando tiene muchos módulos, hablando de forma coloquial "a Drupal le pesa el culo". ¿Pero os habéis parado a mirar porque ocurre esto?

He llegado a este ejercicio por mi relación amor/odio con el módulo Views, y la búsqueda de forma de optimizar Drupal.

Pero vamos por partes, primero tenemos que saber que es un hook.

Drupal's module system is based on the concept of "hooks". A hook is a PHP function that is named foo_bar(), where "foo" is the name of the module (whose filename is thus foo.module) and "bar" is the name of the hook. Each hook has a defined set of parameters and a specified result type.

To extend Drupal, a module need simply implement a hook. When Drupal wishes to allow intervention from modules, it determines which modules implement a hook and calls that hook in all enabled modules that implement it.
Fuente

Los hooks vendrían a ser una suerte de funcionalidades que se pueden extender mediante hooks adicionales, la lógica de los hooks recuerda a la herencia y al polimorfismo de los objetos. No estoy diciendo que los hooks sean objetos, más bien sería una aproximación desde el punto de vista de la programación funcional a las características de herencia y polimorfismo de los objetos. En los hooks de Drupal tenemos acceso a todas las variables que "maneja" el hook y la posibilidad de modificarlas.

Bien, teóricamente ya sabemos que son los hooks, el siguiente paso es saber como Drupal descubre cuando un módulo tiene un hook y como ejecutarlo.

Drupal hace toda su magia a partir de la función:

<?php
function module_invoke_all($hook) {
 
$args = func_get_args();
 
// Remove $hook from the arguments.
 
unset($args[0]);
 
$return = array();
  foreach (
module_implements($hook) as $module) {
   
$function = $module . '_' . $hook;
    if (
function_exists($function)) {
     
$result = call_user_func_array($function, $args);
      if (isset(
$result) && is_array($result)) {
       
$return = array_merge_recursive($return, $result);
      }
      elseif (isset(
$result)) {
       
$return[] = $result;
      }
    }
  }

  return $return;
}
?>

La función module_invoke_all() a la función:

<?php
function module_implements($hook, $sort = FALSE, $reset = FALSE);
?>

La función module_implements() Comprueba si tiene en memoria el hook en cuestión, y por ende el listado de módulos que lo implementan.

Si no tiene en memoria el listado de hooks Drupal recorre el listado de módulos instalados en el site para comprobar que existe el hook en el módulo, y guardarlo en memoria.

<?php
// Este código es parte de la función moudle_implements, la función es mucho más larga.
if (!isset($implementations[$hook])) {
   
// The hook is not cached, so ensure that whether or not it has
    // implementations, that the cache is updated at the end of the request.
   
$implementations['#write_cache'] = TRUE;
   
// Discover implementations for this hook.
   
$hook_info = module_hook_info();
   
$implementations[$hook] = array();
   
$list = module_list(FALSE, FALSE, $sort);
    foreach (
$list as $module) {
     
$include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
     
// Since module_hook() may needlessly try to load the include file again,
      // function_exists() is used directly here.
     
if (function_exists($module . '_' . $hook)) {
       
$implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
      }
    }
?>

Aunque no lleguemos tan abajo, y tengamos en memoria los hooks, recuerda que la función module_invoke_all() tiene un foreach que recorre todos los módulos para comprobar la implementación de dicho hook, y recuerda también que se usan muchos hooks en Drupal.

Y todo esto es por una parte para hacernos reflexionar sobre lo mucho o poco que le cuesta a Drupal crear una página. Es cierto que gracias a Opcache y/O APC los tiempos de ejecución son mucho más rápidos, pero hay que ser cuidadoso.

Todo esto viene por una barrabasada que estoy rumiando y viendo como resolver. Mi idea es montar un sistema de MVC en Drupal para aquellas paginas que se suelen montar con views. La idea es tan sencilla como usar EFQ dentro de traits para los Models, y el sistema de theming que viene por defecto en Drupal para las Vistas, únicamente teniendo una sobrecarga en el controlador, ya que por un lado se utilizaría el hook_menu de drupal, y por otro lado habría que crear un controlador usando un patrón tipo factoría y un lazy loader para poder cargar las clases necesarias.

De esta manera, se reduce algunos hooks, y por otro lado tienes el código en archivos e vez de en BBDD, el siguiente post será con pruebas de carga para ver que es más rápido si el módulo Views o el Frankenstein que me estoy montando en mi cabeza.

Oskar