反射是編程語言的高級特性,能在運行時讓代碼有感知代碼的能力。PHP自5起支持反射機制,其是各種OOP框架底層實現的重要支撐。
反射
從一個簡單的例子理解反射:人有五官四肢,但鮮有人清楚人體內部的經脈走向、骨骼構造。如果你修仙順利,在丹田深處練出元嬰,那么就通過元嬰透析身體內部的構造。理解內部構造后,還可以讓元嬰指引體內真氣在經脈的流向,早日修成正果。
如其名,反射是(從鏡子里)照出自身。我們寫代碼,告訴代碼怎么運行,事件發生在編譯期。代碼運行期間,代碼如何知道自己的結構以及能力呢?反射機制相當于代碼的元嬰,使代碼能夠感知自身結構,并可(部分)改變運行行為。
與運行時類型信息(Runtime Type Informatiion, RTTI)不同,反射重點在運行時檢測、感知、改變自身的結構和行為。反射是元編程(metaprogramming)的重要組成部分。
PHP反射API
反射不是語法分析,不操作表達式、代碼語句。反射獲取的是代碼的結構,即函數、類這些構件的結構。PHP中的反射API均以Reflection
開頭(接口Reflector
除外),重點在函數和類兩種結構。而函數可以看成類的成員函數(多一個隱式的this參數)或者靜態成員函數(public
類型),所以了解反射API可從類信息的ReflectionClass
開始。
ReflectionClass提供了以下獲取類基本信息的接口:
getProperties
:獲取成員變量/屬性,返回一個ReflectionProperty
數組;ReflectionProperty
類中有對屬性詳細說明的API:是否默認屬性(isDefault),是否私有屬性(isPrivate)等。同時ReflectionClass
還提供獲取特定類別屬性的API:getDefaultProperties
,getStaticProperties
;
getConstants
:獲取類中定義的常量;
getMethods
:獲取類中定義的方法,返回一個ReflectionMethod數組;ReflectionMethod將在下文講解;
getInterfaces
:獲取類實現的接口;
getParentClass
:獲取父類的ReflectionClass實例。
在反射中,類、接口、特性不分家,所以ReflectionClass
提供類型判定API:isInterface
、isTrait
。
除了以上基本信息,ReflectionClass
(包括ReflectionMethod/ReflectionFunction
)還提供了一些不可思議的能力:
getDocComment
:獲取類的文檔注釋信息;
getFilename
:獲取類定義的文件;
getStartLine
: 獲取類定義的起始行號;
getEndLine
: 獲取類定義的結束行號;
getModifiers
:獲取類定義的修飾符,其意義名字可通過Reflection::getModifierNames得到,例如:abstract,final。
如果說前述的類結構信息可以通過現有的API獲取(method_exits/property_exits
等),上面列出的功能基本上只能通過反射API獲取(PHP文件中定義的類并且知道定義文件,可以利用token_get_all
得到相同結果,但是實現非常復雜)。這些行為發生在運行期間。由此可見反射API在分析類結構信息功能上的強大。
除了ReflectionClass
,ReflectionMethod
和ReflectionFunction
是另外反射中另外兩個重要的類。函數(function
)定義在類外部,方法(method
)定義在類內部,兩者其實同源,在反射API中有共同的父類:ReflectionFunctionAbstract
。ReflectionFunctionAbstract
有兩者的大部分API,并且基本上是最重要的API。其中最值得關注的是其參數信息的API:getParameters
。其獲取函數的參數信息,返回一個ReflectionParameter
數組。結合getParameters
和ReflectionParameter
,函數(方法)的結構基本上就清晰了。
API操作
知道人體構造和體內真氣分布,你可以引導真氣到手指,練成一陽指、六脈神劍、彈指神通、九陰白骨爪等;也可以讓真氣匯聚,沖破任督二脈,開辟洞天;還可以逆轉全身經脈,練成蛤蟆功…內省的好處可見一斑。
反射讓代碼感知自身結構,有什么好處呢?反射API提供了三種在運行時對代碼操作的能力:
- 設置訪問控制權:
setAccessible
。可獲取私有的方法/屬性。注意:setAccessible
只是讓方法/成員變量可以invoke/getValue/setValue
,并不代表類定義的訪問存取權限改變;
- 調用函數/方法:
invoke/invokeArgs
。配合獲取函數參數的API,可以安全的傳參和調用函數,call_user_func(_array)
的增強版;
- 不依賴構造函數生成實例:
newInstanceWithoutConstructor
。
以單例來說一下反射API的功能,單例類代碼如下:
# foo.php
class Foo {
private static $id;
private static $instance;
private function __construct() {
++ self::$id;
fwrite(STDOUT, "construct, instance id: " . self::$id . "\n");
}
public static function getSingleton() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
在Foo
類中,構造函數是私有,獲取實例只能通過getSingleton
方法,并且獲取到的是單例。但在反射API加持下,能獲取多個實例:
$instance1 = Foo::getSingleton();
var_dump($instance1);
$class = new ReflectionClass("Foo");
$constructor = $class->getConstructor();
if ((ReflectionProperty::IS_PUBLIC $constructor->getModifiers()) === 0) {
$constructor->setAccessible(true);
}
$instance2 = $class->newInstanceWithoutConstructor();
$constructor->invoke($instance2);
var_dump($instance2);
# 腳本執行結果
construct, instance id: 1
object(Foo)#1 (0) {
}
construct, instance id: 2
object(Foo)#4 (0) {
}
我們成功的生成了兩個實例,并調用構造函數完成對象初始化。如果沒有反射API,這幾乎是不可能完成的工作。
除了這三種操作,反射API幾乎已無在運行時動態改變代碼的行為。但作為動態語言,PHP內置了將數據轉換成代碼執行的能力(例如create_function/eval
、動態函數名調用)。而PHP的好基友JavaScript則可以隨時在運行時改變任意函數的行為:

PHP作為最好的語言,理應能做到在運行時動態增減/改變函數定義。這就需要用到另一個PHP核心開發者“Dmitry Zenovich”打造的大殺器:runkit
拓展。這部分內容不屬于反射,加之本人了解不深,不再詳述。
對比
整理一下反射API和函數式API在功能上的差異:
功能 |
函數式API |
反射API |
函數是否存在 |
function_exists |
ReflectionFunction |
類是否存在 |
class_exits |
ReflectionClass |
方法是否存在 |
method_exits |
ReflectionMethod |
變量/屬性是否存在 |
property_exits |
ReflectionProperty |
獲取類變量 |
get_class_vars |
ReflectionClass::getProperties |
獲取類方法 |
get_class_methods |
ReflectionClass::getMethods |
獲取類常量 |
— |
ReflectionClass::RegetReflectionConstant(s) |
獲取函數/方法參數信息 |
— |
ReflectionFunction/Method::getParameters |
獲取函數/方法返回值 |
— |
ReflectionFunction/Method::getReturnType |
類使用的特性 |
class_uses |
ReflectionClass::getTraits |
獲取父類 |
class_parents |
ReflectionClass::getParentClass |
獲取類實現的接口 |
class_implements |
ReflectionClass::getInterfaceNames |
獲取類所在名字空間 |
__NAMESPACE__ |
ReflectionClass::getNamespaceName |
函數調用 |
call_user_func(_array) |
ReflectionMethod(Function)::invoke(Args) |
獲取類名 |
__CLASS__/::class |
ReflectionClass::getName |
獲取函數名 |
__METHOD__/__FUNCTION__ |
ReflectionFunction/Method::getName |
獲取類/常量/變量/方法修飾符 |
— |
ReflectionClass/Constant/Property/Method::getModifiers |
獲取所在文件 |
__FILE__ |
ReflectionClass/Constant/Function/Method::getFileName |
獲取所在行(范圍) |
— |
ReflectionClass/Function/Method::getStartLine/getEndLine |
獲取文檔 |
— |
ReflectionClass/Function/Method::getDocComment |
|
extension_loaded |
ReflectionZendExtension |
拓展 |
get_loaded_extensions |
ReflectionExtension |
|
get_extension_funcs |
從上表可以看出反射API較函數式API能提供更全面的信息。還需要注意到__FILE__
這類魔術常量是編譯期的工作,不是運行時的能力。
同時給出RTTI的函數式API和反射API在功能上的差異:
功能 |
函數式API |
反射API |
類型判斷 |
is_int/is_bool/is_array等 |
— |
獲取對象的類名 |
get_class |
ReflectionObject::getName |
獲取對象父類 |
get_parent_class |
ReflectionObject::getParentClass |
類型/繼承檢測 |
instanceof/is_a/is_subclass_of |
ReflectionObject::isInstance/isSubclassOf |
生成器 |
— |
ReflectionGenerator |
總結
本文對PHP中的反射機制做了簡要總結,并與在運行時獲取代碼信息的函數式API做了對比。即使你token_get_all
用得再熟練,preg_match
等文本操作用得再順手,反射API仍有其獨到一面,值得了解。如本人之前博文“PHP中的重載”所言,有了反射,function_exits/class_exits
、call_user_func
這些函數應該可以退休。但是考慮到兼容、使用便利、運行效率等因素,許多框架仍然依賴這些API。
感謝閱讀,歡迎指正!
以上就是PHP反射知識回顧的詳細內容,更多關于PHP 反射的資料請關注腳本之家其它相關文章!
您可能感興趣的文章:- PHP的反射動態獲取類方法、屬性、參數操作示例
- php面試實現反射注入的詳細方法
- PHP反射原理與用法深入分析
- php提供實現反射的方法和實例代碼
- PHP進階學習之反射基本概念與用法分析
- php反射學習之不用new方法實例化類操作示例
- PHP反射學習入門示例
- PHP反射實際應用示例
- 用PHP的反射實現委托模式的講解
- 淺析PHP類的反射來實現依賴注入過程
- PHP基于反射機制實現自動依賴注入的方法詳解
- PHP基于反射獲取一個類中所有的方法