前言
對於php
的框架,無論是yii
,symfony
或者是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
類依賴於A
,C
類依賴於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是怎麼設計的。