PHP反射基礎知識回顧
反射是程式語言的高階特性,能在執行時讓程式碼有感知程式碼的能力。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
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 反射的資料請關注我們其它相關文章!