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

PHP作為最好的語(yǔ)言,理應(yīng)能做到在運(yùn)行時(shí)動(dòng)態(tài)增減/改變函數(shù)定義。這就需要用到另一個(gè)PHP核心開(kāi)發(fā)者“Dmitry Zenovich”打造的大殺器:runkit拓展。這部分內(nèi)容不屬于反射,加之本人了解不深,不再詳述。
對(duì)比
整理一下反射API和函數(shù)式API在功能上的差異:
| 功能 | 函數(shù)式API | 反射API |
|---|---|---|
| 函數(shù)是否存在 | 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) |
| 獲取函數(shù)/方法參數(shù)信息 | — | ReflectionFunction/Method::getParameters |
| 獲取函數(shù)/方法返回值 | — | ReflectionFunction/Method::getReturnType |
| 類使用的特性 | class_uses | ReflectionClass::getTraits |
| 獲取父類 | class_parents | ReflectionClass::getParentClass |
| 獲取類實(shí)現(xiàn)的接口 | class_implements | ReflectionClass::getInterfaceNames |
| 獲取類所在名字空間 | __NAMESPACE__ | ReflectionClass::getNamespaceName |
| 函數(shù)調(diào)用 | call_user_func(_array) | ReflectionMethod(Function)::invoke(Args) |
| 獲取類名 | __CLASS__/::class | ReflectionClass::getName |
| 獲取函數(shù)名 | __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較函數(shù)式API能提供更全面的信息。還需要注意到__FILE__這類魔術(shù)常量是編譯期的工作,不是運(yùn)行時(shí)的能力。
同時(shí)給出RTTI的函數(shù)式API和反射API在功能上的差異:
| 功能 | 函數(shù)式API | 反射API |
|---|---|---|
| 類型判斷 | is_int/is_bool/is_array等 | — |
| 獲取對(duì)象的類名 | get_class | ReflectionObject::getName |
| 獲取對(duì)象父類 | get_parent_class | ReflectionObject::getParentClass |
| 類型/繼承檢測(cè) | instanceof/is_a/is_subclass_of | ReflectionObject::isInstance/isSubclassOf |
| 生成器 | — | ReflectionGenerator |
總結(jié)
本文對(duì)PHP中的反射機(jī)制做了簡(jiǎn)要總結(jié),并與在運(yùn)行時(shí)獲取代碼信息的函數(shù)式API做了對(duì)比。即使你token_get_all用得再熟練,preg_match等文本操作用得再順手,反射API仍有其獨(dú)到一面,值得了解。如本人之前博文“PHP中的重載”所言,有了反射,function_exits/class_exits、call_user_func這些函數(shù)應(yīng)該可以退休。但是考慮到兼容、使用便利、運(yùn)行效率等因素,許多框架仍然依賴這些API。
感謝閱讀,歡迎指正!
以上就是PHP反射知識(shí)回顧的詳細(xì)內(nèi)容,更多關(guān)于PHP 反射的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- PHP的反射動(dòng)態(tài)獲取類方法、屬性、參數(shù)操作示例
- php面試實(shí)現(xiàn)反射注入的詳細(xì)方法
- PHP反射原理與用法深入分析
- php提供實(shí)現(xiàn)反射的方法和實(shí)例代碼
- PHP進(jìn)階學(xué)習(xí)之反射基本概念與用法分析
- php反射學(xué)習(xí)之不用new方法實(shí)例化類操作示例
- PHP反射學(xué)習(xí)入門示例
- PHP反射實(shí)際應(yīng)用示例
- 用PHP的反射實(shí)現(xiàn)委托模式的講解
- 淺析PHP類的反射來(lái)實(shí)現(xiàn)依賴注入過(guò)程
- PHP基于反射機(jī)制實(shí)現(xiàn)自動(dòng)依賴注入的方法詳解
- PHP基于反射獲取一個(gè)類中所有的方法
相關(guān)文章
mysql_escape_string()函數(shù)用法分析
這篇文章主要介紹了mysql_escape_string()函數(shù)用法,結(jié)合實(shí)例形式講述了mysql_escape_string()函數(shù)的功能,并分析了mysql_escape_string的使用技巧與注意事項(xiàng),需要的朋友可以參考下2016-04-04
Ajax+PHP實(shí)現(xiàn)的刪除數(shù)據(jù)功能示例
這篇文章主要介紹了Ajax+PHP實(shí)現(xiàn)的刪除數(shù)據(jù)功能,涉及php結(jié)合ajax動(dòng)態(tài)交互操作數(shù)據(jù)庫(kù)實(shí)現(xiàn)數(shù)據(jù)查詢與刪除相關(guān)操作技巧,需要的朋友可以參考下2019-02-02
php Illegal string offset 'name&apo
這篇文章主要介紹了php Illegal string offset 'name'問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
table標(biāo)簽的結(jié)構(gòu)與合并單元格的實(shí)現(xiàn)方法
以下是對(duì)table標(biāo)簽的結(jié)構(gòu)與合并單元格的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-07-07
php foreach 使用&(與運(yùn)算符)引用賦值要注意的問(wèn)題
foreach 通過(guò)在 $value 之前加上 & 很容易就能修改數(shù)組的單元,在 foreach 使用引用時(shí)要注意了。也可以在處理完后立即斷開(kāi)引用關(guān)系,后面就不會(huì)有上述情況了。2010-02-02
如何使用PHP對(duì)網(wǎng)站驗(yàn)證碼進(jìn)行破解
這篇文章主要介紹了如何使用PHP對(duì)網(wǎng)站驗(yàn)證碼進(jìn)行破解,需要的朋友可以參考下2015-09-09
PHP如何通過(guò)date() 函數(shù)格式化顯示時(shí)間
這篇文章主要介紹了PHP如何通過(guò)date() 函數(shù)格式化顯示時(shí)間,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11

