類的反射和依賴注入

在講服務容器以前我想先梳理下PHP反射相關的知識,PHP反射是程序實現依賴注入的基礎,也是Laravel的服務容器實現服務解析的基礎,若是你已經掌握了這方面基礎知識,那麼能夠跳過本文直接看服務容器部分的內容。php

PHP具備完整的反射 API,提供了對類、接口、函數、方法和擴展進行逆向工程的能力。經過類的反射提供的能力咱們可以知道類是如何被定義的,它有什麼屬性、什麼方法、方法都有哪些參數,類文件的路徑是什麼等很重要的信息。也正式由於類的反射不少PHP框架才能實現依賴注入自動解決類與類之間的依賴關係,這給咱們平時的開發帶來了很大的方便。 本文主要是講解如何利用類的反射來實現依賴注入(Dependency Injection),並不會去逐條講述PHP Reflection裏的每個API,詳細的API參考信息請查閱官方文檔laravel

再次聲明這裏實現的依賴注入很是簡單,並不能應用到實際開發中去,能夠參考後面的文章服務容器(IocContainer), 瞭解Laravel的服務容器是如何實現依賴注入的。git

爲了更好地理解,咱們經過一個例子來看類的反射,以及如何實現依賴注入。 下面這個類表明了座標系裏的一個點,有兩個屬性橫座標x和縱座標y。github

/** * 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;
    }
}
複製代碼

接下來這個類表明圓形,能夠看到在它的構造函數裏有一個參數是Point類的,即Circle類是依賴與Point類的。數組

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

    /** * @var Point */
    public $center;//圓心點

    const PI = 3.14;

    public function __construct(Point $point, $radius = 1) {
        $this->center = $point;
        $this->radius = $radius;
    }
    
    //打印圓點的座標
    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這個類進行反向工程。 把Circle類的名字傳遞給reflectionClass來實例化一個ReflectionClass類的對象。閉包

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

反射出類的常量

$reflectionClass->getConstants();
複製代碼

返回一個由常量名稱和值構成的關聯數組框架

array(1) {
  ["PI"]=>
  float(3.14)
}
複製代碼

經過反射獲取屬性

$reflectionClass->getProperties();
複製代碼

返回一個由ReflectionProperty對象構成的數組ide

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對象構成的數組函數

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()來單獨獲取類的構造方法,其返回值爲一個ReflectionMethod對象。post

$constructor = $reflectionClass->getConstructor();
複製代碼

反射出方法的參數

$parameters = $constructor->getParameters();
複製代碼

其返回值爲ReflectionParameter對象構成的數組。

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

依賴注入

好了接下來咱們編寫一個名爲make的函數,傳遞類名稱給make函數返回類的對象,在make裏它會幫咱們注入類的依賴,即在本例中幫咱們注入Point對象給Circle類的構造方法。

//構建類的對象
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 {
                //不是可選參數的爲了簡單直接賦值爲字符串0
                //針對構造方法的必須參數這個狀況
                //laravel是經過service provider註冊closure到IocContainer,
                //在closure裏能夠經過return new Class($param1, $param2)來返回類的實例
                //而後在make時回調這個closure便可解析出對象
                //具體細節我會在另外一篇文章裏面描述
                $dependencies[] = '0';
            }
        } else {
            //遞歸解析出依賴類的對象
            $dependencies[] = make($parameter->getClass()->name);
        }
    }

    return $dependencies;
}
複製代碼

定義好make方法後咱們經過它來幫咱們實例化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)*/
複製代碼

經過上面這個實例我簡單描述了一下如何利用PHP類的反射來實現依賴注入,Laravel的依賴注入也是經過這個思路來實現的,只不過設計的更精密大量地利用了閉包回調來應對各類複雜的依賴注入。

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

本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。

相關文章
相關標籤/搜索