菜單(Menu)
前面曾經(jīng)簡單提到過Drupal的菜單, 今天稍微深入來探討一下. 菜單能用來顯示導(dǎo)航信息, 我們安裝的系統(tǒng), 默認(rèn)安裝有3個(gè)菜單, 讓我們查看一下數(shù)據(jù)庫吧, 以menu_開頭的總共有三張表: menu_custom, menu_links, menu_router. 其中menu_custom表存放菜單定義信息, 但想知道他們都是由哪個(gè)模塊定義的麼? 別忘記了菜單如果要顯示就是區(qū)塊哦, 打開區(qū)塊表(blocks)看看吧. Here it is! 用戶模塊(User Module)定義了Navigation菜單(沒看數(shù)據(jù)庫我以為是系統(tǒng)模塊(System Module)定義的呢), 菜單模塊(Menu Module)定義了Primary links和Secondary links兩個(gè)空菜單. 所以從表現(xiàn)層來看, 一個(gè)菜單就對應(yīng)一個(gè)區(qū)塊(Block), 它被放置在頁面的某個(gè)區(qū)域(Region)來顯示給用戶進(jìn)行導(dǎo)航.
其實(shí)Drupal的菜單機(jī)制不僅要能把導(dǎo)航顯示給用戶, 更重要的是在用戶點(diǎn)擊這些導(dǎo)航的時(shí)候, 能夠準(zhǔn)確快速定位到相應(yīng)的業(yè)務(wù)邏輯. 有人會問,難道這也是個(gè)問題嗎? 要知道導(dǎo)航其實(shí)都對應(yīng)他們具體的URI, 而傳統(tǒng)的URI的定位是先按目錄結(jié)構(gòu)找處理文件,然后根據(jù)Request參數(shù)對應(yīng)業(yè)務(wù)邏輯,同時(shí)還要在業(yè)務(wù)邏輯中判斷用戶權(quán)限; 而Drupal有一套自己的內(nèi)部路徑, 它是基于模塊化構(gòu)建的,與目錄結(jié)構(gòu)一點(diǎn)關(guān)系也沒有了, 所以必須要有一套機(jī)制能在URI和業(yè)務(wù)邏輯間進(jìn)行映射, 而Drupal的菜單機(jī)制就是完成這項(xiàng)工作的,用戶點(diǎn)擊菜單項(xiàng)鏈接時(shí), Drupal解析出內(nèi)部路徑, 并根據(jù)內(nèi)部路徑找到對應(yīng)的業(yè)務(wù)邏輯, 并再完成判斷權(quán)限后轉(zhuǎn)交給業(yè)務(wù)邏輯進(jìn)行處理,這個(gè)過程Drupal稱之為分發(fā).
Drupal核心框架中的菜單api(includes/menu.inc文件)實(shí)現(xiàn)了上述功能, 它成功地解決了動態(tài)URL路徑到具體執(zhí)行函數(shù)間映射, 對用戶屏蔽了系統(tǒng)預(yù)定義的Request參數(shù)的復(fù)雜處理, 在路徑和功能建立了必由之路. 啥也別說了, 看看分發(fā)函數(shù)的實(shí)現(xiàn):
<?php
>
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)) {
if ($router_item[‘a(chǎn)ccess‘]) {
if ($router_item[‘file‘]) {
require_once($router_item[‘file‘]);
}
return <strong>call_user_func_array($router_item[‘page_callback‘], $router_item[‘page_arguments‘]);</strong>
}
else {
return MENU_ACCESS_DENIED;
}
}
return MENU_NOT_FOUND;
}
?>
用戶URL請求到達(dá)后, Drupal先進(jìn)行Bootstrap初始化,然后調(diào)用分發(fā)函數(shù)menu_execute_active_handler, 該函數(shù)根據(jù)解析出的內(nèi)部路徑, 在系統(tǒng)構(gòu)建出的菜單路由表中查找,如果找到則判斷可訪問權(quán)限, 然后調(diào)用路由表中對應(yīng)路徑注冊的Pange_callback回調(diào)函數(shù),這樣就完成一個(gè)URL請求到具體頁面邏輯的過程. 流程非常簡單清晰, 學(xué)過計(jì)算機(jī)原理, 熟悉中斷調(diào)用的對這流程應(yīng)該都非常熟悉.
菜單路由(Menu Router)
Drupal系統(tǒng)主要依據(jù)menu_router表構(gòu)建系統(tǒng)菜單路由,而menu_router表的內(nèi)容則是基于各模塊的hook_menu鉤子來獲得, 這個(gè)鉤子較少被調(diào)用,一般都在模塊初始化或其他菜單需要重建的情況. 下面我們選一段Book模塊的menu鉤子代碼來看看:
<?php
function book_menu() {
$items = array();
$items[‘a(chǎn)dmin/content/book/%node‘] = array(
‘title‘ => ‘Re-order book pages and change titles‘,
‘page callback‘ => ‘drupal_get_form‘,
‘page arguments‘ => array(‘book_admin_edit‘, 3),
‘a(chǎn)ccess callback‘ => ‘_book_outline_access‘,
‘a(chǎn)ccess arguments‘ => array(3),
‘type‘ => MENU_CALLBACK,
‘file‘ => ‘book.admin.inc‘,
);
}
?>
book模塊所定義的所有菜單路由表由一個(gè)二維數(shù)組表示, 其中每一項(xiàng)為一個(gè)菜單路由, 下標(biāo)即為該路由的入口路徑, 這里為‘a(chǎn)dmin/content/book/%node‘. 菜單路由項(xiàng)的各屬性看命名已經(jīng)比較清晰了, 想了解可以參考Drupal.
我們看上面用的路徑中包含一段‘%node‘, 對了這是使用了通配符, 比如你訪問admin/content/book/7的時(shí)候, 會自動把node7裝載進(jìn)來, 太多細(xì)節(jié), 就此打住.
菜單項(xiàng)(Menu Item)
保存在menu _links表中, 定義了每個(gè)條目的名字, 條目間父子關(guān)系, 對應(yīng)路徑, 所屬模塊等等很多屬性,它應(yīng)不像菜單路由項(xiàng)一樣隱藏在背后干活, 它是可見的; 同時(shí)由于有通配符的存在, 它與菜單路由項(xiàng)并不是一一對應(yīng)關(guān)系.(比較奇怪的是默認(rèn)的Navigation菜單是用戶模塊創(chuàng)建的, 但它里面的所有菜單項(xiàng)卻是系統(tǒng)模塊定義的.)既然router表的數(shù)據(jù)從鉤子函數(shù)而來, 那link表是否也有對應(yīng)的鉤子呢, 實(shí)際上菜單項(xiàng)是由菜單模塊(Menu Moudle)進(jìn)行管理,通過GUI界面直接配置, 當(dāng)然菜單API也有對應(yīng)接口,比如menu_link_save(). ( 實(shí)在不行你直接寫數(shù)據(jù)庫也行,那不就是hack菜單模塊了麼)
Drupal6.x增加了兩個(gè)alter鉤子函數(shù)對應(yīng)這兩張表,它們是hook_menu_alter()<--->menu_router,hook_menu_link_alter()<--->menu_links, 它們主要處理內(nèi)容變化時(shí)的處理邏輯,由Drupal_alter()函數(shù)調(diào)用, 看代碼注釋說這個(gè)函數(shù)非常Ugly, 要在7中把它解決掉. 頭暈了一天, 今天也沒有心思再看下去了.
總結(jié):
現(xiàn)在我們有點(diǎn)明白Drupal的菜單機(jī)制了吧, 它主要由菜單api和菜單模塊組成, 提供一種框架, 使得其他功能模塊能過注冊菜單路由項(xiàng), 并在分發(fā)過程中, 通過該菜單路由表完成用戶頁面請求(具體URL)到功能模塊業(yè)務(wù)邏輯的映射. 當(dāng)然Drupal的菜單機(jī)制還有很多復(fù)雜特性, 來日方長, 有空繼續(xù)鉆研.
另:有一點(diǎn)不明白的是menu_router為啥要用鉤子函數(shù), 不能直接用初始數(shù)據(jù)庫腳本麼, 不過我也沒研究過安裝過程, 似乎好像沒有一個(gè)地方用了數(shù)據(jù)庫腳本, 有人清楚這一塊麼?