~/blog/wordpress-custom-hooks-actions-filters-guide.md
WordPress 開發與技巧 · 2025 / 07 / 18

自己造 Hook 才算進階:WordPress 自訂 Action 與 Filter 實戰

Eric — 浪花科技創辦人 / AI 架構師
Eric
浪花科技創辦人 · AI 架構師
自己造 Hook 才算進階:WordPress 自訂 Action 與 Filter 實戰
目錄 table-of-contents.md

會呼叫 add_action()add_filter(),只代表你站在 WordPress Hook 系統的「消費端」;真正的進階,是用 do_action()apply_filters() 自己定義掛載點,讓別人來掛你的功能。這層差異,正是「會寫外掛」和「能打造可擴展架構」的分水嶺。這篇就來實戰 WordPress 自訂 Action 與 Filter 的設計方法。

但今天,我想聊點更進階的。我們不只要當一個「使用者」,只會用別人做好的鉤子 (Hooks);我們要成為一個「創造者」,學會在自己的程式碼裡埋下鉤子,讓你的外掛、你的主題變得更有彈性、更容易擴充,甚至讓其他開發者愛上你的作品。這就是所謂的 WordPress Hook 與 Filter 差異與應用的進階心法:從「掛鉤」到「做鉤」。

溫故知新:Action 與 Filter 的核心差異

在我們開始打造自己的鉤子之前,身為一個囉嗦的工程師,我還是得花點時間確保我們在同一個頻道上。簡單來說,WordPress 的 Hooks 系統分為兩種:

  • Action Hooks (動作鉤子):用來在 WordPress 核心、主題或外掛執行的特定時間點「插入」並執行你的程式碼。它不要求你回傳任何東西。就像是在生產線的某個點,你跳進去做一件事,做完就拍拍屁股走人。例如:wp_headinitsave_post
  • Filter Hooks (過濾器鉤子):用來在資料被處理或輸出的過程中「修改」它。它會給你一個值,你必須處理完後再「回傳」這個值。就像是生產線上的品管員,把傳送帶上的產品拿起來加工一下,再放回傳送帶。例如:the_contentwp_title

如果你對這兩者的基礎差異還有些模糊,強烈建議你先閱讀我們之前的文章 解鎖 WordPress 的任督二脈:搞懂 Action & Filter Hooks,你的客製化功力瞬間爆發!,那篇文章有更詳細的拆解。今天,我們的重點在於如何從消費者轉變為生產者。

為什麼要自訂 Hooks?難道 WordPress 內建的不夠用嗎?

這是個好問題。WordPress 核心已經提供了上千個 Hooks,為什麼我們還要自找麻煩?答案是:為了可擴展性 (Extensibility)程式碼解耦 (Decoupling)

想像一下,你正在開發一個複雜的電商功能外掛,其中有一個函式負責處理訂單建立的流程,可能長這樣:


function create_new_order($order_data) {
    // 1. 驗證訂單資料
    validate_order_data($order_data);

    // 2. 寫入資料庫
    $order_id = save_order_to_db($order_data);

    // 3. 減少庫存
    decrease_stock($order_data['products']);

    // 4. 發送訂單確認信給客戶
    send_confirmation_email_to_customer($order_id);

    // 5. 發送新訂單通知給管理員
    send_new_order_notification_to_admin($order_id);

    return $order_id;
}

這段程式碼看起來很完美,不是嗎?但如果今天有個新需求:「嘿 Eric,我們想在訂單成立後,自動把訂單資料同步到我們的 ERP 系統。」你要怎麼辦?直接修改 create_new_order 這個函式嗎?

這就是耦合的災難。一旦你開始在核心函式裡塞入各種客製化需求,程式碼會變得越來越臃腫,難以維護。這時候,自訂 Hooks 就像是超人一樣來拯救你了。

實戰演練:打造你自己的 Action Hook

讓我們用自訂的 Action Hook 來重構上面的例子。我們可以在訂單處理流程的關鍵節點,埋下自己的鉤子。

我們使用 do_action() 函式來建立一個鉤子。第一個參數是鉤子的名稱,後面的參數則是你想傳遞給掛載在這個鉤子上函式的資料。

Step 1: 在你的核心函式中埋下 `do_action`


function create_new_order_with_hooks($order_data) {
    // 讓其他開發者有機會在訂單建立前做些事
    do_action('roamer_before_order_creation', $order_data);

    // 1. 驗證訂單資料
    validate_order_data($order_data);

    // 2. 寫入資料庫
    $order_id = save_order_to_db($order_data);

    // 如果寫入失敗,就此打住
    if (is_wp_error($order_id)) {
        return $order_id;
    }

    // 3. 減少庫存
    decrease_stock($order_data['products']);

    // 4. 發送訂單確認信給客戶
    send_confirmation_email_to_customer($order_id);

    // 5. 發送新訂單通知給管理員
    send_new_order_notification_to_admin($order_id);

    // 核心流程結束,提供一個鉤子讓別人掛載後續動作
    // 我們把新建立的 order_id 和原始的 order_data 都傳出去
    do_action('roamer_after_order_created', $order_id, $order_data);

    return $order_id;
}

看到了嗎?我們在函式的開頭和結尾分別加入了 roamer_before_order_creationroamer_after_order_created 兩個鉤子。我習慣在自訂鉤子前加上一個前綴 (Prefix),像是公司或專案名稱 (roamer),避免跟其他外掛的鉤子名稱衝突,這是個非常重要的好習慣。

Step 2: 使用 `add_action` 來掛載新功能

現在,當我們需要「同步訂單到 ERP」時,我們完全不需要去動 create_new_order_with_hooks 這個核心函式。我們只需要在另一個地方(比如另一個外掛或主題的 `functions.php`)寫下:


// 掛載到我們自訂的鉤子上
add_action('roamer_after_order_created', 'sync_order_to_erp_system', 10, 2);

function sync_order_to_erp_system($order_id, $order_data) {
    // 在這裡撰寫呼叫 ERP API 的程式碼
    $erp_api = new ERP_API();
    $response = $erp_api->send_order($order_id, $order_data);

    // 也可以記錄 log
    if (!$response) {
        error_log('Failed to sync order ' . $order_id . ' to ERP.');
    }
}

看看這個優雅的解決方案!你的核心功能保持乾淨、專注於它該做的事。而所有擴充功能都透過你提供的鉤子掛載進來。未來如果還要串接電子發票、簡訊通知,都只需要再寫一個函式並 add_action 就行了,完全符合軟體工程的「開放/封閉原則」(Open/Closed Principle)。

更上一層樓:用自訂 Filter 讓資料活起來

Action 讓我們可以在特定時間點「做事」,而 Filter 則讓我們可以「修改資料」。這在需要讓外掛或主題保有高度客製化彈性時,非常有用。

假設我們的外掛有一個功能,是顯示商品最終售價。這個價格可能受到會員等級、優惠券、活動折扣等多重因素影響。

Step 1: 在你的核心函式中埋下 `apply_filters`

我們使用 apply_filters() 來建立過濾器。第一個參數是鉤子名稱,第二個參數是「要被過濾的原始資料」,後續的參數可以是你認為在過濾時會需要用到的上下文資訊。


function get_product_final_price($product_id) {
    $product = get_product($product_id);
    $base_price = $product->get_price();

    // 預設的最終價格就是基本價格
    $final_price = $base_price;

    // 建立一個 filter,讓其他程式碼有機會來修改這個價格
    // 我們把原始價格、產品 ID 一併傳出去,方便做判斷
    $final_price = apply_filters('roamer_product_final_price', $final_price, $product_id);

    return $final_price;
}

Step 2: 使用 `add_filter` 來動態調整價格

現在,我們可以根據不同邏輯來掛載價格調整的函式。

比如,我們要為 VIP 會員打九折:


add_filter('roamer_product_final_price', 'apply_vip_discount_price', 10, 2);

function apply_vip_discount_price($price, $product_id) {
    // 檢查目前使用者是否為 VIP
    if (is_current_user_vip()) {
        // VIP 打九折
        $price = $price * 0.9;
    }
    // 千萬記得要把修改後的價格回傳!這是新手最常犯的錯!
    return $price;
}

如果今天又有一個限時特價活動,我們可以再掛載一個 filter,而且可以透過調整優先級 (priority) 來決定哪個折扣先算。


// 優先級設為 20,表示在 VIP 折扣之後才執行
add_filter('roamer_product_final_price', 'apply_special_event_discount', 20, 2);

function apply_special_event_discount($price, $product_id) {
    // 假設某個特定商品正在做特價活動,再折 50 元
    if (is_special_event_product($product_id)) {
        $price = $price - 50;
    }
    return $price;
}

透過 apply_filters,你的 get_product_final_price 函式變得非常有彈性。它只負責取得基本價格,並提供一個「修改點」,所有複雜的商業邏輯都透過 Filter 注入,各司其職,程式碼的可讀性和可維護性大大提升。

工程師的囉嗦:自訂 Hooks 的最佳實踐

在你開始瘋狂地在程式碼裡埋下 `do_action` 和 `apply_filters` 之前,請容我再囉嗦幾點:

  • 有意義的命名與前綴:如前所述,一定要加上獨特的前綴,並使用清晰、易懂的名稱。roamer_after_order_created 就比 my_hook_1 好上一萬倍。
  • 參數的學問:傳遞足夠的上下文參數。在 roamer_after_order_created 裡,我們同時傳了 $order_id$order_data,因為掛載功能的開發者可能兩者都需要。多傳遞一些有用的資訊,會讓你的鉤子更好用。
  • 撰寫註解:為你的自訂鉤子寫下良好的註解,說明這個鉤子的作用、觸發時機、以及傳遞了哪些參數。這對未來的你,或是其他協作的開發者來說,都是無價之寶。
  • 保持一致性:在整個專案中,保持命名風格和參數順序的一致性。

結論:從鉤子的使用者,蛻變為創造者

掌握 WordPress Action 與 Filter 的基本用法,能讓你完成 90% 的客製化需求。但學會如何在你自己的程式碼中建立自訂 Hooks,才是真正讓你從一個 WordPress 開發者,晉升為架構設計者的關鍵一步。

這不僅僅是寫出能動的程式碼,更是寫出乾淨、模組化、可擴展、可維護的程式碼。當你開始思考「我應該在哪裡埋下一個鉤子,讓未來的功能可以更容易地加進來?」,恭喜你,你的開發思維已經提升到了一個新的層次。

希望今天的分享能為你的 WordPress 開發武器庫再添一件神器。如果你正在規劃一個需要高度客製化與擴展性的 WordPress 專案,卻不知道如何下手設計架構,浪花科技的團隊擁有豐富的實戰經驗,能協助你打好最穩固的地基。

相關閱讀

對複雜的 WordPress 專案架構感到困惑嗎?或是有獨特的商業邏輯需要實現?歡迎點擊這裡,填寫表單與我們的資深工程師聊聊,讓我們協助你打造一個不僅功能強大,而且架構優雅的 WordPress 解決方案!

// FAQ

常見問題

WordPress 中要怎麼建立自己的 Action Hook?
在程式碼的特定節點使用 do_action() 函式即可建立自訂 Action Hook,第一個參數是鉤子名稱,後續參數是要傳給掛載函式的資料。其他開發者再用 add_action() 把功能掛載上去,就能在不修改核心函式的情況下擴充行為。
怎麼用 apply_filters 建立自訂的 Filter Hook?
使用 apply_filters() 建立過濾器,第一個參數是鉤子名稱,第二個參數是要被過濾的原始資料,後續可傳入判斷所需的上下文。掛載的函式必須處理後回傳值,apply_filters() 會用回傳值取代原值繼續流程。
為什麼要自訂 Hooks,而不是直接修改核心函式?
自訂 Hooks 的目的是提升可擴展性與程式碼解耦。把擴充需求透過鉤子掛載,核心函式能保持乾淨並專注本身職責,新增同步 ERP、電子發票、簡訊通知等功能時只要再寫函式掛載即可,符合軟體工程的開放/封閉原則。
自訂 Hook 的名稱應該怎麼命名才不會衝突?
建議在自訂鉤子名稱前加上專案或公司的前綴(Prefix),例如以 roamer_ 開頭,避免與 WordPress 核心或其他外掛的鉤子名稱重複。這是維護大型專案時很重要的好習慣。
~/roamer-tech/newsletter // FREE
// newsletter

訂閱免費電子報

把 AI 自動化、企業系統設計與 WordPress / Laravel 開發的真實案例和可直接照做的技巧,整理成電子報寄給你。只寄精選內容、不灌垃圾信,一鍵就能退訂。

$
// final.exec()

準備好讓你的網站開始為你工作了嗎?