眾多 PHP 開發者習慣用 include_once/require_once 防止重複載入,但這在啟用 **APC/OPcache** 時會導致**額外檔案系統呼叫**,抵消 opcode 快取優勢,甚至觸發 Cannot redeclare class 致命錯誤。本文從 PHP 內核機制剖析根因,建議改用 include + 自動載入器。
EG(included_files) 快取,命中則直接返回。opened_path(真實路徑,含 symlink 解析)。EG(included_files):以 opened_path 為鍵查找,命中則返回。compile_file(被 APC/OPcache 劫持,從快取讀取 opcode)。**關鍵問題**:步驟 2 **必須開啟檔案** 才能取得 opened_path,這產生一次 **額外的 open() 系統呼叫**,APC 正是為避免此呼叫而設計快取機制。
APC 引入 apc.include_once_override=1,劫持 ZEND_INCLUDE_OR_EVAL opcode,嘗試用 stat() 解析路徑並改寫 opcode 為 include。但實作長期存在缺陷:
<?php
set_include_path("/tmp");
function a($arg = array()) {
include_once("b.php");
}
a();
a(); // 觸發致命錯誤:Cannot redeclare class
b.php 含 class B {}。APC 錯誤判定未載入而改寫為 include,導致重複定義類別。
include_once 產生額外 open()/stat(),高併發下可觀。include_once 兜底,意味著**載入順序不受控、依賴關係不明確**,違反單一職責與依賴倒置原則。classmap),從根源解決問題。// composer.json
{
"autoload": {
"psr-4": {
"App\\": "src/"
},
"files": ["src/helpers.php"] // 函數檔案
}
}
// 執行 composer dump-autoload -o 生成優化 classmap
// 代碼中直接使用類別,零 include
use App\Service\UserService;
$userService = new UserService();
// 極少數需動態載入(插件系統)時,使用 include + 手動快取
$loaded = [];
function loadPlugin($name) {
global $loaded;
if (isset($loaded[$name])) return true;
$file = __DIR__ . "/plugins/$name.php";
if (file_exists($file)) {
include $file; // 不用 _once
$loaded[$name] = true;
return true;
}
return false;
}
include_once/require_once。autoload.files 或建立 bootstrap.php 統一 require(不需 _once)。使用 include_once 只能證明你對自己的代碼架構沒信心。 擁抱自動載入,擁抱確定性與效能。