PHP 反射 API(Reflection API)是一套用於在運行時分析類、接口、函數、方法、屬性等代碼結構的內置工具集。它允許程序 “自審”(introspection),獲取代碼元信息並動態操作(如調用私有方法、訪問私有屬性),是框架開發、依賴注入、ORM 等高級場景的核心技術。

一、反射 API 核心作用

  1. 元信息獲取:獲取類的名稱、父類、接口、屬性、方法、參數、註解(註釋)等信息;
  2. 動態操作:無需實例化類即可調用方法、修改屬性(包括私有 / 受保護成員);
  3. 代碼分析:自動化文檔生成、單元測試工具、代碼檢查(如 PSR 規範校驗);
  4. 解耦與靈活編程:依賴注入(DI)容器、IOC 框架(如 Laravel、Symfony)的核心實現。

二、反射 API 核心類與結構

PHP 反射 API 以 Reflection 為基類,衍生出一系列針對不同代碼結構的子類,核心類如下:

類名

作用

常用場景

ReflectionClass

分析類 / 接口 /trait 的元信息

獲取類屬性、方法、父類、註解等

ReflectionMethod

分析類的方法(含靜態 / 私有 / 受保護方法)

調用私有方法、獲取方法參數 / 修飾符

ReflectionProperty

分析類的屬性(含靜態 / 私有 / 受保護屬性)

讀寫私有屬性、獲取屬性修飾符

ReflectionParameter

分析函數 / 方法的參數

獲取參數類型、默認值、是否必填

ReflectionFunction

分析全局函數(非類方法)

全局函數元信息獲取

ReflectionExtension

分析 PHP 擴展(如 mysqli、redis)

擴展功能、常量、函數查詢

核心關係:ReflectionClass 可通過 getMethod()/getProperty()/getParameters() 等方法,獲取對應 ReflectionMethod/ReflectionProperty/ReflectionParameter 實例,進而操作具體成員。

三、反射 API 實戰示例(核心場景)

以下通過「類分析 + 動態操作」的組合示例,覆蓋反射最常用場景。

1. 準備測試類

先定義一個包含不同修飾符(public/private/protected)、靜態成員、註解的測試類,用於後續演示:

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)

通過 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)

ReflectionClass 提供 newInstance() 方法,可動態傳入構造參數創建實例,適合依賴注入場景:

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:解析註解(註釋中的元數據)

反射可獲取類 / 方法 / 屬性的註釋(docComment),通過正則解析自定義註解(如 @author@version):

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 的優缺點

優點

  1. 靈活性極高:突破訪問限制,動態操作類成員,適配框架級複雜需求;
  2. 元信息可視化:無需閲讀源碼,即可分析代碼結構,適合工具開發;
  3. 解耦:依賴注入、IOC 等模式的核心,減少代碼硬依賴。

缺點

  1. 性能開銷:反射涉及大量元信息解析,比直接操作(如 $user->name)慢數倍,高頻場景需謹慎;
  2. 破壞封裝性:可訪問私有成員,可能違背類的設計初衷,增加代碼維護成本;
  3. 代碼可讀性降低:動態操作邏輯較隱蔽,不如直接調用直觀。

五、適用場景與禁忌

推薦場景

  1. 框架開發:依賴注入容器(如 Laravel DI)、路由解析、ORM 映射(如 Eloquent 模型屬性解析);
  2. 工具開發:自動化文檔生成(如 PHPDoc)、單元測試框架(如 PHPUnit 模擬私有方法);
  3. 動態擴展:插件系統、鈎子機制(通過反射調用自定義回調)。

不推薦場景

  1. 高頻業務邏輯:如循環中頻繁反射操作,會嚴重影響性能;
  2. 簡單業務開發:普通場景直接調用類成員即可,無需過度設計;
  3. 破壞封裝的濫用:非必要情況下,不要用反射訪問私有成員(應通過類提供的公共接口操作)。

六、總結

PHP 反射 API 是 “高級玩家” 的核心工具,核心價值在於運行時代碼分析與動態操作。它的設計初衷是為框架、工具提供底層支持,而非替代常規編程方式。

使用時需平衡 “靈活性” 與 “性能 / 可維護性”:

  • 框架 / 工具開發:大膽使用,反射是實現複雜邏輯的高效方案;
  • 業務開發:優先通過類的公共接口編程,僅在必要時(如無接口可調用)使用反射,並做好註釋與異常處理