反射讓 PHP 代碼具備自我檢查、動態調用、結構分析能力,是框架開發、插件系統、依賴注入容器、自動化測試、文檔生成的基石。ReflectionClass 是類級反射的入口。
<?php
class Demo {
public string $publicProp = 'public';
protected int $protectedProp = 123;
private bool $privateProp = true;
public const PUBLIC_CONST = 'const_value';
public function __construct(
public readonly string $name,
private string $secret = 'default'
) {}
public function publicMethod(string $arg): string { return $arg; }
protected function protectedMethod(): void {}
private function privateMethod(): int { return 42; }
public static function staticMethod(): string { return 'static'; }
}
// 獲取反射實例
$ref = new ReflectionClass(Demo::class);
// 或
$ref = new ReflectionClass(new Demo('test'));
// 基本信息
echo $ref->getName(); // Demo
echo $ref->getNamespaceName(); // 當前命名空間
echo $ref->getFileName(); // 文件路徑
echo $ref->getStartLine(); // 起始行
echo $ref->getEndLine(); // 結束行
echo $ref->getDocComment(); // 文檔註釋
// 修飾符檢查
$ref->isInstantiable(); // true
$ref->isAbstract(); // false
$ref->isInterface(); // false
$ref->isTrait(); // false
$ref->isFinal(); // false
$ref->isCloneable(); // true
$ref->isReadOnly(); // PHP 8.2+
?>
<?php
$ref = new ReflectionClass(Demo::class);
// 獲取所有屬性(含繼承)
$props = $ref->getProperties();
// 可過濾:ReflectionProperty::IS_PUBLIC | IS_PROTECTED | IS_PRIVATE | IS_STATIC
foreach ($props as $prop) {
echo "屬性: {$prop->getName()}\n";
echo " 可見性: " . $prop->getVisibility() . "\n"; // public/protected/private
echo " 是否靜態: " . ($prop->isStatic() ? '是' : '否') . "\n";
echo " 是否只讀: " . ($prop->isReadOnly() ? '是' : '否') . "\n"; // PHP 8.1+
echo " 是否鉤子: " . ($prop->isHooked() ? '是' : '否') . "\n"; // PHP 8.4+
echo " 默認值: ";
if ($prop->isDefault()) {
var_dump($prop->getDefaultValue());
} else {
echo "無默認值\n";
}
// 文檔註釋
echo " 註釋: " . ($prop->getDocComment() ?? '無') . "\n";
// 屬性屬性 (PHP 8.0+)
foreach ($prop->getAttributes() as $attr) {
echo " 屬性: " . $attr->getName() . "\n";
$instance = $attr->newInstance();
// 使用屬性實例...
}
}
// 動態訪問/修改(繞過可見性)
$obj = new Demo('test');
$prop = $ref->getProperty('privateProp');
$prop->setAccessible(true); // 關鍵:允許訪問私有屬性
echo $prop->getValue($obj); // true
$prop->setValue($obj, false);
// 靜態屬性無需實例
$staticProp = $ref->getProperty('someStatic');
$staticProp->setAccessible(true);
$staticProp->setValue(null, 'new value');
?>
<?php
$ref = new ReflectionClass(Demo::class);
// 獲取方法
$methods = $ref->getMethods(ReflectionMethod::IS_PUBLIC); // 過濾
foreach ($methods as $method) {
echo "方法: {$method->getName()}\n";
echo " 可見性: {$method->getVisibility()}\n";
echo " 是否靜態: " . ($method->isStatic() ? '是' : '否') . "\n";
echo " 是否抽象: " . ($method->isAbstract() ? '是' : '否') . "\n";
echo " 返回類型: " . ($method->getReturnType()?->getName() ?? '無') . "\n";
// 參數詳情
foreach ($method->getParameters() as $param) {
echo " 參數: \${$param->getName()}";
echo $param->isOptional() ? " = " . var_export($param->getDefaultValue(), true) : " (必填)";
echo " 類型: " . ($param->getType()?->getName() ?? 'mixed') . "\n";
echo " 是否引用: " . ($param->isPassedByReference() ? '是' : '否') . "\n";
echo " 是否可變參數: " . ($param->isVariadic() ? '是' : '否') . "\n";
}
// 方法屬性
foreach ($method->getAttributes() as $attr) {
// 處理屬性...
}
}
// 動態調用
$obj = new Demo('instance');
// 實例方法
$method = $ref->getMethod('publicMethod');
$result = $method->invoke($obj, 'argument'); // 等同 $obj->publicMethod('argument')
// 靜態方法
$staticMethod = $ref->getMethod('staticMethod');
$result = $staticMethod->invoke(null); // null 表示靜態調用
// 參數數組調用
$method->invokeArgs($obj, ['arg1', 'arg2']);
// 異常處理
try {
$method->invoke($obj, 'wrong_type'); // 類型錯誤拋出 TypeError
} catch (ReflectionException $e) {
// 方法不存在等
} catch (TypeError $e) {
// 參數類型不匹配
}
?>
<?php
$ref = new ReflectionClass(Demo::class);
// 檢查構造函數
$constructor = $ref->getConstructor();
if ($constructor) {
echo "參數數量: " . $constructor->getNumberOfParameters() . "\n";
echo "必填參數: " . $constructor->getNumberOfRequiredParameters() . "\n";
}
// 正常實例化
$obj1 = $ref->newInstance('constructor_arg');
// 帶參數實例化
$obj2 = $ref->newInstanceArgs(['constructor_arg']);
// 不調用構造函數創建(序列化反序列化、測試桿桿)
$obj3 = $ref->newInstanceWithoutConstructor();
// 手動初始化屬性...
$prop = $ref->getProperty('name');
$prop->setAccessible(true);
$prop->setValue($obj3, 'manual');
?>
<?php
namespace App\Plugin;
interface PluginInterface {
public static function getName(): string;
public static function getVersion(): string;
public static function getDescription(): string;
public function initialize(): void;
public function shutdown(): void;
}
interface EventSubscriberInterface {
public static function getSubscribedEvents(): array;
// 返回 ['event.name' => ['methodName', priority]]
}
?>
<?php
namespace App\Plugin;
class PluginManager {
private array $plugins = [];
private array $eventListeners = [];
private string $scanDir;
public function __construct(string $scanDir) {
$this->scanDir = $scanDir;
}
public function discover(): void {
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->scanDir, FilesystemIterator::SKIP_DOTS)
);
foreach ($files as $file) {
if ($file->getExtension() !== 'php') continue;
// 載入文件(自動註冊類)
require_once $file->getPathname();
}
// 掃描已聲明類
foreach (get_declared_classes() as $className) {
$ref = new ReflectionClass($className);
// 檢查是否實現插件接口
if ($ref->implementsInterface(PluginInterface::class) && !$ref->isAbstract()) {
$this->registerPlugin($ref);
}
}
}
private function registerPlugin(ReflectionClass $ref): void {
// 實例化
$instance = $ref->newInstance();
$name = $instance->getName();
if (isset($this->plugins[$name])) {
throw new \RuntimeException("插件名衝突: {$name}");
}
$this->plugins[$name] = [
'instance' => $instance,
'reflection' => $ref,
'metadata' => [
'version' => $instance->getVersion(),
'description' => $instance->getDescription(),
],
];
// 掃描事件訂閱
if ($ref->implementsInterface(EventSubscriberInterface::class)) {
$events = $instance->getSubscribedEvents();
foreach ($events as $eventName => $listener) {
[$method, $priority] = array_pad($listener, 2, ['onEvent', 0]);
$this->eventListeners[$eventName][] = [
'plugin' => $name,
'method' => $method,
'priority' => $priority,
];
}
// 按優先級排序
uasort($this->eventListeners[$eventName], fn($a, $b) => $b['priority'] <=> $a['priority']);
}
// 初始化
$instance->initialize();
}
public function dispatch(string $eventName, mixed $eventData = null): void {
foreach ($this->eventListeners[$eventName] ?? [] as $listener) {
$plugin = $this->plugins[$listener['plugin']]['instance'];
$method = $listener['method'];
if (method_exists($plugin, $method)) {
$plugin->$method($eventData);
}
}
}
public function getPlugin(string $name): ?array {
return $this->plugins[$name] ?? null;
}
public function getAllPlugins(): array {
return array_keys($this->plugins);
}
}
?>
<?php
// plugins/LoggerPlugin.php
namespace App\Plugins;
use App\Plugin\PluginInterface;
use App\Plugin\EventSubscriberInterface;
class LoggerPlugin implements PluginInterface, EventSubscriberInterface {
public static function getName(): string { return 'logger'; }
public static function getVersion(): string { return '1.0.0'; }
public static function getDescription(): string { return '請求日誌記錄插件'; }
public function initialize(): void {
echo "[Logger] 初始化完成\n";
}
public function shutdown(): void {
echo "[Logger] 關閉\n";
}
public static function getSubscribedEvents(): array {
return [
'request.received' => ['onRequestReceived', 100],
'response.sending' => ['onResponseSending', -100],
];
}
public function onRequestReceived(array $data): void {
// 記錄請求日誌
file_put_contents('logs/requests.log', json_encode($data) . "\n", FILE_APPEND);
}
public function onResponseSending(array $data): void {
// 記錄響應日誌
}
}
?>
<?php
namespace App\Reflection;
class ReflectionCache {
private static array $classCache = [];
private static array $methodCache = [];
private static array $propertyCache = [];
public static function getClass(string $className): ReflectionClass {
return self::$classCache[$className] ??= new ReflectionClass($className);
}
public static function getMethod(string $className, string $methodName): ReflectionMethod {
$key = $className . '::' . $methodName;
return self::$methodCache[$key] ??= self::getClass($className)->getMethod($methodName);
}
public static function getProperty(string $className, string $propName): ReflectionProperty {
$key = $className . '::' . $propName;
return self::$propertyCache[$key] ??= self::getClass($className)->getProperty($propName);
}
public static function clear(): void {
self::$classCache = [];
self::$methodCache = [];
self::$propertyCache = [];
}
}
// 使用
$ref = ReflectionCache::getClass(UserService::class);
$method = ReflectionCache::getMethod(UserService::class, 'getUser');
?>
| 陷阱 | 後果 | 解決方案 |
|---|---|---|
| 頻繁 new ReflectionClass() | 性能開銷大 | 緩存反射對象,或使用靜態工廠 |
| setAccessible(true) 濫用 | 破壞封裝、難以維護 | 僅在序列化、測試、框架底層必要時使用 |
| 忽略繼承鏈 | 遺漏父類屬性/方法 | getProperties()/getMethods() 默認含繼承,可用 DeclaringClass 過濾 |
| 反射調用不檢查參數 | 運行時 TypeError | 調用前驗證,或捕獲 TypeError 統一處理 |
| 匿名類反射 | getName() 返回匿名類標識 | 使用 isAnonymous() 判斷,避免作為鍵名 |
ReflectionClass 賦予 PHP 靈魂般的動態能力。核心應用場景:依賴注入容器自動裝配、插件/模塊熱發現、API 文檔自動生成、測試桿桿私有方法、ORM 實體映射、序列化器。掌握反射 API 的同時,務必建立緩存機制、可見性邊界意識、異常處理規範,避免濫用導致代碼難以靜態分析與維護。