利用 WordPress Hooks 的力量:操作和過濾器解釋

已發表: 2022-07-22

像任何 CMS 一樣,WordPress 並不總是開箱即用地滿足您的所有需求。 由於它是開源的,您可以修改它以使其符合您的業務需求——但是,您可以使用 WordPress 的鉤子來實現您的目標。 使用鉤子構建是一種成功的策略,它可以讓 WordPress 開發人員自由地構建幾乎任何可以想像的網站功能。

WordPress 鉤子:操作和過濾器

WordPress 鉤子不僅僅是強大的自定義工具,它們是 WordPress 組件相互交互的方式。 掛鉤函數管理我們認為是 WordPress 的一部分的許多日常任務,例如向頁面添加樣式或腳本,或用 HTML 元素包圍頁腳文本。 搜索 WordPress Core 的代碼庫會發現 700 多個位置的數千個鉤子。 WordPress 主題和插件包含更多的鉤子。

在我們進入鉤子並探索動作鉤子和過濾器鉤子之間的區別之前,讓我們了解它們在 WordPress 架構中的位置。

WordPress 基礎設施

WordPress 的模塊化元素很容易相互集成,因此我們可以輕鬆混合、匹配和組合:

  1. WordPress 核心:這些是 WordPress 工作所需的文件。 WordPress Core 提供通用架構、WP 管理儀表板、數據庫查詢、安全性等。 WordPress Core 是用 PHP 編寫的,並使用 MySQL 數據庫。
  2. 主題(或父主題):主題定義了網站的基本佈局和設計。 由 PHP、HTML、JavaScript 和 CSS 文件提供支持,主題通過讀取 WordPress MySQL 數據庫來生成在瀏覽器中呈現的 HTML 代碼。 例如,主題中的掛鉤可以添加樣式表、腳本、字體或自定義帖子類型。
  3. 子主題:我們自己創建子主題,以微調父主題提供的基本佈局和設計。 子主題可以定義樣式表和腳本來修改繼承的功能或添加或刪除帖子類型。 子主題說明總是取代父主題的說明。
  4. 插件:為了擴展 WordPress 的後端功能,我們可以從數以千計的第三方插件中進行選擇。 例如,插件中的掛鉤可以在發布帖子時通過電子郵件通知我們,或者隱藏用戶提交的包含禁止語言的評論。
  5. 自定義插件:當第三方插件不能完全滿足業務需求時,我們可以通過在 PHP 中編寫自定義插件來加速它。 或者我們可以從頭開始編寫一個新插件。 在這兩種情況下,我們都會添加鉤子來擴展現有功能。

金字塔從底部到頂部顯示五個級別:(1) WordPress 核心,(2) 主題,(3) 子主題,(4) 插件,(5) 自定義插件。
WordPress 基礎架構層次結構

既然我們可以訪問所有五層的源代碼,為什麼 WordPress 中需要鉤子?

代碼安全

為了跟上不斷發展的技術,WordPress Core、父主題和插件的貢獻者經常發布更新以緩解安全漏洞、修復錯誤、解決不兼容性或提供新功能。 任何具有應急經驗的顧問都知道第一手資料,未能使 WordPress 組件保持最新可能會危及甚至禁用網站。

如果我們直接修改上游 WordPress 組件的本地副本,我們會遇到一個問題:更新會覆蓋我們的自定義。 在自定義 WordPress 時我們如何規避這個問題? 通過鉤子,在子主題和自定義插件中。

在我們的兒童主題中編碼

子主題是一個安全的空間,我們可以在其中自定義已安裝主題的外觀。 此處添加的任何代碼都將覆蓋父級中的類似代碼,而不會被更新覆蓋。

當子主題被激活時,它會鏈接到已停用的父主題,繼承並展示父主題的特徵,同時不受父主題更新的影響。 為了不被修改主題的誘惑,最佳實踐建議在我們的設置中激活子主題。

編寫自定義插件

當一個插件被激活時,它的functions.php文件在服務器上的每個調用中執行。 反過來,WordPress 根據優先級從所有活動插件中加載和排序鉤子,並按順序執行這些鉤子。 為了擴展第三方插件的功能,我們可以編寫自己的 WordPress 自定義插件。

在 WordPress 中放置我們的鉤子的位置

目標例子在哪裡?
子主題 PHP 自定義插件 PHP
修改網頁結構添加自定義樣式表以更改網站元素的顏色和字體
修改另一個插件的功能(即創建一個插件來增強第三方插件的功能) 為自定義帖子類型添加副標題(例如,“新聞”)
添加超越 WordPress Core 的新功能修改訪問帖子時發生的工作流程以包括更新數據庫中的計數器

潛水前準備:定義

為避免混淆術語,我們將堅持使用以下術語:

  • 鉤子是 WordPress 中註冊函數以運行的最佳位置。 我們可以將我們的函數連接到 WordPress 及其組件中的眾多鉤子之一,或者創建我們自己的。
    • 動作掛鉤運行動作。
    • 過濾器掛鉤運行過濾器。
  • 掛鉤函數是我們“掛鉤”到 WordPress 掛鉤位置的自定義 PHP回調函數。 使用哪種類型取決於鉤子是否允許在函數之外進行更改——例如,直接添加到網頁輸出、修改數據庫或發送電子郵件。 這些被稱為副作用
    • 過濾器(或過濾器函數)應該避免副作用,只處理傳遞給它的數據,然後返回修改後的副本。
    • 相反,動作(或動作函數)旨在引起副作用。 它沒有返回值。

顯示與兼容掛鉤配對的功能的圖表。過濾器鉤子附加了過濾器函數,動作鉤子附加了動作函數。
WordPress 鉤子可以有多個回調函數,但所有回調函數都必須與它們註冊的鉤子類型相匹配。

考慮到這些區別,我們可以開始探索鉤子了。

抽象和乾淨的代碼

當一個動作或過濾器根據需要合併到一個鉤子中時,我們實現了每個任務只編寫一個函數並避免在項目中重複代碼的目標。 例如,假設我們想將相同的樣式表添加到我們主題中的三個頁面模板(存檔、單頁和自定義帖子)。 與其覆蓋父模板中的每個模板,然後在子主題中重新創建每個模板,然後將樣式表添加到單獨的 head 部分,我們可以在單個函數中編寫代碼並使用wp_head鉤子附加它。

周到的命名法

通過唯一地命名子主題或自定義插件掛鉤來主動避免衝突。 在單個站點中使用同名掛鉤是導致意外代碼行為的秘訣。 最佳實踐規定我們以唯一的短前綴(例如,作者、項目或公司的首字母)開始我們的鉤子名稱,然後是描述性的鉤子名稱。 例如,對於項目 Tahir 的 Fabulous 插件,使用“項目首字母加掛鉤名稱”模式,我們可以將掛鉤tfp-upload-documenttfp-create-post-news

並發開發和調試

一個鉤子可能觸發不止一個動作或過濾器。 例如,我們可以編寫一個包含多個腳本的網頁,所有這些腳本都使用wp_head動作掛鉤在頁面前端的<head>部分中打印 HTML(例如, <style><script>部分)。

因此,多個插件開發人員可以在單個插件上並行推進多個目標,或者將插件分成多個更簡單的單獨插件。 如果某個特性不能正常工作,我們可以直接調查和調試它的鉤子函數,而不必搜索整個項目。

行動

當 WordPress 中發生事件時,操作會運行代碼。 操作可以執行以下操作:

  • 創建數據。
  • 讀取數據。
  • 修改數據。
  • 刪除數據。
  • 記錄登錄用戶的權限。
  • 跟踪位置並將它們存儲在數據庫中。

可以觸發操作的事件示例包括:

  • init ,在 WordPress 加載之後但在將標頭髮送到輸出流之前。
  • save_post ,當帖子被保存時。
  • wp_create_nav_menu ,在導航菜單創建成功之後。

動作可以與 API 交互以傳輸數據(例如,社交媒體上帖子的鏈接),但它不會將數據返回給調用鉤子。

假設我們想通過社交媒體自動分享我們網站上的所有新帖子。 首先查看 WordPress 文檔以查找可以在發布帖子時觸發的鉤子。

找到我們的鉤子沒有捷徑:我們將通過經驗學習或仔細研究列出的行動以找到可能的候選人。 我們可能會考慮save_post一個候選者,但很快就會排除它,因為它會在單個編輯會話期間觸發多次。 更好的選擇是transition_post_status ,它僅在帖子狀態更改時觸發(例如,從draftpublish ,從publishtrash )。

我們將使用transition_post_status但也會改進我們的操作,使其僅在我們的帖子狀態轉換為publish時運行。 此外,通過遵循各種社交媒體平台的官方文檔和 API,我們可以集成和發布我們的帖子內容以及特色圖片:

 <?php function publish_post_on_social_media ( $new_status = NULL, $old_status = NULL, $post_ID = NULL ) { if ( 'publish' == $new_status && 'publish' != $old_status ) { // build the logic to share on social media } } add_action( 'transition_post_status', 'publish_post_on_social_media', 10, 3 ); ?>

既然我們知道瞭如何使用動作鉤子,那麼有一個特別有用,尤其是在涉及 CSS 時。

使用wp_enqueue_scripts指定優先級

假設我們想在加載完所有其他樣式表後最後添加子主題的樣式表,以確保源自其他地方的任何同名類都被我們的子主題的類覆蓋。

WordPress 以默認順序加載樣式表:

  1. 家長主題
  2. 兒童主題的
  3. 任何插件

在這個結構中:

 add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1)

…添加動作的priority值決定了它的執行順序:

  • wp_enqueue_scripts (或任何操作)的默認priority值為“10”。
  • 如果我們將其priority重置為較低的數字,則函數會更早運行。
  • 如果我們將其priority重置為更高的數字,則函數將稍後運行。

要最後加載我們子主題的樣式表,請使用wp_enqueue_scripts ,這是 WordPress 主題和插件常用的操作。 我們只需將子主題的操作wp_enqueue_scripts的優先級更改為高於默認值“10”的數字,例如“99”:

 add_action( 'wp_enqueue_scripts', 'child_theme_styles', 99 );



通常,我們在不尋找返回值時使用操作。 要將數據返回給調用鉤子,我們需要查看過濾器。

過濾器

過濾器允許我們在處理數據以在瀏覽器中顯示之前對其進行修改。 為此,過濾器接受變量,修改傳遞的值,並返回數據以供進一步處理。

WordPress 在為瀏覽器準備內容之前檢查並執行所有註冊的過濾器。 這樣,我們可以在將數據發送到瀏覽器或數據庫之前根據需要對其進行操作。

我的一位客戶通過在產品上印上客戶提供的圖像來個性化他銷售的產品。 此客戶端使用 WooCommerce 插件來管理電子商務。 WooCommerce 不支持開箱即用的此功能。 因此,我在客戶端的functions.php中添加了兩段代碼:

  1. WooCommerce 文檔中列出的woocommerce_checkout_cart_item_quantity是一個過濾器鉤子,允許客戶在結帳前將外部元素添加到他們的購物車中。
  2. my_customer_image_data_in_cart是一個過濾器,我們將自己編寫並用於在 WooCommerce 準備展示購物車時觸發woocommerce_checkout_cart_item_quantity

使用以下模板,我們可以添加過濾器並修改購物車的默認行為:

 add_filter( 'woocommerce_checkout_cart_item_quantity', 'my_customer_image_data_in_cart', 1, 3 ); function my_customer_image_data_in_cart( $html, $cart_item, $cart_item_key ) { if ( !empty( $cart_item['images_data'] ) ) { // Store image // Get image URL // Modify $html } return $html; }

我們添加過濾器的方式與添加操作的方式相同。 過濾器的工作方式與操作類似,包括如何處理優先級。 過濾器和動作之間的主要區別在於,動作不會將數據返回給調用鉤子,但過濾器會。

自定義的動作鉤子和過濾器鉤子

編寫自定義操作掛鉤不會擴展 Wordpress Core,而只會在我們自己的代碼中創建新的觸發點。

創建自定義操作掛鉤

在我們的主題或插件中添加自定義鉤子允許其他開發人員在不修改我們的代碼庫的情況下擴展功能。 要添加自定義鉤子,請使用 WordPress 核心代碼庫本身使用的相同技術:在我們想要的觸發點,我們只需使用新鉤子的名稱調用do_action ,可以選擇添加盡可能多的參數,因為我們的回調可能會覺得有用:

 do_action( 'myorg_hello_action', $arg1, $arg2 );

這段代碼簡單地運行任何已經掛在我們自定義鉤子上的回調函數。 請注意,命名空間是全局的,因此,如前所述,最好在我們的自定義鉤子名稱前加上我們組織(也可能是我們的項目)名稱的縮寫形式,因此這裡是myorg_

現在我們已經定義myorg_hello_action ,開發人員可以使用與我們之前介紹的內置鉤子完全相同的方式進行掛鉤:定義一個函數,然後調用add_action()

除非我們想純粹在內部使用新的鉤子——畢竟這是一種構建代碼的有用方式——我們必須通過清晰的文​​檔向下游、我們團隊的其他成員或我們插件的外部用戶傳達它的可用性.

創建自定義過濾器掛鉤

WordPress 自定義過濾器鉤子的模式與操作鉤子的模式相同,只是我們調用apply_filters()而不是do_action()

這次讓我們來看一個更具體的例子。 假設我們的插件創建了一個側邊欄菜單,通常由四個項目組成。 我們將添加一個自定義過濾器掛鉤,以便我們(和下游開發人員)可以在其他地方修改該項目列表:

 // Text labels of sidebar menu $sidebar_menu = array( "Page One", "Page Two", "Page Three", "Page Four" ); $sidebar_menu = apply_filters( 'myorg_sidebar_menu', $sidebar_menu );

就是這樣——我們的自定義過濾器掛鉤myorg_sidebar_menu現在可以在插件中使用,該插件可能稍後或在此插件的其他地方加載。 這允許任何人編寫下游代碼來自定義我們的側邊欄。

在使用內置 WordPress 掛鉤時,我們或其他開發人員將遵循相同的模式。 換句話說,我們將從定義一些回調函數開始,這些函數返回它們傳遞的數據的修改版本:

 function lowercase_sidebar_menu( $menu ) { $menu = array_map( 'strtolower', $menu ); return $menu; } function add_donate_item( $menu ) { $menu = array_push( $menu, 'Donate' ); return $menu; }

與我們之前的示例一樣,我們現在準備將過濾器回調函數掛鉤到我們的自定義掛鉤:

 add_filter( 'myorg_sidebar_menu', 'add_donate_item', 100 ); add_filter( 'myorg_sidebar_menu', 'lowercase_sidebar_menu' );

有了這個,我們已經將我們的兩個示例回調函數掛鉤到我們的自定義過濾器掛鉤上。 現在兩者都修改了$the_sidebar_menu的原始內容。 因為我們為add_donate_item賦予了更高的priority值,所以它在lowercase_sidebar_menu執行之後運行。

三個面板描述了本節中描述的過濾器函數的結果。面板 1 顯示了側邊欄,因為它沒有回調掛鉤到過濾器中。面板 2 顯示了側邊欄,因為它是連接到過濾器的 lowercase_sidebar_menu 回調,所有四個項目名稱都是小寫的。面板 3 顯示了側邊欄,就像 donate_button 回調也掛接到過濾器中一樣——與面板 2 中相同的小寫項目加上第五個項目,“捐贈”,留在標題中。

下游開發人員總是可以自由地將更多回調函數掛鉤到myorg_sidebar_menu 。 正如他們所做的那樣,他們可以使用priority參數使他們的鉤子在我們的兩個示例回調函數之前、之後或之間運行。

天空是動作和過濾器的極限

借助操作、過濾器和掛鉤,WordPress 功能可以突飛猛進。 我們可以為我們的網站開發自定義功能,讓我們自己的貢獻與 WordPress 一樣可擴展。 當我們將 WordPress 網站提升到一個新的水平時,Hooks 讓我們堅持安全和最佳實踐。

Toptal 工程博客對 Fahad Murtaza 表示感謝,感謝他的專業知識、beta 測試和對本文的技術審查。