Laravel源碼解析 — 服務容器
- 2021 年 4 月 12 日
- 筆記
前言
本文對將系統的對 Laravel 框架知識點進行總結,如果錯誤的還望指出
- 閱讀書籍
- 《Laravel框架關鍵技術解析》 陳昊
- 學習課程
- Laravel5.4快速開發簡書網站 軒脈刃
- Laravel重構企業級電商項目 檀梵
服務容器
1.什麼是IoC
IOC 模式,不是一種技術,而是一種設計思想。在應用程式開發中,IoC 意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制,也是一種面向介面編程的思想。
當我們以面向介面編程的時候,程式中實例之間的耦合將上升到介面層次,而不是程式碼實現層次,使用配置文件來實現類的耦合。
容器的作用很簡單,將在程式碼中使用像(new object)這樣語法進行耦合的方式,改為配置文件來管理耦合,通過這種改變,從而保證系統重構或者業務邏輯改變時,不會發生「牽一髮而動全身」的效果,從而有更好的可擴展性、可維護性。
總結:藉助於「第三方」實現具有依賴關係的對象之間的解耦。
舉個栗子
在日常開發應用中,我們要實現一個功能,在用戶處理模組
中調用獲取用戶資訊Model
,通常是使用像(new object)這樣的語法,在用戶處理模組
中將對象創造出來,這時程式碼依賴耦合就出現了,在獲取用戶資訊 Model
時則需要修改用戶處理模組里的程式碼。
而調用 IoC 容器則將依賴耦合上升到介面層次,只需要修改容器註冊時所綁定的服務即可。

其中涉及到 依賴注入
、控制反轉
、反射
的思想
2.控制反轉
控制反轉是將組件間的依賴關係從程式內部提到外部容器來管理,那麼就會出現,誰控制誰?反轉是什麼?有正轉嗎?
上述流程圖中
-
應用程式自身調用
用戶處理模組 創建了 獲取用戶資訊Model ,那麼 用戶處理模組 控制了 獲取用戶資訊Model ,這種創建過程稱為 正轉
-
IoC 模式調用
創建權 交給了 IoC容器,由 Ioc 容器去創建 獲取用戶資訊Model ,那麼 Ioc 容器 控制了 獲取用戶資訊Model ,這種創建過程稱為 反轉
正轉:由程式本身在對象中主動控制去直接獲取依賴對象
反轉:由容器來幫忙建立及注入依賴對象
3.依賴注入(DI)
理解依賴注入我們需要先理解什麼是依賴,再理解依賴注入
依賴
在應用程式開發中由於某客戶類依賴於某個服務類稱為依賴
例:
// 實現不同交通工具類
// 腿著
class Leg
{
public function go()
{
echo 'walk to Tibet!!!';
}
}
// 開車
class Car
{
public function go()
{
echo 'drive car to Tibet!!!';
}
}
// 列車
class Train
{
public function go()
{
echo 'go to Tibet!!!';
}
}
// 設計旅遊者類,該類在實現游西藏的功能時要依賴交通工具類
class Traveller
{
private $trafficTool;
public function __construct()
{
$this->trafficTool = new Leg();
}
public function viisitTibet()
{
$this->trafficTool->go();
}
}
$app = new Traveller();
$app->viisitTibet();
上述實例就是一個依賴的過程,當創建一個 Traveller 實例的時候,構造函數中獲取了(new Object)其中一個交通工具服務類,這時依賴就產生了,客戶類(Traveller)依賴於服務類(Leg),在實際開發中需求經常改動,那麼如果直接修改客戶類的程式碼就非常繁瑣並且不利於維護。
依賴注入
動態的向某個對象提供它所需要的其他對象,指組件的依賴通過外部以參數或其他形式注入,不在客戶類裡面用 (new object)的方式去實例化服務類而轉由外部來負責,並且面向介面編程,將參數轉為介面類,而不是具體的某個實現類,拓展性更強。
例:
// 設計公共介面
interface Visit
{
public function go();
}
// 實現不同交通工具類
// 腿著
class Leg implements Visit
{
public function go()
{
echo 'walk to Tibet!!!';
}
}
// 開車
class Car implements Visit
{
public function go()
{
echo 'drive car to Tibet!!!';
}
}
// 列車
class Train implements Visit
{
public function go()
{
echo 'go to Tibet!!!';
}
}
// 設計旅遊者類,該類在實現游西藏的功能時要依賴交通工具類
class Traveller
{
private $trafficTool;
public function __construct( Visit $trafficTool)
{
$this->trafficTool = $trafficTool;
}
public function visitTibet()
{
$this->trafficTool->go();
}
}
// 生成的交通工具依賴
$trafficTool = new Leg();
// 依賴注入的方式解決依賴問題
$app = new Traveller($trafficTool);
$app->visitTibet();
上述實例就是一個依賴注入的過程,當創建一個 Traveller 實例的時候,構造函數依賴一個外部的具有 Visit 介面的實例,我們傳遞一個 Leg
實例,即通過依賴注入的方式解決依賴問題。
這裡要注意的是,依賴注入需要通過介面來限制,不能隨意開放,這也體現了設計模式的另一個原則——針對介面編程,而不是針對實現編程。
4.反射
什麼是反射
PHP 反射機制是指在程式運行狀態中,動態獲取資訊以及動態調用對象。
這種動態獲取資訊以及動態調用對象的方法的功能稱為反射 API。
PHP 中獲取實例的資訊是通過 Reflection 實現
反射作用
為什麼要使用反射機制?直接創建(new)對象不就可以了嗎?
先理解動態調用對象與靜態調用對象的區別
動態調用對象
通過類的路徑或別名獲取關於類、方法、屬性、參數等詳細資訊,包括注釋,就算類成員定義為 private 也可以在外部訪問。
靜態調用對象
通過使用(new Object)的方式獲取類的實例。
程式碼靈活性
而編譯性語言區別更為明顯,靜態調用對象在編譯時確定實例的類型,綁定對象,動態調用對象則是在運行時確定實例的類型,提高了編譯性語言的靈活性,降低類之間的耦合
為什麼要使用反射機制
這樣就可以把調用一個實例的行為寫成一個類的方法或者創建函數,然後統一介面調用創建函數來創建實例對象,有點像工廠方法模式+面向介面編程的思想,這個類的方法和創建函數則是 IoC 容器
舉個栗子
<?php
class B
{
}
class A
{
public function __construct(B $args)
{
}
public function demo()
{
echo 'Hello world';
}
}
//建立class A 的反射
$reflectionClass = new ReflectionClass('A');
$b = new B();
// 創建 class A 的實例
$instance = $reflectionClass->newInstanceArgs([$b]);
// 執行實例中的方法,輸出 『Hellow World』
$instance->demo();
// 獲取class A 的構造函數相關資訊
$constructor = $reflectionClass->getConstructor();
/**
* 獲取class A 構造函數參數的相關資訊
* 參數數組
*/
$dependencies = $constructor->getParameters();
foreach ($dependencies as $dependencie) {
var_dump($dependencie->getClass());
die;
}
// 獲取class A 的構造函數
var_dump($constructor);
// 獲取class A 的構造函數相關資訊
var_dump($dependencies);
5.再看IoC容器
我們再來看一下 IoC 容器的概念在日常開發應用中,我們在A服務中調用B服務要實現一個功能,或者要調用一個對象時都要使用像(new object)這樣的語法,將對象創造出來,這時你需要關心這個對象是什麼,在哪裡,如何創建,然後再創建,這時就出現了程式碼耦合,而IoC 容器就好比 」大象放進冰箱,大象取進冰箱,需要幾步「,需要三步
- 把冰箱門打開 2. 把大象放進去 3. 把冰箱門關上
- 把冰箱門打開 2. 把大象取出來 3. 把冰箱門關上
你不需要關心大象在哪,具體哪個大象,如何放進去,如何取出來,你只需要做到放和取這個動作就行了,這樣就避免了程式碼耦合,而
放大象,則需要將大象放到或者註冊到(bind)容器里
取大象,則需要將大象取出(make)就可以
服務容器可以理解為進階版的工廠模式,更是一種面向介面編程的思想。
工廠模式的大量應用降低了程式碼重複量以及利用率,但是依然還需要調用者去定位工廠。
最理想的情況是,調用者無需關心調用者的實現,也無需定位工廠,而面向介面配置化。
6.IoC容器程式碼
來看一段 IoC 容器程式碼,下面這段程式碼對 Laravel 的設計方法進行了簡化,不是 Laravel 的源碼, 而是來自一本書《laravel 框架關鍵技術解析》,這段程式碼很好的還原了 laravel 的服務容器的核心思想,程式碼有點長,可以嘗試運行調試一下,這樣易於理解:
// 設計容器類,容器類裝實例或提供實例的回調函數
class Container
{
// 容器綁定數組
// 用於裝提供實例的回調函數,真正的容器還會裝實例等其他內容,從而實現單例等高級功能
protected $bindings = [];
// 綁定介面和生成生成相應實例的回調函數
public function bind($abstract, $concrete = null, $shared = false)
{
if (!$concrete instanceof Closure) {
// 如果提供的參數不是回調參數,則產生默認的回調函數
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
// 默認生成實例的回調函數
public function getClosure($abstract, $concrete)
{
// 生成實例的回調函數,$c 一般為 IoC 容器對象,在調用回調生成實例時提供
// 即 build 函數中的 $concrete($this)
return function ($c) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
// 調用的是容器的 build 或 make 方法生成實例
return $c->$method($concrete);
};
}
// 生成實例對象,首先解決介面和要實例化類之間的依賴關係
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
return $object;
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
// 獲取綁定的回調函數
protected function getConcrete($abstract)
{
if (!isset($this->bindings[$abstract])) {
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
// 實例化對象
public function build($concrete)
{
if ($concrete instanceof Closure) {
return $concrete($this);
}
// ReflectionClass 類報告了一個類的有關資訊
$reflector = new ReflectionClass($concrete);
// 檢查類是否可實例化 return bool|false
if (!$reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable.";
}
// 獲取類的構造函數
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
return new $concrete;
}
// 獲取類構造函數的參數
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
protected function getDependencies($parameters)
{
$dependencies = [];
foreach ($parameters as $parameter) {
$dependency = $parameter->getClass();
if (is_null($dependency)) {
$dependencies[] = null;
} else {
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array)$dependencies;
}
protected function resolveClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
}
上面的程式碼就生成了一個容器,下面是如何使用容器
// 實例化 IoC 容器
$app = new Container();
// 完成容器的填充
$app->bind("traveller", "Traveller");
$app->bind("Visit", "Train");
// 通過容器實現依賴注入,完成類的實例化
$tra = $app->make("traveller");
$tra->visitTibet();
$tra = $app->make("Visit");
$tra->go();
程式碼解析
當實例化一個容器類(Container)後,向容器中填充服務
$app->bind("traveller", "Traveller");
$app->bind("Visit", "Train");
綁定完成後,查看容器 $bindings
綁定的值
array(2) {
["traveller"]=>
array(2) {
["concrete"]=>
object(Closure)#2 (3) {
["static"]=>
array(2) {
["abstract"]=>
string(9) "traveller"
["concrete"]=>
string(9) "Traveller"
}
["this"]=>
object(Container)#1 (1) {
["bindings":protected]=>
array(2) {
["traveller"]=>
*RECURSION*
["Visit"]=>
array(2) {
["concrete"]=>
object(Closure)#3 (3) {
["static"]=>
array(2) {
["abstract"]=>
string(5) "Visit"
["concrete"]=>
string(5) "Train"
}
["this"]=>
*RECURSION*
["parameter"]=>
array(1) {
["$c"]=>
string(10) "<required>"
}
}
["shared"]=>
bool(false)
}
}
}
["parameter"]=>
array(1) {
["$c"]=>
string(10) "<required>"
}
}
["shared"]=>
bool(false)
}
["Visit"]=>
array(2) {
["concrete"]=>
object(Closure)#3 (3) {
["static"]=>
array(2) {
["abstract"]=>
string(5) "Visit"
["concrete"]=>
string(5) "Train"
}
["this"]=>
object(Container)#1 (1) {
["bindings":protected]=>
array(2) {
["traveller"]=>
array(2) {
["concrete"]=>
object(Closure)#2 (3) {
["static"]=>
array(2) {
["abstract"]=>
string(9) "traveller"
["concrete"]=>
string(9) "Traveller"
}
["this"]=>
*RECURSION*
["parameter"]=>
array(1) {
["$c"]=>
string(10) "<required>"
}
}
["shared"]=>
bool(false)
}
["Visit"]=>
*RECURSION*
}
}
["parameter"]=>
array(1) {
["$c"]=>
string(10) "<required>"
}
}
["shared"]=>
bool(false)
}
}
當執行 $tra = $app->make("traveller");
時,程式就會用調用 make 方法,判斷是否已經綁定實例,若已綁定好則調用 build
獲取已經綁定好的閉包函數,開始解析,閉包函數在 build
方法中會執行 return $concrete($this)
將當前類作為參數為閉包函數傳參,最終又會執行到 build
方法,類似於遞歸調用,最後執行的 build
方法中 $concrete的值為字元串 Traveller
,通過反射獲取 class Traveller
的類有關資訊,再進行下一步
$reflector->isInstantiable() // 檢查類是否可實例化 return bool|false
,
$reflector->getConstructor(); //獲取類的構造函數
$constructor->getParameters(); // 獲取類構造函數的參數
再獲取構造函數中每個參數是否含依賴,$this->getDependencies($dependencies)
,這個方法知道了 class Traveller
含有依賴類 Visit
,我們要做的就是解決這個依賴
// $dependency
object(ReflectionClass)#7 (1) {
["name"]=>
string(5) "Visit"
}
通過 getDependencies ($parameters)
中的 $parameter->getClass()
獲取到依賴類 Visit
, 再調用 resolveClass (ReflectionParameter $parameter)
就會發現之前的為什麼要 bind
介面類,而不用具體實現類的原因了,因為通過介面類的名稱,在容易中獲得實例,會獲取到所對應的具體實現類,$app->bind("Visit", "Train");
最後我們通過 return $reflector->newInstanceArgs($instances);
獲取到了 Train
的具體實現類。
array(1) {
[0]=>
object(Train)#9 (0) {
}
}
到這裡 IoC 的流程就結束了,這就是其中控制反轉、依賴注入,閉包,反射等概念的關係及應用。