從symfony框架到一個完整的項目需要幾步? (一) 依賴注入與反射

前言

對於php的框架,無論是yiisymfony或者是laravel,大家都在工作中有涉獵。對於在框架中的存放着資源包vendor文件夾,入口文件(index.php 或者 app.php),大家也都與他們每天碰面。但是你真的熟悉這些文件/文件夾嗎?一個完整的項目是如何從一個純淨框架發展而來?各個部分又在框架這個大廈中又起到了怎麼樣的作用?

一、依賴注入

依賴注入不是sql注入o( ̄ヘ ̄o#)

1.1 說一說依賴注入和反射類的關係

當提到依賴注入DI的時候,90%的文章都會提到一個叫做 Reflection的東西,導致很多的看官(也包括我)認爲二本身就是一體的,但實際上雙方是兩個風馬牛不相及的概念。Reflection是反射類,而是一個依賴注入DI是一種設計模式,反射類怎麼會和設計模式屬於同一種東西?所以他們的關係是依賴關係——既依賴注入是依賴於反射類而完成的設計模式。

1.2 什麼是Reflection

寫一個非常簡單的類A,這個類裏面只有一個對象$a,一個方法a()

class A
{ 
    public $a;
    public function a()
    {
        echo __FUNCTION__;
    }
}

之後我們開始調用反射類Reflection

$A       = new A();
$reflect = new ReflectionObject($A);
$props   = $reflect->getProperties(); // 獲取該類所有的對象
...

我們可以通過反射知道該類所有我們想知道的內容,好像是x光一樣獲取一個類裏面的所有細節。但是,當我完反射類之後,又陷入了迷茫。設計這種反射類到底是爲了什麼?如果想知道原來類裏面有多少方法/對象直接看原來的類不就可以了嗎?爲什麼要多此一舉呢?這又和依賴注入有什麼關係呢?
先不要着急,不妨讓我們先說說別的,然後回頭來看看。

1.3 都是依賴怎麼辦

現在有這樣的一個場景,我想實例化B,但是B依賴於A。這該怎麼做?很簡單如下就可以解決。

Class A{
    // do sth.
}
Class B{
    public function __construct(A $a){
        // B類依賴於A
    }
}
$a = new A();
$b = new B($a); 

很簡單,對不對?兩下就可以解決,這沒有什麼困難的。但是如果現在出現了這樣一種情況:一共有A-Z一共26個類,B類依賴於AC類依賴於B,以此類推。那麼我如果想實例化Z類,你需要怎麼做?難道還使用上圖中的方法?那豈不是實例化Z需要寫26行?

那麼有沒有一種方法能夠直接實例化B,然後就可以把其他的類全部自動加載進來?還真有。就需要用到上面剛剛講過的反射類。

<?php
/**
* 工具類,使用該類來實現自動依賴注入。
*/
class Ioc {
    // 獲得類的對象實例
    public static function getInstance($className) {
        $paramArr = self::getMethodParams($className);
        return (new ReflectionClass($className))->newInstanceArgs($paramArr);
    }

    /**
     * 獲得類的方法參數,只獲得有類型的參數。
     * 通過遞歸方法
     * @param  [type] $className   [類名]
     * @param  [type] $methodsName [方法名]
     * @return [mixed]              
     */
    protected static function getMethodParams($className, $methodsName = '__construct') {

        // 通過反射獲得該類
        $class = new ReflectionClass($className);
        $paramArr = []; // 記錄參數,和參數類型

        // 判斷該類是否有構造函數
        if ($class->hasMethod($methodsName)) {
            // 獲得構造函數
            $construct = $class->getMethod($methodsName);

            // 判斷構造函數是否有參數
            $params = $construct->getParameters();

            if (count($params) > 0) {

                // 判斷參數類型
                foreach ($params as $key => $param) {

                    if ($paramClass = $param->getClass()) {

                        // 獲得參數類型名稱
                        $paramClassName = $paramClass->getName();

                        /**
                         * 獲得參數類型
                         * 在這裏使用遞歸
                         */
                        $args = self::getMethodParams($paramClassName);
                        $paramArr[] = (new ReflectionClass($paramClass->getName()))->newInstanceArgs($args);
                    }
                }
            }
        }
        return $paramArr;
    }

    /**
     * 執行類的方法
     * @param  [type] $className  [類名]
     * @param  [type] $methodName [方法名稱]
     * @param  [type] $params     [額外的參數]
     * @return [type]             [description]
     */
    public static function make($className, $methodName, $params = []) {
        // 獲取類的實例
        $instance = self::getInstance($className);
        // 獲取該方法所需要依賴注入的參數
        $paramArr = self::getMethodParams($className, $methodName);
        return $instance->{$methodName}(...array_merge($paramArr, $params));
    }
}

class A {
    // ...
}

class B {
    public function __construct(A $a) {
        // ...
    }
}

class C {
    public function __construct(B $b) {
        // ...
    }
}

$cObj = Ioc::getInstance('C');

讓我們來着重的看一下Ioc這個類的getMethodParams方法。

先說一下大體的情況:既該方法是使用遞歸對被加載的類進行遍歷來達到將所有被依賴的類全部加載進來的目的。

這個方法首先實例化Reflection,然後使用getMethod方法獲取被加載進來的類的__construct方法,然後看看這個__construct中是否有其他的類作爲參數,如果有,則遞歸重複以上流程,直到沒有依賴爲止。

以上就是使用Reflection進行依賴注入的一個簡單例子。雖然在實際框架中會比上面的例子複雜一些,不過依然大同小異。

比如說,在很多的框架中,會使用一種叫做容器container的概念--使用$this->getContainer()->get(YOUR_CLASS_NAME)的方式獲取類。這個容器是什麼高科技的東西?不用擔心,說到底他還是依賴注入。

我們會在後面刨根問底,來看看symfony的container是怎麼設計的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章