淺析如何通過 PHP 類的反射來實(shí)現(xiàn)依賴注入

PHP具有完整的反射 API穿铆,提供了對類您单、接口、函數(shù)荞雏、方法和擴(kuò)展進(jìn)行逆向工程的能力虐秦。通過類的反射提供的能力我們能夠知道類是如何被定義的,它有什么屬性凤优、什么方法悦陋、方法都有哪些參數(shù),類文件的路徑是什么等很重要的信息筑辨。也正式因?yàn)轭惖姆瓷浜芏郟HP框架才能實(shí)現(xiàn)依賴注入自動解決類與類之間的依賴關(guān)系俺驶,這給我們平時的開發(fā)帶來了很大的方便。 本文主要是講解如何利用類的反射來實(shí)現(xiàn)依賴注入(Dependency Injection)棍辕,并不會去逐條講述PHP Reflection里的每一個API暮现,詳細(xì)的API參考信息請查閱官方文檔

再次聲明這里實(shí)現(xiàn)的依賴注入非常簡單,并不能應(yīng)用到實(shí)際開發(fā)中去楚昭,后續(xù)我計劃再開一篇來詳細(xì)介紹laravel框架的服務(wù)容器(IocContainer), 在那里再來看Laravel的服務(wù)容器是如何實(shí)現(xiàn)依賴注入的栖袋。

為了更好地理解,我們通過一個例子來看類的反射抚太,以及如何實(shí)現(xiàn)依賴注入塘幅。
下面這個類代表了坐標(biāo)系里的一個點(diǎn)昔案,有兩個屬性橫坐標(biāo)x和縱坐標(biāo)y。

/**
 * Class Point
 */
class Point
{
    public $x;
    public $y;

    /**
     * Point constructor.
     * @param int $x  horizontal value of point's coordinate
     * @param int $y  vertical value of point's coordinate
     */
    public function __construct($x = 0, $y = 0)
    {
        $this->x = $x;
        $this->y = $y;
    }
}

接下來這個類代表圓形晌块,可以看到在它的構(gòu)造函數(shù)里有一個參數(shù)是Point類的爱沟,即Circle類是依賴與Point類的。

class Circle
{
    /**
     * @var int
     */
    public $radius;//半徑

    /**
     * @var Point
     */
    public $center;//圓心點(diǎn)

    const PI = 3.14;

    public function __construct(Point $point, $radius = 1)
    {
        $this->center = $point;
        $this->radius = $radius;
    }

    //打印圓點(diǎn)的坐標(biāo)
    public function printCenter()
    {
        printf('center coordinate is (%d, %d)', $this->center->x, $this->center->y);
    }

    //計算圓形的面積
    public function area()
    {
        return 3.14 * pow($this->radius, 2);
    }
}

ReflectionClass

下面我們通過反射來對Circle這個類進(jìn)行反向工程匆背。
Circle類的名字傳遞給reflectionClass來實(shí)例化一個ReflectionClass類的對象呼伸。

$reflectionClass = new reflectionClass(Circle::class);
//返回值如下
object(ReflectionClass)#1 (1) {
  ["name"]=>
  string(6) "Circle"
}

反射出類的常量

$reflectionClass->getConstants();

返回一個由常量名稱和值構(gòu)成的關(guān)聯(lián)數(shù)組

array(1) {
  ["PI"]=>
  float(3.14)
}

通過反射獲取屬性

$reflectionClass->getProperties();

返回一個由ReflectionProperty對象構(gòu)成的數(shù)組

array(2) {
  [0]=>
  object(ReflectionProperty)#2 (2) {
    ["name"]=>
    string(6) "radius"
    ["class"]=>
    string(6) "Circle"
  }
  [1]=>
  object(ReflectionProperty)#3 (2) {
    ["name"]=>
    string(6) "center"
    ["class"]=>
    string(6) "Circle"
  }
}

反射出類中定義的方法

$reflectionClass->getMethods();

返回ReflectionMethod對象構(gòu)成的數(shù)組

array(3) {
  [0]=>
  object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(11) "__construct"
    ["class"]=>
    string(6) "Circle"
  }
  [1]=>
  object(ReflectionMethod)#3 (2) {
    ["name"]=>
    string(11) "printCenter"
    ["class"]=>
    string(6) "Circle"
  }
  [2]=>
  object(ReflectionMethod)#4 (2) {
    ["name"]=>
    string(4) "area"
    ["class"]=>
    string(6) "Circle"
  }
}

我們還可以通過getConstructor()來單獨(dú)獲取類的構(gòu)造方法,其返回值為一個ReflectionMethod對象钝尸。

$constructor = $reflectionClass->getConstructor();

反射出方法的參數(shù)

$parameters = $constructor->getParameters();

其返回值為ReflectionParameter對象構(gòu)成的數(shù)組括享。

array(2) {
  [0]=>
  object(ReflectionParameter)#3 (1) {
    ["name"]=>
    string(5) "point"
  }
  [1]=>
  object(ReflectionParameter)#4 (1) {
    ["name"]=>
    string(6) "radius"
  }
}

依賴注入

好了接下來我們編寫一個名為make的函數(shù),傳遞類名稱給make函數(shù)返回類的對象珍促,在make里它會幫我們注入類的依賴铃辖,即在本例中幫我們注入Point對象給Circle類的構(gòu)造方法。

//構(gòu)建類的對象
function make($className)
{
    $reflectionClass = new ReflectionClass($className);
    $constructor = $reflectionClass->getConstructor();
    $parameters  = $constructor->getParameters();
    $dependencies = getDependencies($parameters);

    return $reflectionClass->newInstanceArgs($dependencies);
}

//依賴解析
function getDependencies($parameters)
{
    $dependencies = [];
    foreach($parameters as $parameter) {
        $dependency = $parameter->getClass();
        if (is_null($dependency)) {
            if($parameter->isDefaultValueAvailable()) {
                $dependencies[] = $parameter->getDefaultValue();
            } else {
                //不是可選參數(shù)的為了簡單直接賦值為字符串0
                //針對構(gòu)造方法的必須參數(shù)這個情況
                //laravel是通過service provider注冊closure到IocContainer,
                //在closure里可以通過return new Class($param1, $param2)來返回類的實(shí)例
                //然后在make時回調(diào)這個closure即可解析出對象
                //具體細(xì)節(jié)我會在另一篇文章里面描述
                $dependencies[] = '0';
            }
        } else {
            //遞歸解析出依賴類的對象
            $dependencies[] = make($parameter->getClass()->name);
        }
    }

    return $dependencies;
}

定義好make方法后我們通過它來幫我們實(shí)例化Circle類的對象:

$circle = make('Circle');
$area = $circle->area();
/*var_dump($circle, $area);
object(Circle)#6 (2) {
  ["radius"]=>
  int(1)
  ["center"]=>
  object(Point)#11 (2) {
    ["x"]=>
    int(0)
    ["y"]=>
    int(0)
  }
}
float(3.14)*/

通過上面這個實(shí)例我簡單描述了一下如何利用PHP類的反射來實(shí)現(xiàn)依賴注入猪叙,Laravel的依賴注入也是通過這個思路來實(shí)現(xiàn)的娇斩,只不過設(shè)計的更精密大量地利用了閉包回調(diào)來應(yīng)對各種復(fù)雜的依賴注入,由于還有一些東西需要梳理具體的細(xì)節(jié)我最近會在另一篇文章里來詳細(xì)描述穴翩。

本文的示例代碼的下載鏈接


原文:https://segmentfault.com/a/1190000012696784

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末犬第,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芒帕,更是在濱河造成了極大的恐慌歉嗓,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件背蟆,死亡現(xiàn)場離奇詭異鉴分,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)带膀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門志珍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人本砰,你說我怎么就攤上這事碴裙。” “怎么了点额?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長莺琳。 經(jīng)常有香客問我还棱,道長,這世上最難降的妖魔是什么惭等? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任珍手,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘琳要。我一直安慰自己寡具,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布稚补。 她就那樣靜靜地躺著童叠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪课幕。 梳的紋絲不亂的頭發(fā)上厦坛,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機(jī)與錄音乍惊,去河邊找鬼杜秸。 笑死,一個胖子當(dāng)著我的面吹牛润绎,可吹牛的內(nèi)容都是我干的撬碟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼莉撇,長吁一口氣:“原來是場噩夢啊……” “哼呢蛤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起稼钩,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤顾稀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坝撑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體静秆,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年巡李,在試婚紗的時候發(fā)現(xiàn)自己被綠了抚笔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡侨拦,死狀恐怖殊橙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狱从,我是刑警寧澤膨蛮,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站季研,受9級特大地震影響敞葛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜与涡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一惹谐、第九天 我趴在偏房一處隱蔽的房頂上張望持偏。 院中可真熱鬧,春花似錦氨肌、人聲如沸鸿秆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卿叽。三九已至,卻和暖如春桩了,著一層夾襖步出監(jiān)牢的瞬間附帽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工井誉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕉扮,地道東北人。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓颗圣,卻偏偏與公主長得像喳钟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子在岂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理奔则,服務(wù)發(fā)現(xiàn),斷路器蔽午,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 5易茬、產(chǎn)品規(guī)劃中的競品分析競品是競爭產(chǎn)品,也就是競爭對手的產(chǎn)品及老,競品分析顧名思義是對競爭對手的產(chǎn)品進(jìn)行比較分析抽莱,尋找...
    萬能天哥閱讀 618評論 0 5
  • 不知道是從什么時候開始,對你產(chǎn)生了一種超越年齡的感覺骄恶,也許就是第一眼食铐,當(dāng)我轉(zhuǎn)頭時看見你奶奶將你帶進(jìn)教室時,便已經(jīng)注...
    黑色兔子閱讀 239評論 0 1
  • 將字符串轉(zhuǎn)化為可變字符串,再根據(jù)指定字符查找該字符寞秃,再在該字符前面插入換行符
    心底碎片閱讀 721評論 0 0
  • 萬物皆有裂痕春寿,那是光照進(jìn)來的地方犁柜。 我始終相信,上帝為你關(guān)了一扇門堂淡,一定會在另一個地方為你開一扇窗馋缅。 很多次有過這...
    lamanda閱讀 535評論 2 1