反射 API 核心類
| 類別 | 用途 |
ReflectionClass | 類別資訊:方法、屬性、介面、父類、註解、常數 |
ReflectionMethod | 方法資訊:參數、回傳型別、靜態/公開/私有、呼叫 |
ReflectionProperty | 屬性資訊:型別、可見性、預設值、讀寫 |
ReflectionFunction | 函式資訊 |
ReflectionParameter | 參數資訊 |
插件系統架構設計
介面契約 (IPlugin)
│
├── 實現類 A (MyCoolPlugin)
├── 實現類 B (AnotherPlugin)
└── 實現類 C (...)
│
▼
反射掃描器 (findPlugins)
│
├── 遍歷 get_declared_classes()
├── ReflectionClass::implementsInterface('IPlugin')
└── 收集符合類別
│
▼
動態呼叫器 (compute)
│
├── ReflectionMethod::isStatic() 判斷
├── 靜態:invoke(null)
├── 實例:newInstance() → invoke($instance)
└── 合併結果
完整實作程式碼
<?php
/**
* 插件介面契約
*/
interface IPlugin {
public static function getName(): string;
public function getMenuItems(): array;
public static function getArticles(): array;
}
/**
* 插件實現範例
*/
class MyCoolPlugin implements IPlugin {
public static function getName(): string {
return 'MyCoolPlugin';
}
public function getMenuItems(): array {
return [
['description' => 'MyCoolPlugin', 'link' => '/my-cool-plugin'],
];
}
public static function getArticles(): array {
return [
['path' => '/my-cool-plugin', 'title' => 'Really Cool Article', 'text' => 'Content...'],
];
}
}
/**
* 掃描所有實現指定介面的類別
* @param string $interfaceName
* @return ReflectionClass[]
*/
function findPlugins(string $interfaceName): array {
$plugins = [];
foreach (get_declared_classes() as $className) {
$refClass = new ReflectionClass($className);
if ($refClass->implementsInterface($interfaceName)) {
$plugins[] = $refClass;
}
}
return $plugins;
}
/**
* 動態呼叫所有插件的指定方法並合併結果
* @param string $methodName
* @param string $interfaceName
* @return array
*/
function compute(string $methodName, string $interfaceName): array {
$plugins = findPlugins($interfaceName);
$results = [];
foreach ($plugins as $plugin) {
if (!$plugin->hasMethod($methodName)) continue;
$method = $plugin->getMethod($methodName);
if ($method->isStatic()) {
// 靜態方法直接呼叫
$items = $method->invoke(null);
} else {
// 實例方法需建立實例
$instance = $plugin->newInstance();
$items = $method->invoke($instance);
}
$results = array_merge($results, is_array($items) ? $items : [$items]);
}
return $results;
}
// ===== 使用示範 =====
$menu = compute('getMenuItems', 'IPlugin');
$articles = compute('getArticles', 'IPlugin');
$names = compute('getName', 'IPlugin');
echo "Menu:\n"; print_r($menu);
echo "\nArticles:\n"; print_r($articles);
echo "\nNames:\n"; print_r($names);
關鍵反射方法對比
| 場景 | 方法 | 說明 |
| 檢查類別實現介面 | $refClass->implementsInterface('IPlugin') | 包含父類實現的介面 |
| 檢查方法存在 | $refClass->hasMethod('methodName') | 含繼承方法 |
| 取得方法反射物件 | $refClass->getMethod('methodName') | 返回 ReflectionMethod |
| 判斷靜態方法 | $refMethod->isStatic() | boolean |
| 呼叫靜態方法 | $refMethod->invoke(null, ...$args) | 第一參數 null |
| 建立實例 | $refClass->newInstance(...$args) | 支援建構子參數 |
| 呼叫實例方法 | $refMethod->invoke($instance, ...$args) | 第一參數為實例 |
| 呼叫任意參數 | $refMethod->invokeArgs($instance, $argsArray) | 參數陣列 |
new Class() vs new ReflectionClass() 差異
$obj = new MyClass(); // 1. 實例化物件,受可見性限制 (private 無法存取)
$ref = new ReflectionClass('MyClass'); // 2. 取得類別「元資料物件」 (含 private 方法/屬性)
// 反射可做 new 做不到的事:
$ref->getMethod('privateMethod')->setAccessible(true); // 突破私有限制
$ref->newInstanceWithoutConstructor(); // 不呼叫建構子建立實例
$ref->getProperties(ReflectionProperty::IS_PRIVATE); // 列出私有屬性
$ref->getDocComment(); // 讀取 PHPDoc 註解
實務應用場景
- 🔌 **插件/模組系統** (WordPress、Laravel Service Provider、Symfony Bundle)
- 📝 **註解路由/驗證/ORM 映射** (Doctrine、Laravel Route::resource)
- 🧪 **測試框架** (PHPUnit 自動發現測試方法、資料提供者)
- 🔄 **序列化/轉換器** (JMS Serializer、Fractal Transformer)
- 🏗 **依賴注入容器** (自動解析建構子參數、介面綁定)
- 📊 **程式碼分析/生成器** (PHPStan、Laravel IDE Helper、API 文檔生成)
效能提醒
- 反射較直接呼叫慢 10-100 倍,**避免熱路徑大量使用**
- 啟動階段掃描、建立快取、運行期直接讀取快取
- 生產環境建議配合
opcache、classmap 自動載入
PHP 反射 API 是框架級開發的核心工具,掌握「掃描 → 篩選 → 動態呼叫」模式,即可構建高擴展性的插件架構與元編程系統。