反射(Reflection)
PHP的反射機制提供了一套反射API,用來訪問和使用類、方法、屬性、參數和註釋等,比如可以通過一個對象知道這個對象所屬的類,這個類包含哪些方法,這些方法需要傳入什麼參數,每個參數是什麼類型等等,不用創建類的實例也可以訪問類的成員和方法,就算類成員定義爲 private
也可以在外部訪問。
官方文檔提供了諸如 ReflectionClass
、ReflectionMethod
、ReflectionObject
、ReflectionExtension
等反射類及相應的API,用得最多的是 ReflectionClass
。
爲了演示反射效果,首先創建一個類(假設定義了一個類 User
),並實例化。基於這個實例,反射類可以訪問 User
中的屬性和方法。
<?php
/**
* 用戶相關類
*/
class User {
public $username;
private $password;
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
/**
* 獲取用戶名
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* 設置用戶名
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}
/**
* 獲取密碼
* @return string
*/
private function getPassword()
{
return $this->password;
}
/**
* 設置密碼
* @param string $password
*/
private function setPassowrd($password)
{
$this->password = $password;
}
}
創建反射類實例
$refClass = new ReflectionClass(new User('liulu', '123456'));
// 也可以寫成
$refClass = new ReflectionClass('User'); // 將類名User作爲參數,建立User類的反射類
反射屬性
$properties = $refClass->getProperties(); // 獲取User類的所有屬性,返回ReflectionProperty的數組
$property = $refClass->getProperty('password'); // 獲取User類的password屬性
//$properties 結果如下:
Array (
[0] => ReflectionProperty Object ( [name] => username [class] => User )
[1] => ReflectionProperty Object ( [name] => password [class] => User )
)
//$property 結果如下:
ReflectionProperty Object ( [name] => password [class] => User )
反射方法
$methods = $refClass->getMethods(); // 獲取User類的所有方法,返回ReflectionMethod數組
$method = $refClass->getMethod('getUsername'); // 獲取User類的getUsername方法
//$methods 結果如下:
Array (
[0] => ReflectionMethod Object ( [name] => __construct [class] => User )
[1] => ReflectionMethod Object ( [name] => getUsername [class] => User )
[2] => ReflectionMethod Object ( [name] => setUsername [class] => User )
[3] => ReflectionMethod Object ( [name] => getPassword [class] => User )
[4] => ReflectionMethod Object ( [name] => setPassowrd [class] => User )
)
//$method 結果如下:
ReflectionMethod Object ( [name] => getUsername [class] => User )
反射註釋
$classComment = $refClass->getDocComment(); // 獲取User類的註釋文檔,即定義在類之前的註釋
$methodComment = $refClass->getMethod('setPassowrd')->getDocComment(); // 獲取User類中setPassowrd方法的註釋
//$classComment 結果如下:
/** * 用戶相關類 */
//$methodComment 結果如下:
/** * 設置密碼 * @param string $password */
反射實例化
$instance = $refClass->newInstance('admin', 123, '***'); // 從指定的參數創建一個新的類實例
//$instance 結果如下:
User Object ( [username] => admin [password:User:private] => 123 )
注:雖然構造函數中是兩個參數,但是newInstance方法接受可變數目的參數,用於傳遞到類的構造函數。
$params = ['xiaoming', 'asdfg'];
$instance = $refClass->newInstanceArgs($params); // 從給出的參數創建一個新的類實例
//$instance 結果如下:
User Object ( [username] => xiaoming [password:User:private] => asdfg )
訪問、執行類的公有方法——public
$instance->setUsername('admin_1'); // 調用User類的實例調用setUsername方法設置用戶名
$username = $instance->getUsername(); // 用過User類的實例調用getUsername方法獲取用戶名
echo $username . "\n"; // 輸出 admin_1
// 也可以寫成
$refClass->getProperty('username')->setValue($instance, 'admin_2'); // 通過反射類ReflectionProperty設置指定實例的username屬性值
$username = $refClass->getProperty('username')->getValue($instance); // 通過反射類ReflectionProperty獲取username的屬性值
echo $username . "\n"; // 輸出 admin_2
// 還可以寫成
$refClass->getMethod('setUsername')->invoke($instance, 'admin_3'); // 通過反射類ReflectionMethod調用指定實例的方法,並且傳送參數
$value = $refClass->getMethod('getUsername')->invoke($instance); // 通過反射類ReflectionMethod調用指定實例的方法
echo $value . "\n"; // 輸出 admin_3
訪問、執行類的非公有方法——private、protected
try {
// 正確寫法
$property = $refClass->getProperty('password'); // ReflectionProperty Object ( [name] => password [class] => User )
$property->setAccessible(true); // 修改 $property 對象的可訪問性
$property->setValue($instance, '987654321'); // 可以執行
$value = $property->getValue($instance); // 可以執行
echo $value . "\n"; // 輸出 987654321
// 錯誤寫法
$refClass->getProperty('password')->setAccessible(true); // 臨時修改ReflectionProperty對象的可訪問性
$refClass->getProperty('password')->setValue($instance, 'password'); // 不能執行,拋出不能訪問異常
$refClass = $refClass->getProperty('password')->getValue($instance); // 不能執行,拋出不能訪問異常
$refClass = $instance->password; // 不能執行,類本身的屬性沒有被修改,仍然是private
} catch (Exception $e){
echo $e;
}
// 錯誤寫法 結果如下:
ReflectionException: Cannot access non-public member User::password in xxx.php
小結
- 不管反射類中定義的屬性、方法是否爲
public
,都可以獲取到。 - 直接訪問
protected
或則private
的屬性、方法,會拋出異常。 - 訪問非公有成員需要調用指定的
ReflectionProperty
或ReflectionMethod
對象setAccessible(true)
方法。