很多人第一次碰子主題的 functions.php,都以為它跟 page.php、header.php 這些模板檔一樣,子主題放一份同名檔案就會「蓋掉」父主題那份。實際跑下去才發現整站白畫面,錯誤訊息寫著 Cannot redeclare function。問題就出在這裡:子主題 functions.php 的運作邏輯,跟其他模板檔完全相反。
模板檔是「取代」,functions.php 是「疊加」。子主題的 functions.php 不會覆蓋父主題的版本,兩份檔案都會被載入,而且子主題那份還比父主題早執行。搞懂這個載入順序,才知道為什麼有時候你的程式碼明明寫對了卻沒生效,也才知道要覆寫父主題的功能該用哪一招。
這篇會把子主題 functions.php 的載入時機講清楚,示範三種覆寫父主題函式的正確做法,並補上多數教學跳過的踩雷點:remove_action 在子主題裡為什麼常常失效、區塊主題(block theme)時代 functions.php 還需不需要手動掛載樣式。看完你會知道每一行該放哪裡、為什麼放那裡。
子主題的 functions.php 跟模板檔差在哪裡
一句話先講結論:子主題的模板檔(如 page.php)會取代父主題的同名檔,但 functions.php 不會取代,而是兩份都載入、子主題先跑。
WordPress 載入主題時,模板檔走的是「子主題優先」的查找邏輯。當頁面要用 page.php,WordPress 先看子主題資料夾有沒有,有就用子主題那份、完全略過父主題。這就是模板覆寫,子主題放一份同名檔案即可。
functions.php 走的是另一條路。WordPress 不會在兩份 functions.php 之間二選一,而是兩份都 include 進來,順序是子主題的 functions.php 先載入,緊接著載入父主題的 functions.php。兩份檔案裡的程式碼會合在一起共同運作。
這個差異帶來一個直接後果:你不能靠「在子主題寫一個同名函式」來覆蓋父主題的函式。兩份檔案都被載入,等於同一個函式名被宣告兩次,PHP 會直接丟出 Cannot redeclare function 的致命錯誤,整站變白畫面。要安全地改寫父主題的行為,得用後面講的三種方法,而不是複製貼上同名函式。
子主題 functions.php 的載入順序與時機
子主題的 functions.php 在父主題 functions.php 之前載入,這個順序是 WordPress 寫死的,目的是讓子主題有機會「先佈署條件」,再讓父主題的程式碼接手。
把一次請求的主題初始化想成一條時間軸,順序大致如下:
functions.php
先載入
functions.php
後載入
setup_theme
主題設定
初始化
scripts
掛載樣式腳本
兩份 functions.php 載入的階段,PHP 只做一件事:把檔案裡的 add_action、add_filter 等掛勾「登記」起來,這時候大部分函式還沒真正執行。真正執行是在後面的 after_setup_theme、init、wp_enqueue_scripts 這些動作(action)觸發時,依各掛勾登記的優先順序依序跑。
理解這條時間軸很重要,因為它解釋了一個矛盾:子主題的 functions.php 比父主題早載入,但你卻常常需要「在父主題之後」才動手。原因是載入順序(檔案被 include 的先後)跟執行順序(掛勾被觸發的先後)是兩回事。子主題雖然先被讀進來,但只要把工作掛在較晚觸發的動作上、或給較高的優先順序數字,實際執行時就會排在父主題後面。後面講 remove_action 的踩雷點時,這個觀念是關鍵。
子主題 functions.php 怎麼正確掛載樣式表
子主題要載入自己的 style.css,標準做法是在 functions.php 用 wp_enqueue_style 掛在 wp_enqueue_scripts 這個動作上,而不是沿用舊時代的 @import。要不要同時掛載父主題樣式,取決於父主題本身怎麼載入它的 CSS,沒有一體適用的標準寫法。
先講一個常見誤解。早年的教學會叫你在子主題的 style.css 開頭用 @import 拉父主題的樣式,這個做法現在不建議:@import 會讓瀏覽器多一次序列下載、拖慢載入。現行做法一律改用 functions.php 裡的 wp_enqueue_style。
關鍵在於:你必須先去看父主題的 functions.php,找出它是怎麼掛載樣式的,再決定子主題要寫什麼。實務上分三種情況。
父主題只用 get_stylesheet_uri 載入「當前啟用主題」的 style.css:因為子主題啟用時,get_stylesheet_uri 指向的就是子主題的 style.css,父主題那行程式碼會自動幫你載入子主題的樣式,你的 style.css 不需要自己掛。但這種情況下父主題自己的 CSS 反而沒被載到,你得在子主題補掛父主題樣式:
add_action( 'wp_enqueue_scripts', 'mychild_enqueue_styles' );
function mychild_enqueue_styles() {
wp_enqueue_style(
'mychild-parent-style',
get_parent_theme_file_uri( 'style.css' )
);
}
父主題用 get_template_directory_uri 明確載入自己的 style.css:父主題的 CSS 已經進來了,你只要再掛子主題自己的就好:
add_action( 'wp_enqueue_scripts', 'mychild_enqueue_styles' );
function mychild_enqueue_styles() {
wp_enqueue_style(
'mychild-style',
get_stylesheet_uri()
);
}
父主題同時載入父、子兩份樣式:這是最省事的情況,子主題完全不用動 functions.php,樣式會自動載入。
這裡有兩個函式很容易搞混,但在子主題裡會影響你到底拉到哪一份檔案:
- get_stylesheet_uri:回傳「當前啟用主題」的 style.css 網址。在子主題環境下,它指向子主題的 style.css。
- get_template_directory_uri:永遠回傳父主題的目錄網址,就算你正在用子主題也一樣。
- get_stylesheet_directory_uri:回傳子主題的目錄網址。
簡單記法是,名字裡有 template 的指向父主題,有 stylesheet 的指向子主題。要拉子主題自己的 JS 或圖片,就用 get_stylesheet_directory_uri;要明確抓父主題的檔案,才用 template 系列。
如果你掛了子主題樣式卻發現沒蓋過父主題,先檢查載入順序。CSS 是後載入的優先權重高,子主題樣式必須排在父主題之後才會生效。若父主題的 enqueue 用了較高優先順序,你可以在 add_action 的第四個參數給子主題函式一個更大的數字,讓它排到最後執行。
覆寫父主題函式的三種正確方法
子主題沒辦法靠同名函式覆蓋父主題的函式,正確的覆寫方式有三種:可插拔函式(pluggable function)、調整優先順序、用 remove_action 卸掉再重掛。選哪一種,取決於父主題的函式是怎麼寫的、掛在哪個掛勾上。
用 function_exists 包住的可插拔函式怎麼覆寫
如果父主題的函式被 if ( ! function_exists(…) ) 包住,這叫可插拔函式,你只要在子主題寫一個同名函式就能覆蓋它。
可插拔函式的原理是父主題在宣告函式前先檢查「這個名字是不是已經被宣告過了」,沒被宣告才宣告。父主題程式碼長這樣:
if ( ! function_exists( 'theme_setup' ) ) {
function theme_setup() {
// 父主題的預設行為
}
}
因為子主題的 functions.php 先載入,你在子主題裡先宣告了同名的 theme_setup,等父主題 functions.php 載入時,function_exists 檢查發現名字已存在,就跳過自己那份。子主題的版本順理成章地接管:
function theme_setup() {
// 子主題改寫後的行為
}
這招最乾淨,但有前提:父主題得事先把函式寫成可插拔的。多數第三方主題只會把部分核心函式做成可插拔,所以動手前要先翻父主題的程式碼,確認目標函式有沒有被 function_exists 包住。沒包住就硬寫同名函式,等著吃 Cannot redeclare function 的致命錯誤。
父主題函式沒做可插拔時用優先順序覆寫
如果父主題的函式掛在某個動作或過濾器(filter)上、又沒做成可插拔,你可以在子主題把自己的函式掛到同一個掛勾,但給更大的優先順序數字,讓它最後執行、蓋過前面的結果。
WordPress 執行同一個掛勾上的函式時,依優先順序數字由小到大跑,數字越大越晚執行。沒指定優先順序時預設是 10。假設父主題這樣寫:
add_action( 'wp_enqueue_scripts', 'parent_enqueue', 10 );
你在子主題就給一個比 10 大的數字,例如 15,讓子主題的函式排在父主題之後跑:
function mychild_enqueue() {
// 蓋掉或補強父主題的設定
}
add_action( 'wp_enqueue_scripts', 'mychild_enqueue', 15 );
這招適合「在父主題的結果上再修改」的情境,例如父主題加了某段樣式、你想用更高優先權的設定覆蓋掉。要注意它不會阻止父主題的函式執行,只是讓你的函式更晚跑、結果以你的為準。如果父主題那段邏輯根本不該執行,這招就不夠,得用第三種。
用 remove_action 與 remove_filter 直接卸掉父主題函式
當你需要的是「父主題那個函式根本別跑」,就用 remove_action 或 remove_filter 把它從掛勾上卸下來。父主題用 add_action 掛的就用 remove_action 卸,用 add_filter 掛的就用 remove_filter 卸。
寫法看起來很簡單,卸掉一個掛在 init 上的父主題函式:
remove_action( 'init', 'parent_function' );
但這行直接放在子主題 functions.php 的最外層,幾乎一定失效。下一段會專門講這個坑。要卸掉時還有一個鐵則:如果父主題當初掛載時指定了優先順序,你卸載時必須帶上一模一樣的優先順序數字,否則卸不掉。父主題若是:
add_action( 'init', 'parent_function', 20 );
你就得這樣卸,第三個參數的 20 不能漏:
remove_action( 'init', 'parent_function', 20 );
卸掉之後,你通常會再掛一個自己的版本上去,達成完整的替換。
remove_action 在子主題裡為什麼常常失效
remove_action 寫在子主題 functions.php 的最外層卻沒作用,最常見的原因是時機不對:你想卸的那個掛勾,這時候父主題還沒掛上去,等於卸一個還不存在的東西。
回到前面那條時間軸。子主題的 functions.php 比父主題早載入,所以當子主題 functions.php 跑到 remove_action 那一行時,父主題的 functions.php 都還沒被讀進來,父主題的 add_action 自然也還沒執行。你要移除的掛勾此刻根本不存在,remove_action 找不到對象,靜悄悄地什麼都沒做。
解法是把 remove_action 包進一個「晚一點才觸發」的掛勾裡,等父主題確定已經掛載完成,再動手卸。常用的做法是包進 after_setup_theme 或 init,並給一個夠大的優先順序,確保排在父主題的掛載動作之後:
add_action( 'after_setup_theme', 'mychild_remove_parent', 11 );
function mychild_remove_parent() {
remove_action( 'init', 'parent_function' );
}
選哪個掛勾來包,要看父主題的函式掛在哪個階段。原則是:你用來包 remove_action 的那個動作,觸發時機必須晚於父主題掛載目標函式的時機。如果父主題是在 after_setup_theme 上掛東西,你就用一個比父主題優先順序更大的數字、或改用更晚的 init 來執行移除。
實務除錯時,照這個順序檢查:第一、優先順序數字有沒有跟父主題一致(卸載要對齊掛載的優先順序);第二、掛勾名稱跟函式名稱有沒有打錯一個字;第三、移除的時機是不是排在父主題掛載之後。三項都對,remove_action 才會確實生效。
區塊主題時代 functions.php 還需要做哪些事
WordPress 進入區塊主題(block theme)後,子主題的 functions.php 角色變輕了,但沒有消失。樣式與版面設定大多移到 theme.json 處理,functions.php 主要留給「theme.json 做不到的 PHP 邏輯」。
兩種主題的差別可以這樣分:
- 傳統主題(classic theme):CSS 多半得靠 functions.php 用 wp_enqueue_style 掛載,前面講的三種掛樣式情境都適用,functions.php 是樣式進場的主要管道。
- 區塊主題:外觀、字體、色票、間距這些大多寫在 theme.json,子主題要改樣式時優先改 theme.json,子主題的 theme.json 會覆蓋父主題的 theme.json。functions.php 不再是載入 CSS 的主要途徑。
那區塊主題的子主題 functions.php 還能做什麼?答案是處理 theme.json 與模板檔覆蓋不了的功能性需求,例如註冊自訂的區塊樣式變體、用 PHP 過濾器調整輸出、掛載額外的 JavaScript、或卸掉父主題某段你不想要的 PHP 邏輯。覆寫父主題函式的三種方法在區塊主題裡同樣有效,因為它們處理的是 PHP 行為,跟主題是傳統還是區塊無關。
要特別釐清的是層級關係。在區塊主題的世界裡,從底層到上層依序是 WordPress 內建的預設 theme.json、父主題、子主題,最上面還有一層使用者在後台「外觀」介面做的個人化調整,這層存在資料庫裡、優先權最高,等於一種存在資料庫而非檔案系統的「孫主題」。但要做可安裝、可移植的修改,標準層級仍只有父主題與子主題兩層,functions.php 就活在子主題這一層。
寫子主題 functions.php 時最該守住的幾個習慣
把前面的觀念落到日常開發,子主題 functions.php 寫得安全又好維護,靠的是幾個習慣,而不是死背某段程式碼。
第一、函式一律加前綴。子主題與父主題的 functions.php 會一起載入,只要兩邊出現同名又非可插拔的函式,整站就掛掉。給每個函式加上專屬前綴(例如以子主題名稱開頭),父子主題用不同前綴,撞名的風險幾乎歸零。
第二、改父主題行為前先讀父主題程式碼。要用哪一招覆寫,完全取決於目標函式是不是可插拔、掛在哪個掛勾、用了什麼優先順序。先打開父主題的 functions.php 看清楚,再決定用同名覆蓋、調優先順序、還是 remove_action,省下大量試錯。
第三、別把父主題的 functions.php 整段複製到子主題。複製過來等於宣告了一堆同名函式,幾乎必定觸發 Cannot redeclare function 的致命錯誤。子主題只放「你真正要新增或改寫」的程式碼。
第四、要引入子主題自己的檔案,用對的路徑函式。在子主題 functions.php 裡 require 一個放在子主題的 PHP 檔,用 get_theme_file_path 會自動以子主題目錄為基準;要抓圖片或樣式的網址,用 get_theme_file_uri。直接寫死路徑或誤用 template 系列函式,啟用子主題後常常抓錯到父主題去。
子主題 functions.php 真正的門檻不在語法,而在於分清楚「載入順序」與「執行順序」這兩件事:兩份檔案都會載入、子主題先載入,但實際生效的先後由掛勾的觸發時機與優先順序決定。掌握這條時間軸,你就知道掛樣式該選哪種寫法、覆寫函式該用哪一招、remove_action 該包在哪個掛勾裡。下一步,去翻一遍你正在用的父主題 functions.php,把它掛了哪些東西、用什麼優先順序記下來,這份清單會是你後續所有客製化的地圖。