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

