5Drupal执行流程

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

  Drupal是如何处理一个HTTP页面请求呢?如果对于这样的请求处理,我们能够理解其基本的流程结构,那么对于今后的学习,将会很有帮助。如果你想追踪Drupal的代码执行过程,那么首先可以搭建好调试环境,从index.php文件开始,逐步设置断点,这样就可以快速的了解Drupal的基本原理了。让我们以一个简单的例子,来分析一下常用请求的处理流程。假定一个注册用户访问我的站点http://zhupou.cn,并浏览文章“Drupal入围2008全球开源CMS大奖赛决赛”,也就是访问路径http://zhupou.cn/node/88(我这里假定zhupou.cn已经升级到Drupal7)。

 

    1、首先,我们启用了简洁URLWeb服务器收到请求后,会按照URL重写把用户看到的URL,转换为系统可以理解的URL,在这里就是将http://zhupou.cn/node/88 转换为了http://zhupou.cn/index.php?node/88ApacheIISNginx都支持URL重写。

 

    2PHP开始执行Drupalindex.php文件,Drupal获取到内部路径“node/88.

 

    3Drupal启动完整地引导指令流程,完成资源的初始化,加载所有启用的模块后,将内部路径“node/88”映射到了节点模块的对应回调函数node_page_view上。

 

    4、节点模块执行node_page_view函数,经过node_page_view -à node_show à node_view_multipleànode_viewànode_build_content这样的持续回调,它从数据库中读取ID88的节点,并将返回的数据封装成一个drupal_render可以识别的数组。在node_viewnode_build_content函数中,先后触发了以下钩子函数:hook_field_prepare_view,hook_field_formatter_prepare_viewhook_entity_prepare_viewhook_field_formatter_viewhook_node_viewhook_entity_viewhook_node_view_alterhook_entity_view_alter

 

    5、主题系统获取节点88的数据信息,并将其使用html代码进行封装,同时应用CSS。主题系统获取与节点88相关的其它页面元素数据信息,并分别将其使用html代码进行封装,同时应用CSS

 

    6Drupal完成所有的处理后,把最终封装好的HTMLCSS数据传送给用户的浏览器。浏览器将这些数据显示成web页面,呈现给最终用户。

 

    这里重点需要理解的就是Drupal的引导指令,和相关的钩子函数调用。让我们对这两点进一步的解释。

1e30e924b899a901947c2b8d1d950a7b0308f5c4.jpg


Drupal版本:

5.1 引导指令

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

 我们在前面提到,Drupal会启动一个完整的引导指令,实际上,对于每个由Drupal处理的页面请求,Drupal都会启用自己的引导指令。Drupal的引导指令,定义在bootstrap.inc文件中,它包含八个阶段:

 

 

初始化配置阶段

在这一阶段,通过include_once()来解析settings.php文件,提取该文件中保存的键值信息,将会初始化Drupal的内部配置数组,并建立站点的基路径($base_url),获取HTTP请求的内部路径,初始化一些全局变量。

 

页面缓存阶段

在有些情况下,我们需要更高性能的站点,因此就需要不经过数据库就调用缓存系统。在页面缓存阶段,系统会加载自带的缓存处理器,并尝试加载第3方模块(比如memcached)的缓存处理器,如果启用了页面缓存,那么系统将会在本阶段直接返回缓存的页面,并终止引导指令后续阶段的执行。

 

初始化数据库阶段

在初始化数据库阶段,将会初始化数据库系统。需要注意的是,数据库连接只有在实际调用时,才会正式初始化;同时会注册自动加载的函数,这样我们就可以方便的访问系统中定义的类和接口了。

 

初始化变量系统阶段

 

在这个阶段,将会初始化Drupal的锁系统;加载所有的系统变量,注意这里没有覆写settings.php中的变量;加载引导指令模块。

 

初始化会话处理阶段

Drupal采用了PHP内置的会话处理机制,并在在此基础上,实现了自己用户层级的会话存储处理器,使用session_set_save_handler函数重载了SESSION存储方式,这样就可以使用数据库来存储会话信息了。这里我们需要注意的是,Drupal的会话信息不是存储在内存中的,而是存储在数据库中的。在本阶段,将会初始化或者重新构建会话。代表当前用户的全局对象$User也会在这一阶段初始化,不过出于效率的考虑,并不是对象的所有属性都是可用的(当需要时,可以通过明确的调用函数user_load()来加载这些属性)。

 

设立页面头部阶段

在这个阶段,会使用bootstrap_invoke_all触发hook_boot钩子,系统会使用这个钩子来设置一些全局参数,以供后面调用;同时还会初始化锁系统;发送默认的HTTP头部。需要注意的是,在调用hook_boot钩子时,大多数的模块和许多通用函数库还没有加载进来,它是处理页面请求所调用的第一个钩子,比hook_init还要早。如果你所用的模块实现了这个钩子,那么会在“性能”管理页面,提示你这个模块与激进缓存模式不兼容。

 

语言判定阶段

    在这个阶段,会初始化所有已定义的语言类型。如果站点启用了多语言特性,系统会基于语言协定设置,为每个给定类型选择一个语言;同时在多语言环境下,完成了语言系统初始化后,还会调用hook_language_init钩子。

 

完成

该阶段是引导指令的最后一个阶段,它包括加载一些通用函数库,比如path.inctheme.incpager.incmenu.incfile.incform.incajax.inctoken.inc等等。在这里将设置Drupal定制的错误处理器,并加载所有启用了的模块。同时还会初始化Drupal内部路径,初始化主题系统。最后Drupal调用hook_init钩子,这样在对请求正式处理以前,为相应模块提供一个交互的机会。

 


Drupal版本:

5.2 钩子的执行顺序

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

  前面我们在Drupal概念中讲到钩子,现在进一步的对其进行分析,掌握了Drupal的钩子机制,熟悉各种常用的钩子,这是Drupal开发过程中的必备条件。

 

    钩子的触发,通常使用module_invoke_all来完成,这是一种常用的方式,但是还存在其它的方式。比如在引导指令阶段,就使用了bootstrap_invoke_all触发hook_boot钩子。代码如下:

   bootstrap_invoke_all('boot');

   

    而在node_build_content函数中,hook_node_view_alterhook_entity_view_alter钩子的触发,则是使用drupal_alter完成的,代码如下:

    drupal_alter(array('node_view', 'entity_view'), $build, $type);

 

   而hook_field_prepare_viewhook_field_formatter_prepare_view钩子的触发,使用的代码分别是:

_field_invoke_multiple('prepare_view', $entity_type, $prepare);

_field_invoke_multiple_default('prepare_view', $entity_type, $prepare, $view_mode);

 

    注意,这两个都是内部函数,专门用于field相关的模块。后者对前者作了封装,最终将使用field_default_prepare_view()来触发hook_field_formatter_prepare_view钩子。

 

    这些触发钩子的函数,尽管名字各不相同,但是里面的核心代码,是一致的。

 

 foreach ($modules as $module) {  

    $function = $module . '_hookname';

    if (function_exists($function)) {

      $function();

    }

  }  

   

    如果多个函数都实现相同的钩子,那么这些钩子之间的执行顺序是怎么决定的呢?Drupal首先会使用模块的重量进行排序,按照顺序依次执行。重量越小,越靠前;重量越大,越靠后。但是通常情况下,模块的重量都默认为0。在重量相同的情况下,则按照模块名字的字母顺序进行排列。比如,我们使用form_alter模块修改特定表单,如果有多个模块同时修改了该表单,那么form_alter钩子的执行顺序将会对结果产生影响。而默认的按模块名字的字母顺序执行,有时候并不能得到我们想要的结果,这个时候我们可以调整模块本身的重量。示例代码如下(这里假定模块名字为module_name):

db_update('system')  

  ->fields(array(    

    'weight' => 999,      

  ))  

  ->condition('name', 'module_name')  

  ->execute();

 

    我画了一张钩子执行顺序的流程图,希望能够方便大家理解构字的执行顺序。

1.png 

                   钩子执行顺序图

 

 

总结

读完本章以后,你应该能够

理解Drupal是什么,

Drupal核心的文件结构、

Drupal常用术语,

Drupal处理http请求的大致流程,

引导指令流程,

钩子回调流程。