WordPress的Hook機制與原理


作者: | 2011/10/10 14:31:35 | 9 則迴響


稍有接觸過WordPress佈景或外掛客製修改的朋友,對WordPress的Hook機制應該不陌生,但通常剛接觸WordPress Hook的新手,對其運作原理可能會有點混亂或糢糊。本文針對WordPress Hook運作大致做個簡單的說明,而預設讀者是理解基本的PHP function語法及運作,但對WordPress Hook機制不是很明白。

Hook機制裡登場的角色

先從「登場角色」的個別說明開始:

WordPress核心

指的是WordPress內建的程式碼架構,提供WordPress主要的基本功能。

Hook

也許你早已聽說,Hook本身雖是鈎子的意思,但直譯又有點奇怪,所以一般通常都不直譯它,而是直接稱它Hook。WordPress的Hook也可以想像成「鈎子」,這些「鈎子」會埋在WordPress網站中特定幾處的程式碼中,埋進去時使用的語法,其「標示位置」的意義比較大,沒有實質運作的內容。當程式執行到有埋Hook的地方時,它會找出所有對應到自己的Hook Function (也就是所有「鈎到」該Hook的hook function),並一一執行。

因此若沒有針對此Hook去「加入」要鈎上去的Hook Function,執行到此什麼也不會做。因此,它等於是WordPress核心預留一個執行的機會給未來想要加入客製功能的開發者。

Hook Function

Hook Function裡會有實質運作的內容,即是實作了一些客製功能,可能是存取DB、增加HTML code、執行其他函式…等。我們在Hook Function裡寫好所需的功能後,就可以利用「加入至對應Hook」的語法,把Hook Function自已鈎到該Hook上,使得該Hook被執行到時,也會連帶執行自己。

Hook機制是如何運作的?

舉個例子,我們拿wp_head及wp_footer這兩個內建的hook來說明,wp_head這個hook就是用來埋在負責輸出標籤的程式碼中,而wp_footer就是用來埋在輸出頁尾的程式碼中 (定義於wp-includes/general-template.php,用wp_head()及wp_footer()包裝起來)。這兩個hook,主要都是在佈景檔案中使用的,常見會出現在header.php及footer.php中。

請看下面的情境示例圖,我們把wp_head及wp_footer看成是「鈎子」,而別的hook functions就能來鈎住它:

我們馬上來寫一個簡單的例子。我們要寫一個hook function,就叫它print_sth(),然後把它鈎上wp_head這個hook。因為wp_head()的內容實際上就只有do_action(‘wp_head’); 這一行內容,而wp_footer()的內容也只有 do_action(‘wp_footer’);,所以我們直接把do_action的語法換到圖上去,比較容易做說明,因此示意圖變成:

如此,只要執行到輸出header.php時,就會執行到wp_head(),就如同執行到do_action(‘wp_head’),此時WP核心會去找所有「鈎上」wp_head這個hook的hook function,於是就找到我們寫的print_sth(),然後就執行它,所以結果它做的事就會出現在網站上,也完成了「客製」的動作:

簡單的說,Hook機制就是:WP核心或其他plugin、theme提供想客製功能的人一個置入客製程式碼(Hook Function)到特定的執行時間點(Hook)的機會。

WordPress的Action Hook與Filter Hook

WordPress中的Hook有兩種,分別是「Action Hook」及「Filter Hook」,我們剛才舉例的wp_head及wp_footer都是屬於Action Hook。不過,一開始你可以先把這兩種Hook看成是一樣的東西,只是Filter多了一點點不同的特色,接著說明。

Action Hook

WP核心 (或佈景、外掛)在做它們該做的事時,如果執行到有埋action hook的程式碼 (即是do_action語法) 時,會去找尋對應到的hook functions,進而執行這些hook functions(即那些透過add_action()來加入的hook functions),藉此完成客製功能。WP核心並不期待Action Hook functions會有回傳值,所以這裡的hook function只被視為一個「獨立切出來運作的功能」。

WP核心做它該做的事,你做你想做的事,做完就各自結束。

Filter Hook

跟Action Hook一樣,WP核心 (或佈景、外掛)在做它們該做的事時,如果執行到有埋filter hook的程式碼 (即是apply_filters語法) 時,就會去找尋對應的hook functions,進而執行這些hook functions(即那些透過add_filter()來加入的hook functions),藉此完成客製功能。與Action Hook不同之處是,所有「鈎上」Filter Hook的hook functions通常都會接收到參數,而WP核心會期待你拿到它提供的參數,並做完你想做的事後,要回傳(return)一個值,讓WP核心再利用你回傳的值來接著完成它該做的事。

透過你的干涉,修改了WP核心丟給你的參數,WP核心再接著拿你改過的參數,繼續完成它該做的事,此動作就像「過濾」的動作,因而得名filter。

比較Action Hook與Filter Hook的實作語法

比較一下兩種Hook在埋進某處程式碼時所用的語法,假設我們在某處 (可能是在輸出頁首的程式碼處,或輸出文章標題、文章內容、側邊欄…等地方,要「出現客製效果」的地方)埋下這兩種hook:


/*--------------- Action Hook ---------------*/
// 埋下一個名叫'do_more'的action hook
do_action('do_more');

/*--------------- Filter Hook ---------------*/
// 埋下一個名叫'get_special'的filter hook,注意它會有回傳值
$c = apply_filters('get_special',$a, $b);

然後我們可以在某處 (可能是其他外掛、functions.php等處,要「實作客製功能」的地方) 實作對應的hook function:


/*--------------- Action Hook Function---------------*/
// 增加要鈎上'do_more'這個hook的hook function,
// 並為此hook function取名叫more_func。
// 第一個參數是hook名稱、第二個是hook function名稱
add_action('do_more', 'more_func');
// 實作more_func的內容,不需回傳值
function more_func() {
    echo 'do more thing...';
}

/*--------------- Filter Hook Function ---------------*/
// 增加要鈎上'get_special' hook的hook function,
// 並為此hook function取名叫special_func。
// 參數1是hook名稱、參數2是hook function名稱
// 參數3是Priority(優先序)、參數4是hook function參數的數目
add_filter('get_special', 'special_func', 10, 2);
// 實作special_func的內容,需要給它回傳值
function special($a, $b) {
    $c = $a.' & '.$b; //做一些事,例如把兩個參數連接起來
    return $c; //回傳值
}

所以其實兩種Hook的運作方式幾乎一樣,只差在增加Action Hook函式不需回傳值,而增加Filter Hook function時,你必須要回傳一個值。所以Filter Hook函式通常都有提供參數,讓想客製的人可以取得它,處理後再回傳。

但如果有一個Filter Hook,它沒有任何hook function有去鈎它,它該怎麼取得回傳值?答案是,直接拿第一個它給的參數,以上面的例子來說,它會直接拿$a丟進$c。另外,其實我們也可以把filter寫的跟action一樣,只要不回傳值就行,但action hook就沒辦法「模仿」filter hook,因為無法取得回傳值。

Hook Function的優先序(Priority)

如果有很多地方(plugin或者佈景functions.php)都add同一個hook,會怎麼決定出現順序?等案很顯然是可以透過Hook Function的Priority參數來作優先序的設定:

就像我們剛才說明的例子中,我們使用add_filter加入special_func時設定的優先序是10,這也是Priority參數的預設值。如果你希望它能優先被執行,就設定小於10的數字,反之,就設個100、500之類的,讓它延後被執行。

但其實這裡有個隱含的衝突問題。

以wp_head這個hook為例,如果我寫了一個外掛,希望透過wp_head來輸出「增加a.css檔案」的HTML語法,而a.css會重新設定body元素的樣式,所以我希望它可以最後才被匯入,不要被其他css檔干擾,於是我將Priority設為900,但我怎麼知道Priority 900夠不夠大?若某個WP網站,它除了安裝我的外掛,也安裝了其他外掛,而其他外掛剛好也重新設定body元素的樣式,然後把Priority設為950,此時我寫的外掛在處理body樣式時就出事了,於是就跟其他外掛衝突了。

所以此時我們需要了解的是:我的WP網站可能裝了很多外掛,我怎麼知道同一個Hook被加了多少Hook Function,而每個Hook Function的Priority被設定為多少?

答案是,我們可以透過$wp_filters這個global變數來取得所有hook的資訊,像是如下的function:


// 列出所有的hook function及其priority
function list_hooked_functions($tag=false) {
global $wp_filter;
if ($tag) {
$hook[$tag]=$wp_filter[$tag];
if (!is_array($hook[$tag])) {
trigger_error("Nothing found for '$tag' hook", E_USER_WARNING);
return;
}
}else{
$hook=$wp_filter;
ksort($hook);
}
echo '<pre>';
foreach($hook as $tag => $priority) {
echo "<br>>>>>>\t<strong>$tag</strong><br>";
ksort($priority);
foreach($priority as $priority => $function) {
echo $priority;
foreach($function as $name => $properties) echo "\t$name<br>";
}
}
echo '</pre>';
return;
}
當我們呼叫 list_hooked_functions(‘wp_head’); 時,就會列出wp_head這個Hook所鈎住的所有hook function,可以看到priority 10之後有好幾個都沒有數字,因為它們都沒有特別指定priority,所以都是10,包括我們剛才寫的print_sth也在其中:

>>>>> wp_head

1	wp_enqueue_scripts
2	feed_links
3	feed_links_extra
8	wp_print_styles
9	wp_print_head_scripts
10	rsd_link
wlwmanifest_link
index_rel_link
parent_post_rel_link
start_post_rel_link
adjacent_posts_rel_link_wp_head
locale_stylesheet
wp_generator
rel_canonical
wp_shortlink_wp_head
print_sth
wp_admin_bar_header
_admin_bar_bump_cb

所以,衝突很難提早避免,但發生衝突時,可以預先思考有沒有可能是因為priority的設定,導致結果跟預期不符合。


標籤:, ,

分類:,

本文作者是Audi Lu

9 則留言

發佈回覆給「evyatartal」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。

*

*

*

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料