说实在的,drupal确实很复杂, 在国内用的人也不多,唯一看的懂的中文 技术 网站估计就是 drupalchina.org了, 但是对于源代码 的分析是少之又少.没有办法 ,只能自己来从头看起了! 越复杂的东西,往往功能 越强大,drupal也是这样的. 其实,看drupal 的代码想一次就弄清楚,恐怕不是很容易,我是前后看了三次,才渐渐对他略有了解, 真羞! 分析代码之前,先了解drupal的几个重要的系统 必须的也是访问页面 老是查来查去的几个表! system 这个表记录系统所有的模块和主题的开启状态以及相关信息,我们开启某一模块和主题,就是使用这个表来记录! blocks 记录系统中所有可用的区块,记录的信息包括 区块名称,所属模块,所属主题,位置.等 node 每种内容类型都看做一个节点,每发一个内容都要存到node表中.只记录title node_revisions 记录所发内容的实际数据 包括 body title id和node关联 variables 记录系统运行时需要的变量 和值 cache 缓存表 系统运行需要从此表中找出相应的数据 没有则构造存入表中 menu_router 顾名思义菜单路由的表,根据具体路径找出对应的模块.和函数 . drupal 的原理是根据传递的参数获得相应的路径进而找到相应的模块,并调用之.从开发 的角度上看, 系统的每一次点击连接,就是在调用我们的模块!这样,就形成了我们写模块,然后通过各个超级连接来 调用. 通过超级连接调用?听起来比较怪, 事实上,以前做web开发的时候,基本是每一种连接对应一个单独的页面, 这样,我们的程序 就有了 index show.php , edit.php 等 .我们看下drupal,根目录 下除了index.php 再看不到 一点和网站显示相关的文件 了! 那drupal是怎么在一个页面中各个地址间跳来跳去的呢. 记得有一个高人说过,源码 之前无秘密,想知道这个秘密,我们就必须打开index.php来看个究竟! 然而,结果又一次让我们失望! 在index.php 中我们只看到了这样的代码 require_once './includes/bootstrap.inc'; drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); $return = menu_execute_active_handler(); print theme('page', $return); 也就是说,系统的运行,就是这三个函数的功劳! 先从drupal_bootstrap这个函数看起吧! 从 bootstrap.inc中我们知道了 DRUPAL_BOOTSTRAP_FULL 的值是8! 这样 看下面的函数: function drupal_bootstrap($phase) { static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $phase_index = 0; while ($phase >= $phase_index && isset($phases[$phase_index])) { $current_phase = $phases[$phase_index]; unset($phases[$phase_index++]); _drupal_bootstrap($current_phase); } } 其实就是循环调用 _drupal_bootstrap 8 次,下面是对 _drupal_bootstrap的分析, 一些函数只写了功能,并没有列出实际代码! function _drupal_bootstrap($phase) { global $conf; switch ($phase) { case DRUPAL_BOOTSTRAP_CONFIGURATION: drupal_unset_globals(); // 从$globals 中清除 $_post $_GET 等变量 // Start a page timer: // global $timers; 开始计时 获取 当前时间 timer_start('page'); conf_init();// 给 $conf赋值 空数组 // 包含 setting.php 文件 break; case DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: require_once variable_get('cache_inc', './includes/cache.inc'); // 包含 ./includes/cache.inc 或者 $conf[cache_inc] 的值 if (variable_get('page_cache_fastpath', FALSE) && page_cache_fastpath()) { exit; } break; case DRUPAL_BOOTSTRAP_DATABASE: require_once './includes/database.inc'; db_set_active();// 安装数据表 获得数据库 连接字符 串 break; case DRUPAL_BOOTSTRAP_ACCESS: if (drupal_is_denied('host', ip_address())) { // 判断 是否是应该拒绝的地址 header('HTTP/1.1 403 Forbidden'); print 'Sorry, '. check_plain(ip_address()) .' has been banned.'; exit(); } break; case DRUPAL_BOOTSTRAP_SESSION: require_once variable_get('session _inc', './includes/session.inc'); // 在$conf中取数据 或者是 ./includes/session.inc session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc'); session_start();// 开始 session break; case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: $conf = variable_init(isset($conf) ? $conf : array()); // 从 variables 中找初始变量的值并放进$conf中 // Load module handling. require_once './includes/module.inc'; $cache_mode = variable_get('cache', CACHE_DISABLED); // Get the page from the cache. $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();// 从 cache 中取数据 // If the skipping of the bootstrap hooks is not enforced, call hook_boot. if ($cache_mode != CACHE_AGGRESSIVE) { bootstrap_invoke_all('boot');// 找出bootstrap是1的模块 并运行模块函数 hook_boot } // If there is a cached page, display it. if ($cache) { drupal_page_cache_header($cache); // If the skipping of the bootstrap hooks is not enforced, call hook_exit. if ($cache_mode != CACHE_AGGRESSIVE) { bootstrap_invoke_all('exit'); // 找出bootstrap是1的模块 并运行模块函数 hook_exit } // We are done. exit; } // Prepare for non-cached page workflow. drupal_page_header(); break; case DRUPAL_BOOTSTRAP_LANGUAGE: drupal_init_language(); //获取语言 存入变量 $language break; case DRUPAL_BOOTSTRAP_PATH: require_once './includes/path.inc'; // Initialize $_GET['q'] prior to loading modules and invoking hook_init(). drupal_init_path(); // 初始化$_GET['q']变量 break; case DRUPAL_BOOTSTRAP_FULL: require_once './includes/common.inc'; _drupal_bootstrap_full(); // 加载所有模块文件并运行模块函数 hook_init break; } } 到这里,我们知道了drupal_bootstrap的实际作用, 就是加载所有的系统必须文件和模块文件并运行模块函数 hook_init,这就是为什么我们调用 哪个模块,都可以很自由的调用呢,他一次全部include所有我们开启了的模块文件了!!!接着看menu_execute_active_handler(), function menu_execute_active_handler($path = NULL) { if (_menu_site_is_offline()) { return MENU_SITE_OFFLINE; } if (variable_get('menu_rebuild_needed', FALSE)) { menu_rebuild(); } if ($router_item = menu_get_item($path)) { // menu_get_item 函数的作用是根据传递的参数返回需要调用的模块名字! if ($router_item['access']) { if ($router_item['file']) { require_once($router_item['file']); } return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); // $router_item['page_callback'] 是模块里面的 callback函数 } else { return MENU_ACCESS_DENIED; } } return MENU_NOT_FOUND; } menu_get_item的代码如下::: function menu_get_item($path = NULL, $router_item = NULL) { static $router_items; if (!isset($path)) { $path = $_GET['q']; } if (isset($router_item)) { $router_items[$path] = $router_item; } if (!isset($router_items[$path])) { $original_map = arg(NULL, $path); $parts = array_slice($original_map, 0, MENU_MAX_PARTS); list($ancestors, $placeholders) = menu_get_ancestors($parts); //返回Array ( [0] => Array ( [0] => annotate ) [1] => Array ( [0] => '%s' ) ) 接着查 menu_router表 . if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) { $map = _menu_translate($router_item, $original_map); if ($map === FALSE) { $router_items[$path] = FALSE; return FALSE; } if ($router_item['access']) { $router_item['map'] = $map; $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts'])); } } $router_items[$path] = $router_item; } return $router_items[$path]; } 其实menu_get_item是关键了, 他其实就是 接收q的值,然后解析出对应的模块,并调用函数!!!到这一步,我们基本弄清楚了菜单路由的原理,下面看看theme('page')这 个函数了,他是最关键的,我们定义的区块和区块里面的内容以及我们自己做的模版文件*.tpl.php都是通过他来显示的!够牛吧!!;下面,将theme分解开来看,我们将基本可以弄清楚 他的原理. function theme() { $args = func_get_args(); //获得传递给 theme的参数 $hook = array_shift($args); // $hook 是 接受的第一个参数 是 page ,blocks block ,menu_item , 你可以直接输出 $hook看了! static $hooks = NULL; //记录函数每次运行后的值 if (!isset($hooks)) { //第一次调用则初始化 init_theme();// 获取当前主题 并初始化 此函数里面调用了 theme_get_registry(); theme_get_registry()里面的static 将记录每次获取的值 //下面列出init_theme代码: ******************************************************************************************* function init_theme() { global $theme, $user, $custom_theme, $theme_key; // If $theme is already set, assume the others are set, too, and do nothing if (isset($theme)) { //确保只初始化一次 $theme 是 $user中来的,$user 是在drupal_bootstrap中赋的值. return; } drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); //连接数据库 $themes = list_themes(); //列出所有主题存在数组中 // Only select the user selected theme if it is available in the // list of enabled themes. $theme = !empty($user->theme) && !empty($themes[$user->theme]->status) ? $user->theme : variable_get('theme_default', 'garland'); //从用户 变量获得站点$theme // Allow modules to override the present theme... only select custom theme // if it is available in the list of installed themes. $theme = $custom_theme && $themes[$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)) { $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme]; $ancestor = $themes[$ancestor]->base_theme; } _init_theme($themes[$theme], array_reverse($base_theme));//初始化站点$theme } *****************************************************************************/ $hooks = theme_get_registry(); //给 $hooks值,使其不为NULL /********************************************************** $hooks 很重要,他记录了本站关于显示的所有数据,是一个庞大的数组,下面输出他的一部分,请注意,很关键! /*********************************** [page] => Array ( [template] => page [path] => themes/garland [type] => theme_engine [theme path] => themes/garland [arguments] => Array ( [content] => [show_blocks] => 1 [show_messages] => 1 ) [theme paths] => Array ( [0] => modules/system [1] => themes/garland ) [preprocess functions] => Array ( [0] => template_preprocess [1] => template_preprocess_page [2] => phptemplate_preprocess_page ) ) [blocks] => Array ( [arguments] => Array ( [region] => ) [type] => module [theme path] => modules/system [function] => theme_blocks [theme paths] => Array ( [0] => modules/system ) [preprocess functions] => Array ( [0] => template_preprocess ) ) *********************************************************************** } if (is_array($hook)) { foreach ($hook as $candidate) { if (isset($hooks[$candidate])) { break; } } $hook = $candidate; } if (!isset($hooks[$hook])) { // $hook['page'] $hook['blocks'] 不存在则返回!!!! return; } // $hook 是传递给theme的参数!!! $hooks 是 theme_get_registry的返回的值 是存在cache中的 ,通过构建后的数组!!! $info = $hooks[$hook];//为什么传block就调用block, 传page 就调用 page的模版 这里就是了 可以输出$hook看下 global $theme_path; $temp = $theme_path; // point path_to_theme() to the currently used theme path: $theme_path = $hooks[$hook]['theme path']; // Include a file if the theme function or preprocess function is held elsewhere. if (!empty($info['file'])) { // $info == $hook['page'] 或则 $hook['page']之类,反正是 $hooks数组中的!! 判断有文件则包含之 $include_file = $info['file']; if (isset($info['path'])) { $include_file = $info['path'] .'/'. $include_file; } include_once($include_file); } if (isset($info['function'])) { //有函数则调用之! [function] => theme_blocks // The theme call is a function. $output = call_user_func_array($info['function'], $args); } else { //else开始 // The theme call is a template. $variables = array( 'template_files' => array() ); if (!empty($info['arguments'])) { $count = 0; foreach ($info['arguments'] as $name => $default) { $variables[$name] = isset($args[$count]) ? $args[$count] : $default; $count++; } } // default render function and extension. $render_function = 'theme_render_template'; //一个函数名 $extension = '.tpl.php'; // Run through the theme engine variables, if necessary global $theme_engine; if (isset($theme_engine)) { // If theme or theme engine is implementing this, it may have // a different extension and a different renderer. if ($hooks[$hook]['type'] != 'module') { if (function_exists($theme_engine .'_render_template')) { $render_function = $theme_engine .'_render_template'; } $extension_function = $theme_engine .'_extension'; if (function_exists($extension_function)) { $extension = $extension_function(); } } } // else 结束 if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) { //有 preprocess functions 则调用之 // This construct ensures that we can keep a reference through // call_user_func_array. $args = array(&$variables, $hook); foreach ($info['preprocess functions'] as $preprocess_function) { if (function_exists($preprocess_function)) { call_user_func_array($preprocess_function, $args); } } } // Get suggestions for alternate templates out of the variables // that were set. This lets us dynamically choose a template // from a list. The order is FILO, so this array is ordered from // least appropriate first to most appropriate last. $suggestions = array(); if (isset($variables['template_files'])) { $suggestions = $variables['template_files']; } if (isset($variables['template_file'])) { $suggestions[] = $variables['template_file']; } if ($suggestions) { $template_file = drupal_discover_template($info['theme paths'], $suggestions, $extension); //根据路径返回相应的 tpl 文件 } if (empty($template_file)) { $template_file = $hooks[$hook]['template'] . $extension; if (isset($hooks[$hook]['path'])) { $template_file = $hooks[$hook]['path'] .'/'. $template_file; } } $output = $render_function($template_file, $variables); //调用theme_render_template ,下面是代码,其实就是根据路径包含各tpl文件,并获得输出缓冲! //**************************** function theme_render_template($file, $variables) { extract($variables, EXTR_SKIP); // Extract the variables to a local namespace ob_start(); // Start output buffering include "./$file"; // Include the file $contents = ob_get_contents(); // Get the contents of the buffer ob_end_clean(); // End buffering and discard return $contents; // Return the contents } //***************************** } // restore path_to_theme() $theme_path = $temp; return $output; } 让我们在从头开始,运行一次 theme('page'),首先init_theme(),获取当前主题 并初始化 此函数里面调用了 theme_get_registry(); theme_get_registry()里面的static 将记录每次获取的值 $hooks = theme_get_registry();给 $hooks值,接着进行判断数组! if (!empty($info['file'])) { //有文件则包含之 $include_file = $info['file']; if (isset($info['path'])) { $include_file = $info['path'] .'/'. $include_file; } include_once($include_file); } if (isset($info['function'])) { //有函数则调用之! // ['page'] ['function']是空,只有 [preprocess functions] => Array ( [0] => template_preprocess [1] => template_preprocess_page [2] => phptemplate_preprocess_page ) 所以不调用!!!! $output = call_user_func_array($info['function'], $args); } else { //else开始 } 以上判断完毕后,接着 if (isset($info['preprocess functions']) && is_array($info['preprocess functions'])) { //有 preprocess functions 则调用之 // This construct ensures that we can keep a reference through // call_user_func_array.. //[preprocess functions] => Array ( [0] => template_preprocess [1] => template_preprocess_page [2] => phptemplate_preprocess_page )//派上用场了 $args = array(&$variables, $hook); foreach ($info['preprocess functions'] as $preprocess_function) { if (function_exists($preprocess_function)) { call_user_func_array($preprocess_function, $args);//开始调用 template_preprocess template_preprocess_page phptemplate_preprocess_page } } } 其中 template_preprocess_page 是关键 , 以下是 template_preprocess_page 关键代码!!! $regions = system_region_list($theme);//根据theme获取模版中所有的区域 // Load all region content assigned via blocks. foreach (array_keys($regions) as $region) { // Prevent left and right regions from rendering blocks when 'show_blocks' == FALSE. if (!(!$variables['show_blocks'] && ($region == 'left' || $region == 'right'))) { $blocks = theme('blocks', $region); //显示每一个区域的值 又一次调用 theme ,也就是说theme形成了递归调用! 在开始的 theme('page')中,就成了一个递归调用!接着调用 theme('blocks'). 于是 在 $hooks中找到[blocks] => Array ( [arguments] => Array ( [region] => ) [type] => module [theme path] => modules/system [function] => theme_blocks [theme paths] => Array ( [0] => modules/system ) [preprocess functions] => Array ( [0] => template_preprocess ) ) *************************// } else { $blocks = ''; } // Assign region to a region variable. isset($variables[$region]) ? $variables[$region] .= $blocks : $variables[$region] = $blocks; } 通过调用 theme('blocks') ,又调用了[function] => theme_blocks, 以下是 theme_blocks, 他又调用theme了不过是 theme('block', $block)! function theme_blocks($region) { $output = ''; if ($list = block_list($region)) { foreach ($list as $key => $block) { // $key == <i>module</i>_<i>delta</i> $output .= theme('block', $block); } } // Add any content assigned to this region through drupal_set_content() calls. $output .= drupal_get_content($region); return $output; } 转了一圈,有点晕? 其实,就是这样: 先获得系统关于主题的一个数组.放在$hooks中,接着从 $hooks 中找出相应的数组 然后接着进行处理 . 首先调用的肯定是 $hooks['page'] 这是关键,从$hooks['page'] 取出需要数据后,接着看 file function 等键的值,有file的包含,有处理函数的调用 , 在调用函数的时候调用了template_preprocess_page, 此函数是显示用来显示页面中各区块的 首先列出各区域 然后.foreach theme('blocks',$regions);而在调用 theme('blocks')的时候,又有数组 [blocks] => Array(Array ( [arguments] => Array ( [region] => ) [type] => module [theme path] => modules/system [function] => theme_blocks 这样,在调用 theme('blocks') 也就是在显示 block的时候, 又调用了 theme_blocks ,我们看下 theme_blocks 的函数,该函数先找出区块某一区域的所有值放入数组中 然后接着调用 theme('block') 最后这些变量都得到了,也该输出了吧,接着调用 drupal_discover_template 这个函数 是根据 路径和参数来返回各个tpl文件 比如 node 则调用 node.tpl.php 最后, theme_render_template 这个函数真正的包含各个tpl文件,并 ob_start ob_get_contents 来获取缓冲区的内容!!!到这里,页面显示完毕!!! theme 实际上是一个递归函数, 从 page 开始 一直到 block 再到 menu_item link 依次调用,层层递归 !!!!!! 到这里, drupal 的代码基本被我们翻了一小下,应该有收获了!!