You are here

7 不修改Drupal核心代码

admin 的头像
Submitted by admin on 星期五, 2015-09-18 06:03

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们在前面通过修改Drupal核心代码,实现了在主题层也能覆写breadcrumb.tpl.php这个模版文件。在实现这个功能以后,我就想到了,其实不修改核心代码,也能够在我们的模块中实现这个功能。就是说,我们把_theme_build_registry里面的基主题、主题引擎、主题的_theme_process_registry的调用,在breadcrumb2_theme_registry_alter里面,重新调用执行一遍就可以了。

开始的时候,我是这样写的代码:

function breadcrumb2_theme_registry_alter(&$theme_registry) {

  if (isset($theme_registry['breadcrumb'])) {

    $path = drupal_get_path('module', 'breadcrumb2');

//$path = path_to_theme();

    $theme_registry['breadcrumb']['path'] = $path;

$theme_registry['breadcrumb']['theme path'] = $path;

    $theme_registry['breadcrumb']['template'] = 'breadcrumb';

    $theme_registry['breadcrumb']['function'] = NULL;

unset($theme_registry['breadcrumb']['function']);

  }

  $themes = list_themes();

  foreach ($themes as $theme) {

    _theme_process_registry($theme_registry, $theme->name, 'theme', $theme->name, dirname($theme->filename));

//print debug($theme);

  }

}

我当时这样想的,把所有启用的主题,分别重新注册一遍,这样它们在注册的时候,就可以覆写我们的模版文件了。就是把所有主题的_theme_process_registry,放到后面,重新执行一遍。

但是很遗憾,还是不行。当时,我就有了一个疑问,什么疑问呢?就是说,在_theme_build_registry里面,这句话是这样写的:

  // Finally, hooks provided by the theme itself.

  _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));

 

    这里面,没有把所有的主题都列出来,分别处理一下,而是处理了一个。我当时特别想知道,这个$theme是从哪里来的,我能不能在breadcrumb2_theme_registry_alter里面直接调用这个变量?很遗憾,在breadcrumb2_theme_registry_alter,是没有将这个变量传递过来的。这个时候,对代码,进行了分析。这里的$theme是从函数的参数里面传递过来的:

function _theme_build_registry($theme, $base_theme, $theme_engine) 

    

都有哪些函数,调用了函数_theme_build_registry,我通过搜索查找,这个函数的调用,只出现在了_theme_load_registry函数里面:

function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) {

  if ($complete) {

    // Check the theme registry cache; if it exists, use it.

    $cached = cache_get("theme_registry:$theme->name");

    if (isset($cached->data)) {

      $registry = $cached->data;

    }

    else {

      // If not, build one and cache it.

      $registry = _theme_build_registry($theme, $base_theme, $theme_engine);

      // Only persist this registry if all modules are loaded. This assures a

      // complete set of theme hooks.

      if (module_load_all(NULL)) {

        _theme_save_registry($theme, $registry);

      }

    }

    return $registry;

  }

  else {

    return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache');

  }

}

    而这个$theme,也来自于_theme_load_registry函数本身的参数传递。通过搜索查找,我在theme.inc文件里面,找到两个地方,包含这个函数:

function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') {

还有一处是:

function theme($hook, $variables = array()) {

  // If called before all modules are loaded, we do not necessarily have a full

  // theme registry to work with, and therefore cannot process the theme

  // request properly. See also _theme_load_registry().

  if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) {

    throw new Exception(t('theme() may not be called until all modules are loaded.'));

  }

    通过阅读_drupal_theme_initialize,我们知道这个函数最终调用了_theme_load_registry,而$theme也是从这个函数的参数里面传递过来的。

function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') {

  global $theme_info, $base_theme_info, $theme_engine, $theme_path;

  $theme_info = $theme;

  $base_theme_info = $base_theme;

 

  $theme_path = dirname($theme->filename);

……

  if (isset($registry_callback)) {

    _theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine));

  }

}

_drupal_theme_initialize里面的$theme,则是从drupal_theme_initialize传递过来的 。drupal_theme_initialize函数本身没有参数,

function drupal_theme_initialize() {

  global $theme, $user, $theme_key;

 

  // If $theme is already set, assume the others are set, too, and do nothing

  if (isset($theme)) {

    return;

  }

 

  drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE);

  $themes = list_themes();

 

  // Only select the user selected theme if it is available in the

  // list of themes that can be accessed.

  $theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : variable_get('theme_default', 'bartik');

 

  // Allow modules to override the theme. Validation has already been performed

  // inside menu_get_custom_theme(), so we do not need to check it again here.

  $custom_theme = menu_get_custom_theme();

  $theme = !empty($custom_theme) ? $custom_theme : $theme;

 

  // Store the identifier for retrieving theme settings with.

  $theme_key = $theme;

 

  // Find all our ancestor themes and put them in an array.

  $base_theme = array();

  $ancestor = $theme;

  while ($ancestor && isset($themes[$ancestor]->base_theme)) {

    $ancestor = $themes[$ancestor]->base_theme;

    $base_theme[] = $themes[$ancestor];

  }

  _drupal_theme_initialize($themes[$theme], array_reverse($base_theme));

 

  // Themes can have alter functions, so reset the drupal_alter() cache.

  drupal_static_reset('drupal_alter');

 

  // Provide the page with information about the theme that's used, so that a

  // later Ajax request can be rendered using the same theme.

  // @see ajax_base_page_theme()

  $setting['ajaxPageState'] = array(

    'theme' => $theme_key,

    'theme_token' => drupal_get_token($theme_key),

  );

  drupal_add_js($setting, 'setting');

}

这里面,我们的$theme来自于$themes[$theme],而global $theme返回的则是当前主题的机读名字。

drupal_theme_initialize,主要是在theme_get_registry函数中被调用的;而theme_get_registry,则主要是在theme()里面调用:

function theme($hook, $variables = array()) {

  // If called before all modules are loaded, we do not necessarily have a full

  // theme registry to work with, and therefore cannot process the theme

  // request properly. See also _theme_load_registry().

  if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) {

    throw new Exception(t('theme() may not be called until all modules are loaded.'));

  }

 

  $hooks = theme_get_registry(FALSE);

….

我们画个流程图:

图片1.png


                    

弄明白这个结构,不容易啊。但是,我还没有完全不明白,一个Drupal站点里面,包含多个Drupal主题,每个主题的注册表是怎么注册的呢?

我们都知道,点击性能页面的清除缓存按钮,就可以清除所有的缓存。那个时候,肯定也会清除主题注册表的缓存。我们平时,添加一个新模版文件的时候,总是要清除缓存才会起作用的。我们从这个路径入手,根据admin/config/development/performance,从system.module里面,找到system_menu这个钩子函数。我们找到对应菜单项的定义:

  $items['admin/config/development/performance'] = array(

    'title' => 'Performance',

    'description' => 'Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.',

    'page callback' => 'drupal_get_form',

    'page arguments' => array('system_performance_settings'),

    'access arguments' => array('administer site configuration'),

    'file' => 'system.admin.inc',

    'weight' => -20,

  );

根据这个信息,在'system.admin.inc'文件中找到'system_performance_settings'

function system_performance_settings() {

  drupal_add_js(drupal_get_path('module', 'system') . '/system.js');

 

  $form['clear_cache'] = array(

    '#type' => 'fieldset',

    '#title' => t('Clear cache'),

  );

 

  $form['clear_cache']['clear'] = array(

    '#type' => 'submit',

    '#value' => t('Clear all caches'),

    '#submit' => array('system_clear_cache_submit'),

  );

……

  return system_settings_form($form);

}

    我们找到了对应的提交处理函数system_clear_cache_submit,然后查看这个提交函数的源代码:

function system_clear_cache_submit($form, &$form_state) {

  drupal_flush_all_caches();

  drupal_set_message(t('Caches cleared.'));

}

代码很简单,主要是drupal_flush_all_caches函数的调用。我们在当前页面搜索这个函数,没有找到它的定义。通过Google搜索一下,这个Drupal API函数。这个函数定义在includes/common.inc里面:

function drupal_flush_all_caches() {

  // Change query-strings on css/js files to enforce reload for all users.

  _drupal_flush_css_js();

 

  registry_rebuild();

  drupal_clear_css_cache();

  drupal_clear_js_cache();

 

  // Rebuild the theme data. Note that the module data is rebuilt above, as

  // part of registry_rebuild().

  system_rebuild_theme_data();

  drupal_theme_rebuild();

 

  entity_info_cache_clear();

  node_types_rebuild();

  // node_menu() defines menu items based on node types so it needs to come

  // after node types are rebuilt.

  menu_rebuild();

 

  // Synchronize to catch any actions that were added or removed.

  actions_synchronize();

 

  // Don't clear cache_form - in-progress form submissions may break.

  // Ordered so clearing the page cache will always be the last action.

  $core = array('cache', 'cache_path', 'cache_filter', 'cache_bootstrap', 'cache_page');

  $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);

  foreach ($cache_tables as $table) {

    cache_clear_all('*', $table, TRUE);

  }

 

  // Rebuild the bootstrap module list. We do this here so that developers

  // can get new hook_boot() implementations registered without having to

  // write a hook_update_N() function.

  _system_update_bootstrap_status();

}

这段代码,建议大家认真的读两遍,这里涉及到CSSJS、主题、Entity、菜单,以及Drupal核心缓存表中缓存的清除工作。而我们这里关心的是:

  system_rebuild_theme_data();

  drupal_theme_rebuild();

这里面的system_rebuild_theme_data,是负责构建主题的info文件里面的数据的,有兴趣的可以继续沿着这个方向阅读对应的源代码。我们这里略过这个函数。drupal_theme_rebuild是用来重新构建Drupal的主题注册表的。我们来看这个函数,这个函数位于includes/theme.inc中:

function drupal_theme_rebuild() {

  drupal_static_reset('theme_get_registry');

  cache_clear_all('theme_registry', 'cache', TRUE);

}

我们看到了什么?这里清除了theme_get_registry以及theme_registry的所有缓存数据。但是这里面并没有重建这些数据。这个时候,我开始快明白了。

这个时候,我又做了一个这样的试验,就是在breadcrumb2_theme_registry_alter里面加上这样的调试信息

function breadcrumb2_theme_registry_alter(&$theme_registry) {

 print debug($theme_registry['page']);

….

}

然后清除缓存。我得到了这样的调试信息:

Debug: 

array (

  'template' => 'page',

  'path' => 'themes/seven',

  'type' => 'theme_engine',

  'theme path' => 'themes/seven',

  'render element' => 'page',

  'preprocess functions' => 

  array (

    0 => 'template_preprocess',

    1 => 'template_preprocess_page',

    2 => 'contextual_preprocess',

    3 => 'overlay_preprocess_page',

    4 => 'shortcut_preprocess_page',

    5 => 'seven_preprocess_page',

  ),

  'process functions' => 

  array (

    0 => 'template_process',

    1 => 'template_process_page',

    2 => 'rdf_process',

  ),

)

接着,我访问首页,我看到了新的调试信息:

array (

  'template' => 'page',

  'path' => 'themes/bartik/templates',

  'type' => 'theme_engine',

  'theme path' => 'themes/bartik',

  'render element' => 'page',

  'preprocess functions' => 

  array (

    0 => 'template_preprocess',

    1 => 'template_preprocess_page',

    2 => 'contextual_preprocess',

    3 => 'overlay_preprocess_page',

    4 => 'shortcut_preprocess_page',

  ),

  'process functions' => 

  array (

    0 => 'template_process',

    1 => 'template_process_page',

    2 => 'rdf_process',

    3 => 'bartik_process_page',

  ),

)

这个时候,我明白了一个道理,Drupal主题的注册表缓存数据,是在第一次需要的时候,才生成这些数据的。我们这里用到了Sevenbartik两个主题,对于其它Drupal主题,比如Garland,假如我们启用了它,但是没有页面使用这个主题的话,就不会为它缓存主题注册表数据。而不是我刚开始猜测的,为所有启用的主题,在清除缓存的时候,就重新生成一遍主题注册表里面的数据。

这一点和ImageCacheImage style),Boost模块的机制是一样的。归纳一下,就是说,当需要这个东西的时候,首先检查缓存,看是否存在,如果存在就使用缓存数据,如果不存在,系统就会重新生成一遍,并把数据缓存起来。在需要的时候,才会去缓存。不会去缓存用不到的数据。

至此,我通过对Drupal核心代码的研读,终于明白了主题机制背后的原理。这是改进后的代码:

function breadcrumb2_theme_registry_alter(&$theme_registry) {

 print debug($theme_registry['page']);

  if (isset($theme_registry['breadcrumb'])) {

    $path = drupal_get_path('module', 'breadcrumb2');

//$path = path_to_theme();

    $theme_registry['breadcrumb']['path'] = $path;

$theme_registry['breadcrumb']['theme path'] = $path;

    $theme_registry['breadcrumb']['template'] = 'breadcrumb';

    $theme_registry['breadcrumb']['function'] = NULL;

unset($theme_registry['breadcrumb']['function']);

  }

  global $theme;

  $themes = list_themes();

  $theme_obj = $themes[$theme];

  //print debug($themes[$theme]);

   _theme_process_registry($theme_registry, $theme_obj->name, 'theme', $theme_obj->name, dirname($theme_obj->filename));

}

清除缓存,测试,仍然没有起作用。我们最后一次修改核心代码:

function _theme_build_registry($theme, $base_theme, $theme_engine) {

  $cache = array();

  // First, process the theme hooks advertised by modules. This will

  // serve as the basic registry. Since the list of enabled modules is the same

  // regardless of the theme used, this is cached in its own entry to save

  // building it for every theme.

  if ($cached = cache_get('theme_registry:build:modules')) {

    $cache = $cached->data;

  }

  else {

    foreach (module_implements('theme') as $module) {

      _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));

    }

    // Only cache this registry if all modules are loaded.

    if (module_load_all(NULL)) {

      cache_set('theme_registry:build:modules', $cache);

    }

  }

  // Let modules alter the registry.

  drupal_alter('theme_registry', $cache);

  print debug($cache['breadcrumb']);

  // Process each base theme.

  foreach ($base_theme as $base) {

    // If the base theme uses a theme engine, process its hooks.

    $base_path = dirname($base->filename);

    if ($theme_engine) {

      _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path);

    }

    _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path);

  }

  print debug($cache['breadcrumb']);

  // And then the same thing, but for the theme.

  if ($theme_engine) {

    _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename));

  }

  print debug($cache['breadcrumb']);

  // Finally, hooks provided by the theme itself.

  _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));

  print debug($cache['breadcrumb']);

  // Let modules alter the registry.

  //drupal_alter('theme_registry', $cache);

 

  // Optimize the registry to not have empty arrays for functions.

  foreach ($cache as $hook => $info) {

    foreach (array('preprocess functions', 'process functions') as $phase) {

      if (empty($info[$phase])) {

        unset($cache[$hook][$phase]);

      }

    }

  }

  return $cache;

}

这里面,我加入了四个print debug($cache['breadcrumb']);,我想看看是在哪个阶段覆写的。清除缓存,然后再访问首页,我们看到这样的调试信息:

array (

  'variables' => 

  array (

    'breadcrumb' => NULL,

  ),

  'type' => 'module',

  'theme path' => 'sites/all/modules/breadcrumb2',

  'preprocess functions' => 

  array (

    0 => 'template_preprocess_breadcrumb',

  ),

  'process functions' => 

  array (

  ),

  'path' => 'sites/all/modules/breadcrumb2',

  'template' => 'breadcrumb',

)in _theme_build_registry() (line 695 of D:\xampp\htdocs\breadcrumb2\includes\theme.inc). 

Debug: array (

  'variables' => 

  array (

    'breadcrumb' => NULL,

  ),

  'type' => 'module',

  'theme path' => 'sites/all/modules/breadcrumb2',

  'preprocess functions' => 

  array (

    0 => 'template_preprocess_breadcrumb',

  ),

  'process functions' => 

  array (

  ),

  'path' => 'sites/all/modules/breadcrumb2',

  'template' => 'breadcrumb',

)in _theme_build_registry() (line 705 of D:\xampp\htdocs\breadcrumb2\includes\theme.inc). 

Debug: array (

  'template' => 'breadcrumb',

  'path' => 'themes/bartik/templates',

  'type' => 'theme_engine',

  'theme path' => 'themes/bartik',

  'variables' => 

  array (

    'breadcrumb' => NULL,

  ),

  'preprocess functions' => 

  array (

    0 => 'template_preprocess_breadcrumb',

  ),

  'process functions' => 

  array (

  ),

)in _theme_build_registry() (line 710 of D:\xampp\htdocs\breadcrumb2\includes\theme.inc). 

Debug: array (

  'template' => 'breadcrumb',

  'path' => 'themes/bartik/templates',

  'type' => 'theme_engine',

  'theme path' => 'themes/bartik',

  'variables' => 

  array (

    'breadcrumb' => NULL,

  ),

  'preprocess functions' => 

  array (

    0 => 'template_preprocess_breadcrumb',

  ),

  'process functions' => 

  array (

  ),

)

    从调试信息里面可以看出,是在第3print debug前面的一个阶段,完成了覆写工作。所以我们的代码应该这样写:

function breadcrumb2_theme_registry_alter(&$theme_registry) {

  //print debug($theme_registry['page']);

  if (isset($theme_registry['breadcrumb'])) {

    $path = drupal_get_path('module', 'breadcrumb2');

//$path = path_to_theme();

    $theme_registry['breadcrumb']['path'] = $path;

$theme_registry['breadcrumb']['theme path'] = $path;

    $theme_registry['breadcrumb']['template'] = 'breadcrumb';

    $theme_registry['breadcrumb']['function'] = NULL;

unset($theme_registry['breadcrumb']['function']);

  }

  global $theme;

  $themes = list_themes();

  $theme_obj = $themes[$theme];

  //print debug($themes[$theme]);

  _theme_process_registry($theme_registry, 'phptemplate', 'theme_engine', $theme_obj->name, dirname($theme_obj->filename));

  //_theme_process_registry($theme_registry, $theme_obj->name, 'theme', $theme_obj->name, dirname($theme_obj->filename));

}

我们把主题引擎的搬了过来。由于Drupal7下面,只有一个phptemplate主题引擎,所以这里直接写死在里面了。然后我们把theme.inc文件里面,修改过的代码,改回原状。恢复到Drupal核心最初的样子即可。

清除缓存,测试,成功了。其实我们这里面,只加了四行代码,但是为了加上这四行代码,我们对Drupal核心的主题机制,做了非常认真的分析,彻底搞明白了背后的基本原理。在实际的开发中,我从来不鼓励别人修改Drupal核心的代码,但是在练习当中,在学习实践当中,我总是随心所欲的在Drupal核心代码里面加上print debug,以追踪Drupal核心的执行流程。我这里介绍的是我的土办法,如果你有更好的工具,也可以使用你集成环境里面的代码逐行调试功能。

此外,我们的Think in Drupal4集也就写到这里了。加上以前所写的Field validation的历程,Drupal核心模板文件里面变量的含义说明,还有Drupal开发最佳实践什么的。

本来还想写JavaScript、单元测试、安装包profile的开发,考虑到Javascript本身的变化不大,而且我们阅读了contextula模块的代码,里面有相关的JS可供参考;单元测试、安装包profile的开发在中国,没有公司用的,所以就将这些知识点的介绍推后了。下面就是把所有的这段时间编写的,合并到一起,打印出来,然后,自己认真的读一遍,修正里面的文字错误,修正完以后的版本,自己拿去打印店打印,开始taobao上面出售了。就这样的流程。

20121231号的1724分,完成了Think in Drupal的写作。就是说,当别人放假,出去玩的时候,我们还在整理着这样的资料。

 



Drupal版本: