在 PHP 開發中,include 與 require 是最基礎的代碼復用機制,但許多開發者僅停留在「報錯等級不同」的表層認知。本文從 **編譯時機、條件執行、循環行為、錯誤處理、Opcode 緩存影響** 五個維度,徹底釐清兩者本質區別,指導正確選型。
| 維度 | require / require_once | include / include_once |
|---|---|---|
| 編譯階段 | **編譯期** 解析、編譯、合並入主腳本 AST | **運行期** 遇到語句時才解析、編譯 |
| 條件執行 | 無條件:if(false){ require 'a.php'; } 依然會編譯 a.php | 有條件:if(false){ include 'a.php'; } 不會編譯 a.php |
| 循環動態載入 | 循環內每次迭代**重複載入同一文件** (早期版本) | 循環內可動態載入不同文件 include "file{$i}.php" |
| 失敗後果 | E_COMPILE_ERROR → 致命錯誤,腳本立即終止 | E_WARNING → 警告,腳本繼續執行 |
| Opcache 行為 | 編譯期確定依賴,利於靜態分析與預加載 | 運行期動態決定,難以靜態分析,預加載需顯式聲明 |
| 返回值處理 | 同 include,均可接收被包含文件 return 的值 | 同上 |
<?php
// config_prod.php 包含生產環境密鑰
// config_dev.php 包含測試環境密鑰
$env = 'prod';
// ❌ 錯誤:require 編譯期無條件載入,兩個文件都會被編譯
// 若 config_dev.php 有語法錯誤,生產環境也會崩潰
if ($env === 'prod') {
require 'config_prod.php';
} else {
require 'config_dev.php';
}
// ✅ 正確:include 運行期條件載入,僅編譯滿足條件的分支
if ($env === 'prod') {
include 'config_prod.php';
} else {
include 'config_dev.php';
}
<?php
// 需求:依次載入 module_1.php, module_2.php, module_3.php
// ❌ require 早期版本:每次循環都載入 module_1.php (編譯期展開)
// PHP 7+ 已修復,但語義仍為「無條件載入」
for ($i = 1; $i <= 3; $i++) {
require "module_{$i}.php"; // 不推薦,語義不清
}
// ✅ include:運行期動態解析文件名,完美支持
for ($i = 1; $i <= 3; $i++) {
include "module_{$i}.php"; // 推薦
}
<?php
// missing.php 不存在
// test_require.php
require 'missing.php';
echo "這行永遠不會執行\n"; // Fatal error 終止
// test_include.php
include 'missing.php';
echo "這行會正常輸出\n"; // Warning 後繼續
輸出對比:
// require
Warning: require(missing.php): failed to open stream...
Fatal error: require(): Failed opening required 'missing.php'...
// include
Warning: include(missing.php): failed to open stream...
這行會正常輸出
; php.ini
opcache.preload = /var/www/preload.php
// preload.php
// ✅ 靜態依賴:編譯期可解析,自動預加載
require 'core/Config.php';
require 'core/Database.php';
require 'core/Router.php';
// ⚠️ 動態依賴:運行期才確定,需顯式 opcache_compile_file()
$plugins = glob(__DIR__ . '/plugins/*.php');
foreach ($plugins as $plugin) {
include $plugin; // 不會被自動預加載
// 需額外:opcache_compile_file($plugin);
}
composer dump-autoload -o 生成類映射,極致性能file_exists() 或 is_readable() 預檢更安全function render(string $template, array $data): string {
extract($data);
ob_start();
include __DIR__ . "/templates/{$template}.php"; // 模板內可用 $var
return ob_get_clean();
}
模板屬於「視圖邏輯」,運行期動態決定,include 語義更貼切;且模板缺失不應導致整站崩潰(可降級顯示錯誤頁)。
// ❌ 反模式:每次請求重複編譯
foreach ($items as $item) {
include 'render_item.php';
}
// ✅ 優化:捕獲模板內容一次,循環渲染
$tpl = file_get_contents('render_item.php');
foreach ($items as $item) {
// 簡單替換或使用模板引擎
echo str_replace('{{name}}', $item['name'], $tpl);
}
| 版本 | require 循環行為 | include 返回值 | 致命錯誤可捕獲 |
|---|---|---|---|
| PHP 4 | 每次迭代重複編譯 | 支持 | 否 |
| PHP 5.3+ | 優化:同一文件僅編譯一次 | 支持 | 否 |
| PHP 7.0+ | 編譯期展開,循環無影響 | 支持 | 否 (E_COMPILE_ERROR 不可 try-catch) |
| PHP 8.0+ | 同 7.0,JIT 進一步優化 | 支持 | 否 |
「require是骨架,include是血肉。骨架必須堅不可摧,血肉可按需生長。」