PHP 反射 API(Reflection API)是一套用於在運行時分析類、接口、函數、方法、屬性等代碼結構的內置工具集。它允許程序 “自審”(introspection),獲取代碼元信息並動態操作(如調用私有方法、訪問私有屬性),是框架開發、依賴注入、ORM 等高級場景的核心技術。
一、反射 API 核心作用
二、反射 API 核心類與結構
|
類名
|
作用
|
常用場景
|
|
|
分析類 / 接口 /trait 的元信息
|
獲取類屬性、方法、父類、註解等
|
|
|
分析類的方法(含靜態 / 私有 / 受保護方法)
|
調用私有方法、獲取方法參數 / 修飾符
|
|
|
分析類的屬性(含靜態 / 私有 / 受保護屬性)
|
讀寫私有屬性、獲取屬性修飾符
|
|
|
分析函數 / 方法的參數
|
獲取參數類型、默認值、是否必填
|
|
|
分析全局函數(非類方法)
|
全局函數元信息獲取
|
|
|
分析 PHP 擴展(如 mysqli、redis)
|
擴展功能、常量、函數查詢
|
三、反射 API 實戰示例(核心場景)
1. 準備測試類
php運行
/**
* 測試反射的用户類
* @author 測試作者
* @version 1.0
*/
class User {
// 公共屬性
public $name;
// 受保護屬性
protected $age = 18;
// 私有屬性
private $email = 'user@example.com';
// 靜態屬性
public static $count = 0;
/**
* 構造方法
* @param string $name 用户名
*/
public function __construct(string $name) {
$this->name = $name;
self::$count++;
}
/**
* 公共方法
*/
public function getInfo(): string {
return "姓名:{$this->name},年齡:{$this->age}";
}
/**
* 私有方法
* @param string $prefix 前綴
* @return string 拼接後的郵箱
*/
private function getEmail(string $prefix): string {
return $prefix . '_' . $this->email;
}
/**
* 受保護的靜態方法
*/
protected static function getCount(): int {
return self::$count;
}
}
2. 場景 1:分析類的元信息(ReflectionClass)
php運行
// 1. 初始化 ReflectionClass(傳入類名,無需實例化)
$refClass = new ReflectionClass(User::class);
// 2. 獲取類的基礎信息
echo "類名:" . $refClass->getName() . "\n"; // 輸出:User
echo "父類:" . ($refClass->getParentClass() ? $refClass->getParentClass()->getName() : '無') . "\n"; // 輸出:無
echo "是否為類:" . ($refClass->isClass() ? '是' : '否') . "\n"; // 輸出:是
echo "是否有構造方法:" . ($refClass->hasMethod('__construct') ? '是' : '否') . "\n"; // 輸出:是
// 3. 獲取類的註解(註釋)
$docComment = $refClass->getDocComment();
echo "類註解:\n" . $docComment . "\n";
// 輸出:
// /**
// * 測試反射的用户類
// * @author 測試作者
// * @version 1.0
// */
// 4. 獲取類的所有屬性(含私有/受保護)
$properties = $refClass->getProperties(); // 獲取所有屬性(默認過濾繼承屬性)
foreach ($properties as $prop) {
echo "屬性名:" . $prop->getName() . ",修飾符:";
// 判斷屬性修飾符
if ($prop->isPublic()) echo "public ";
if ($prop->isProtected()) echo "protected ";
if ($prop->isPrivate()) echo "private ";
if ($prop->isStatic()) echo "static";
echo "\n";
}
// 輸出:
// 屬性名:name,修飾符:public
// 屬性名:age,修飾符:protected
// 屬性名:email,修飾符:private
// 屬性名:count,修飾符:public static
// 5. 獲取類的所有方法
$methods = $refClass->getMethods();
foreach ($methods as $method) {
echo "方法名:" . $method->getName() . ",修飾符:";
if ($method->isPublic()) echo "public ";
if ($method->isPrivate()) echo "private ";
if ($method->isProtected()) echo "protected ";
if ($method->isStatic()) echo "static";
echo "\n";
}
// 輸出:
// 方法名:__construct,修飾符:public
// 方法名:getInfo,修飾符:public
// 方法名:getEmail,修飾符:private
// 方法名:getCount,修飾符:protected static
3. 場景 2:動態操作屬性(ReflectionProperty)
php運行
// 1. 實例化目標類
$user = new User("張三");
// 2. 操作公共屬性(直接訪問也可,但反射統一語法)
$refNameProp = $refClass->getProperty('name');
echo "原始姓名:" . $refNameProp->getValue($user) . "\n"; // 輸出:張三
$refNameProp->setValue($user, "李四"); // 修改公共屬性
echo "修改後姓名:" . $user->name . "\n"; // 輸出:李四
// 3. 操作受保護屬性(age):需先設置可訪問
$refAgeProp = $refClass->getProperty('age');
$refAgeProp->setAccessible(true); // 突破訪問限制(關鍵)
echo "原始年齡:" . $refAgeProp->getValue($user) . "\n"; // 輸出:18
$refAgeProp->setValue($user, 20); // 修改受保護屬性
echo "修改後年齡:" . $refAgeProp->getValue($user) . "\n"; // 輸出:20
// 4. 操作私有屬性(email)
$refEmailProp = $refClass->getProperty('email');
$refEmailProp->setAccessible(true);
echo "私有郵箱:" . $refEmailProp->getValue($user) . "\n"; // 輸出:user@example.com
$refEmailProp->setValue($user, "lisi@example.com");
echo "修改後郵箱:" . $refEmailProp->getValue($user) . "\n"; // 輸出:lisi@example.com
// 5. 操作靜態屬性(count)
$refCountProp = $refClass->getProperty('count');
echo "靜態屬性 count:" . $refCountProp->getValue() . "\n"; // 靜態屬性無需傳實例,輸出:1
$refCountProp->setValue(2); // 修改靜態屬性
echo "修改後 count:" . User::$count . "\n"; // 輸出:2
4. 場景 3:動態調用方法(ReflectionMethod)
php運行
// 1. 調用公共方法(getInfo)
$refGetInfo = $refClass->getMethod('getInfo');
$info = $refGetInfo->invoke($user); // 調用實例方法,傳入實例
echo "公共方法返回:" . $info . "\n"; // 輸出:姓名:李四,年齡:20
// 2. 調用私有方法(getEmail):先設置可訪問
$refGetEmail = $refClass->getMethod('getEmail');
$refGetEmail->setAccessible(true); // 突破訪問限制
// 調用方法並傳參:invoke(實例, 參數1, 參數2, ...)
$email = $refGetEmail->invoke($user, "prefix");
echo "私有方法返回:" . $email . "\n"; // 輸出:prefix_lisi@example.com
// 3. 調用靜態受保護方法(getCount)
$refGetCount = $refClass->getMethod('getCount');
$refGetCount->setAccessible(true);
$count = $refGetCount->invoke(null); // 靜態方法傳 null 即可
echo "靜態受保護方法返回:" . $count . "\n"; // 輸出:2
// 4. 獲取方法參數信息(以 __construct 為例)
$refConstructor = $refClass->getMethod('__construct');
$params = $refConstructor->getParameters();
foreach ($params as $param) {
echo "參數名:" . $param->getName() . ",";
echo "類型:" . ($param->getType() ? $param->getType()->getName() : '無') . ",";
echo "是否必填:" . ($param->isOptional() ? '否' : '是') . "\n";
}
// 輸出:參數名:name,類型:string,是否必填:是
5. 場景 4:通過反射創建類實例(無需直接 new)
php運行
// 方式 1:直接傳構造參數
$refClass = new ReflectionClass(User::class);
$user1 = $refClass->newInstance("王五"); // 等價於 new User("王五")
echo $user1->getInfo() . "\n"; // 輸出:姓名:王五,年齡:18
// 方式 2:通過參數數組傳參(適合參數較多時)
$user2 = $refClass->newInstanceArgs(["趙六"]);
echo $user2->getInfo() . "\n"; // 輸出:姓名:趙六,年齡:18
// 方式 3:無參構造(若類支持)
// $user3 = $refClass->newInstanceWithoutConstructor(); // 跳過構造方法創建實例
6. 場景 5:解析註解(註釋中的元數據)
php運行
/**
* 解析註解的工具函數
* @param ReflectionClass|ReflectionMethod|ReflectionProperty $ref 反射實例
* @return array 註解數組
*/
function parseAnnotations($ref): array {
$doc = $ref->getDocComment();
if (!$doc) return [];
$annotations = [];
// 正則匹配 @key value 格式的註解
preg_match_all('/@(\w+)\s*(.*?)\s*/', $doc, $matches);
foreach ($matches[1] as $index => $key) {
$annotations[$key] = trim($matches[2][$index]);
}
return $annotations;
}
// 1. 解析類註解
$classAnnos = parseAnnotations($refClass);
echo "類作者:" . $classAnnos['author'] . "\n"; // 輸出:測試作者
echo "類版本:" . $classAnnos['version'] . "\n"; // 輸出:1.0
// 2. 解析方法註解(getEmail)
$refGetEmail = $refClass->getMethod('getEmail');
$methodAnnos = parseAnnotations($refGetEmail);
echo "方法參數註解:" . $methodAnnos['param'] . "\n"; // 輸出:string $prefix 前綴
四、反射 API 的優缺點
優點
缺點
五、適用場景與禁忌
推薦場景
不推薦場景
六、總結
- 框架 / 工具開發:大膽使用,反射是實現複雜邏輯的高效方案;
- 業務開發:優先通過類的公共接口編程,僅在必要時(如無接口可調用)使用反射,並做好註釋與異常處理