PHP 專案若每個類別都手動 require,維護極其困難。自動載入(Autoloading)讓類別在首次使用時自動載入對應檔案。本文詳細比較 舊式 __autoload() 魔術函式 與 現代 spl_autoload_register() 多載入器堆疊,並提供可直接使用的 PSR-4 相容實作。
project/ ├── autoload_demo.php # 測試腳本 ├── src/ │ ├── Autoload/ # 自動載入器實作 │ │ ├── ClassicLoader.php # __autoload 風格 │ │ └── SplLoader.php # spl_autoload_register 風格 │ └── Model/ │ ├── User.php │ └── Order.php
__autoload()(PHP 5.1-7.1,8.0 移除)<?php
// src/Autoload/ClassicLoader.php
function __autoload(string $className): void {
// 將命名空間分隔符 \ 轉為目錄分隔符
$file = __DIR__ . '/../' . str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
if (file_exists($file)) {
require_once $file;
} else {
// 開發環境拋出例外,正式環境記錄日誌
throw new RuntimeException("找不到類別檔案:{$className} → {$file}");
}
}
// 使用:直接 new 類別即可觸發
$user = new \Model\User();
$order = new \Model\Order();
缺點:全域僅能存在一個 __autoload,無法組合多個載入規則(如專案本體 + 第三方套件)。
spl_autoload_register() 多載入器堆疊(推薦,PHP 5.1+)<?php
// src/Autoload/SplLoader.php
class SplLoader {
private $baseDirs = [];
public function __construct(array $baseDirs = []) {
$this->baseDirs = $baseDirs;
}
public function register(): void {
// 註冊靜態方法為載入器,優先序預設最後註冊最高
spl_autoload_register([$this, 'loadClass']);
}
public function loadClass(string $className): void {
// PSR-4 風格:命名空間前綴對應基礎目錄
foreach ($this->baseDirs as $prefix => $baseDir) {
$len = strlen($prefix);
if (strncmp($className, $prefix, $len) !== 0) continue;
// 移除前綴,轉換路徑
$relativeClass = substr($className, $len);
$file = $baseDir . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';
if (file_exists($file)) {
require_once $file;
return; // 找到即返回,不繼續嘗試其他載入器
}
}
}
}
// ---------- 使用範例 ----------
$loader = new SplLoader([
'App\\' => __DIR__ . '/src', // 專案命名空間
'Vendor\\' => __DIR__ . '/vendor/src', // 第三方套件
]);
$loader->register();
// 也可註冊匿名函式作為載入器
spl_autoload_register(function($class) {
if (strpos($class, 'Legacy_') === 0) {
$file = __DIR__ . '/legacy/' . str_replace('_', '/', $class) . '.php';
if (file_exists($file)) require_once $file;
}
});
// 測試
$user = new \App\Model\User(); // 載入 src/Model/User.php
$legacy = new \Legacy_OldClass(); // 載入 legacy/OldClass.php
Composer 產生的 vendor/autoload.php 本質上就是高度優化的 spl_autoload_register 堆疊:
// vendor/composer/autoload_real.php 簡化版
$loader = require __DIR__ . '/ClassLoader.php';
$loader->register(true); // 註冊為自動載入器
// ClassLoader 核心邏輯
class ClassLoader {
private $prefixLengthsPsr4 = []; // 前綴長度索引,加速查找
private $prefixDirsPsr4 = []; // 前綴對應目錄陣列
public function loadClass($class) {
// 1. PSR-4 查找(最常用)
if ($file = $this->findFileWithExtension($class, '.php')) {
includeFile($file);
return true;
}
// 2. PSR-0 查找(舊式)
// 3. ClassMap 查找(大型專案預編譯對照表,最快)
return false;
}
}
| 方案 | 查找速度 | 適用場景 |
|---|---|---|
__autoload + 線性查找 | 慢 | 極小專案、教學 |
spl_autoload_register + 多載入器 | 中 | 中型專案、多命名空間 |
| Composer ClassMap(預編譯對照表) | 最快 | 大型專案、生產環境 |
| Composer PSR-4(動態對應) | 快 | 現代框架預設 |
// 兼容舊碼:將 __autoload 也註冊進堆疊
spl_autoload_register('__autoload'); // 函式名稱字串
// 或包裝為可呼叫物件
spl_autoload_register(function($class) {
__autoload($class); // 呼叫舊函式
});
理解 spl_autoload_register 的堆疊機制、PSR-4 對應規則、ClassMap 預編譯優化,是 PHP 現代化開發的必修課。新專案**直接使用 Composer** 管理自動載入;維護舊專案時,將零散的 require_once 重構為一個 SplLoader 實例並註冊,即可獲得清晰的模組邊界與載入效能。