Feeds:
文章
留言

Archive for 六月 11th, 2009

MediaWiki 的頁面呈現機制

這次說明 MediaWiki 的 View 部份的機制,我個人是覺得有些小複雜。相關的全域變數有

  • $wgOut
  • $wgDefaultSkin

相關類別

  • OutputPage — 輸出實際資料到前端
  • Skin — 實作套版
  • SkinTemplate — 實作自訂樣板機制
  • QuickTemplate — 自訂樣板基礎類別

整理後的類別圖(UML 不是很熟希望是沒畫錯)如下:

mw04

首先 OutputPage 類別負責整個架構實際輸出資料到前端,在 OutputPage::output 中會透過 User::getSkin() 取得目前使用者所用的樣板,getSKin 首先會透過 normalizeKey 正規化樣板的名稱,正規化簡單的說就是指定的樣板名稱是否存在 /skins/樣板名稱.php 的檔案。如果不存在就使用預設的樣板。所以樣板檔案名稱必須符合一定的規範。

正規化之後,載入該樣板檔檢查是否有符合 Skin+樣板名稱 命名規則的類別,如果沒有就使用預設樣板。最後將 MySkin 類別動態生成。此時已經具有從 Skin 繼承下來的物件。

接著透過取得 MySkin 物件的 outputPage 套用樣板。outputPage Method 是在 SkinTemplate 類別實作,其中呼叫 steupTemplate Method 動態生成 MySkin->template 定義的樣板類別 MyTemplate。

取得 MyTemplate 之後就是一系列 set, setRef 呼叫,最終喚起 execute 套用 MyTemplate 自訂樣板產生 HTML 輸出。

這就是 MediaWiki 的頁面呈現流程~

Read Full Post »

MediaWiki 的 Hook 架構

MediaWiki 提供許多方式讓外部人員進行擴充,其中一個就是 Hook 機制,Hook 機制基本上就是 Observer Pattern。

這個 Pattern 在各種語言都有用到,簡單的說就是註冊一堆 Observer,在系統必要時通知 Observer 進行處理。

MediaWiki 使用了一個全域變數和一個函數來實作。首先是全域變數 $wgHooks ,這個變數主要負責儲存和新增 Observer,一般都會用一個類別來實作,不過 PHP 的 array 已經有相當方便的新增方式。新增格式我整理出下面五種方式:

格式1: 函數

$wgHooks[‘event_name’][] = $function;

$wgHooks[‘event_name’][] = array( $function );

格式2: 用於包含資料的函數呼叫

$wgHooks[‘event_name’][] = array($function, $data);

格式3:用於類別呼叫

$wgHooks[‘event_name’][] = array($object, ‘method’);

格式4:類別需要實作一個 on+event_name 的 method

$wgHooks[‘event_name’][] = object;

$wgHooks[‘event_name’][] = array( object );

格式5:用於包含資料的類別呼叫

$wgHooks[‘event_name’][] = array($object, ‘method’, ,$data);

上述之所以支援五種 Observer 是相依於 Observer 的通知,也就是後面要介紹的函數。

MediaWiki 採用全域變數可讓任何程式在任何地方都可以隨時新增必要的 Observer。以上就 MediaWiki 透過全域變數實作 Observer 的新增。

 

再來就是中要得函數 wfRunHooks (定義在 includes/Hooks.php),他用來實作 Observer 的通知,我將不相關的註解和除錯移除整粒如下:

function wfRunHooks($event, $args = null) {
global $wgHooks;
if (!is_array($wgHooks)) {
return false;
}
if (!array_key_exists($event, $wgHooks)) {
return true;
}
if (!is_array($wgHooks[$event])) {
return false;
}
foreach ($wgHooks[$event] as $index => $hook) {
$object = NULL;
$method = NULL;
$func = NULL;
$data = NULL;
$have_data = false;
if (is_array($hook)) {
if (count($hook) < 1) {
                            // empty hook
} else if (is_object($hook[0])) {
$object =& $wgHooks[$event][$index][0];
if (count($hook) < 2) {
$method = "on" . $event;
} else {
$method = $hook[1];
if (count($hook) > 2) {
$data = $hook[2];
$have_data = true;
}
}
} else if (is_string($hook[0])) {
$func = $hook[0];
if (count($hook) > 1) {
$data = $hook[1];
$have_data = true;
}
} else {
                                // "Unknown datatype in hooks for " . $event . "n"
}
} else if (is_string($hook)) { # functions look like strings, too
$func = $hook;
} else if (is_object($hook)) {
$object =& $wgHooks[$event][$index];
$method = "on" . $event;
} else {
// "Unknown datatype in hooks for " . $event . "n";
}
if ($have_data) {
$hook_args = array_merge(array($data), $args);
} else {
$hook_args = $args;
}
if ( isset( $object ) ) {
$func = get_class( $object ) . ‘::’ . $method;
}
if( isset( $object ) ) {
$retval = call_user_func_array(array(&$object, $method), $hook_args);
} else {
$retval = call_user_func_array($func, $hook_args);
}
if (is_string($retval)) {
return false;
} else if (!$retval) {
return false;
}
}
return true;
}

 

函數相當的長,但是其實關鍵只有幾個,首先他使用了全域變數 $wgHooks 取出指定事件已註冊的 Observer。然後中間一大串就是為了支援五種 Observer 格式的判斷過程。你也可以簡化或者增加你要的 Observer 格式。最後用了下面兩個 PHP 函數來達成 Observer 通知

  • get_class
  • call_user_func_array

就是這麼簡單的實現了 Observer Pattern。

雖然實現簡單,但我個人是覺得還蠻好用,透過這個 Pattern,可在系統許多地方進行不同的 Observer 通知,增加系統的可擴充性。

例如變更使用者登入驗證方式:

$wgHooks['UserLogin'][] = array('ldapLogin', $ldapServer);
 
$ldap['server']="ldaps://ldap.company.com/";
$ldap['port'] = 636;
$ldap['base'] = ",ou=Staff,dc=company,dc=com";
 
function ldapLogin($username, $password) {
 global $ldap;
 $auth_user="uid=".$username.$ldap['base'];
  if($connect=@ldap_connect($ldap['server'],$ldap['port'])){
      if($bind=@ldap_bind($connect, $auth_user, $password)){
          @ldap_close($connect);
          return(true);
      }//if bound to ldap
      else {
         echo "Error on ldap_bind";
      }
  }//if connected to ldap
  else {
     echo "Error on ldap_connect";
  }
  @ldap_close($connect);
  return(false);
}

至於還有哪寫應用就自己想吧 ~

Read Full Post »