前言
本文對將系統的對 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 的流程就結束了,這就是其中控制反轉、依賴注入,閉包,反射等概念的關係及應用。