簡介
就算是類成員定義為private也可以在外部訪問,不用創建類的實例也可以訪問類的成員和方法。
PHP自5.0版本以后添加了反射機制,它提供了一套強大的反射API,允許你在PHP運行環境中,訪問和使用類、方法、屬性、參數和注釋等,其功能十分強大,經常用於高擴展的PHP框架,自動加載插件,自動生成文檔,甚至可以用來擴展PHP語言。由於它是PHP內建的oop擴展,為語言本身自帶的特性,所以不需要額外添加擴展或者配置就可以使用。更多內容見官方文檔。
反射類型
PHP反射API會基於類,方法,屬性,參數等維護相應的反射類,已提供相應的調用API。
類型 | 說明 |
---|---|
Reflector | Reflector 是一個接口,被所有可導出的反射類所實現(implement) |
Reflection | 反射(reflection)類 |
ReflectionClass | 報告了一個類的有關信息 |
ReflectionZendExtension | 報告Zend擴展的相關信息 |
ReflectionExtension | 報告了PHP擴展的有關信息 |
ReflectionFunction | 報告了一個函數的有關信息 |
ReflectionFunctionAbstract | ReflectionFunction 的父類 |
ReflectionMethod | 報告了一個方法的有關信息 |
ReflectionObject | 報告了一個對象(object)的相關信息 |
ReflectionParameter | 取回了函數或方法參數的相關信息 |
ReflectionProperty | 報告了類的屬性的相關信息 |
訪問
假設定義了一個類 User,我們首先需要建立這個類的反射類實例,然后基於這個實例可以訪問 User 中的屬性或者方法。不管類中定義的成員權限聲明是否為public,都可以獲取到。
<?php
namespace Extend;
use ReflectionClass;
use Exception;
/**
* 用戶相關類
* Class User
* @package Extend
*/
class User{
const ROLE = 'Students';
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;
}
}
$class = new ReflectionClass('Extend\User'); // 將類名User作為參數,即可建立User類的反射類
$properties = $class->getProperties(); // 獲取User類的所有屬性,返回ReflectionProperty的數組
$property = $class->getProperty('password'); // 獲取User類的password屬性ReflectionProperty
$methods = $class->getMethods(); // 獲取User類的所有方法,返回ReflectionMethod數組
$method = $class->getMethod('getUsername'); // 獲取User類的getUsername方法的ReflectionMethod
$constants = $class->getConstants(); // 獲取所有常量,返回常量定義數組
$constant = $class->getConstant('ROLE'); // 獲取ROLE常量
$namespace = $class->getNamespaceName(); // 獲取類的命名空間
$comment_class = $class->getDocComment(); // 獲取User類的注釋文檔,即定義在類之前的注釋
$comment_method = $class->getMethod('getUsername')->getDocComment(); // 獲取User類中getUsername方法的注釋文檔
注意:創建反射類時傳送的類名,必須包含完整的命名空間,即使使用了 use 關鍵字。否則找不到類名會拋出異常。
交互
一旦創建了反射類的實例,我們不僅可以通過反射類訪問原來類的方法和屬性,還能創建原來類的實例或則直接調用類里面的方法。
$class = new ReflectionClass('Extend\User'); // 將類名User作為參數,即可建立User類的反射類
$instance = $class->newInstance('youyou', 1, '***'); // 創建User類的實例
$instance->setUsername('youyou_2'); // 調用User類的實例調用setUsername方法設置用戶名
$value = $instance->getUsername(); // 用過User類的實例調用getUsername方法獲取用戶名
echo $value;echo "\n"; // 輸出 youyou_2
$class->getProperty('username')->setValue($instance, 'youyou_3'); // 通過反射類ReflectionProperty設置指定實例的username屬性值
$value = $class->getProperty('username')->getValue($instance); // 通過反射類ReflectionProperty獲取username的屬性值
echo $value;echo "\n"; // 輸出 youyou_3
$class->getMethod('setUsername')->invoke($instance, 'youyou_4'); // 通過反射類ReflectionMethod調用指定實例的方法,並且傳送參數
$value = $class->getMethod('getUsername')->invoke($instance); // 通過反射類ReflectionMethod調用指定實例的方法
echo $value;echo "\n"; // 輸出 youyou_4
try {
$property = $class->getProperty('password_1');
$property->setAccessible(true); // 修改 $property 對象的可訪問性
$property->setValue($instance, 'password_2'); // 可以執行
$value = $property->getValue($instance); // 可以執行
echo $value;echo "\n"; // 輸出 password_2
$class->getProperty('password')->setAccessible(true); // 修改臨時ReflectionProperty對象的可訪問性
$class->getProperty('password')->setValue($instance, 'password');// 不能執行,拋出不能訪問異常
$value = $class->getProperty('password')->getValue($instance); // 不能執行,拋出不能訪問異常
$value = $instance->password; // 不能執行,類本身的屬性沒有被修改,仍然是private
}catch(Exception $e){echo $e;}
注意事項
直接訪問 protected 或則 private 的熟悉或者方法會拋出異常
需要調用指定的 ReflectionProperty 或則 ReflectionMethod 對象 setAccessible(true)方法才能訪問非公有成員
修改非公有成員的訪問權限只作用於當前的反射類的實例
需要注意獲取靜態成員和非靜態成員所使用的方法不一樣
獲取父類成員的方法和一般的不一樣
有時間會整理出反射類的API表,詳細的API列表可以先查閱官方文檔。