許多 PHP 開發者知道 include_once 需要檢查已載入檔案列表,因此效能略遜於 include。但這只是冰山一角。本文將深入 PHP 核心原始碼與 APC 快取機制,揭示 include_once 在現代生產環境中隱藏的嚴重缺陷,並論證為何應堅持使用 include / require 搭配良好的架構設計。
當 PHP 遇到 include_once("2.php") 時,內部執行邏輯如下:
getcwd() 與 include_path 解析真實路徑(opened_path)。open() 檔案才能取得真實路徑。opened_path 後再次查找,未存在則進入編譯階段。compile_file 編譯 opcode。關鍵痛點:即便檔案已被載入,PHP 在 compile_file 前仍可能產生一次額外的 open() 系統呼叫(步驟 3)。
APC (Alternative PHP Cache) 透過劫持 compile_file 避免重複編譯與檔案讀取。然而 include_once 的邏輯導致:
include_once -> open() 系統呼叫 -> APC 劫持 compile_file -> 從快取返回 opcode
這意味著 每次 include_once 都會產生一次多餘的檔案系統 I/O,抵消了 Opcode 快取「避免 open」的核心優勢。
為解決此問題,APC 引入 apc.include_once_override=1 設定:
ZEND_INCLUDE_OR_EVAL opcode handler。stat() 解析絕對路徑。include 執行。然而,此機制實作極其複雜且充滿邊界 Bug。 經典失效案例:
<?php
set_include_path("/tmp");
function a($arg = array()) {
include_once("b.php");
}
a();
a();
?>
若 b.php 定義 class B {},啟用 include_once_override 時,第二次呼叫 a() 會拋出 Fatal error: Cannot redeclare class。原因在於 opcode 改寫邏輯在動態 include_path、函式作用域、預設參數等場景下判斷失誤,導致重複編譯。
使用 include_once 往往暴露了架構設計的缺失:
spl_autoload_register 實現「用到才載、精確載入一次」。最佳實踐:
include/require。require(檔案不存在視為嚴重錯誤,立即中止),並在架構層面保證單一入口、單一載入。include_once / require_once 看似提供了「安全網」,實則在效能(額外 I/O)、穩定性(APC override Bug)、架構(掩蓋設計缺陷)三個維度皆為負資產。棄用它們,擁抱自動載入與嚴謹的模組化設計,才是專業 PHP 工程師的正確選擇。