前言
通過實現laravel 框架功能,以便深入理解laravel框架的先進思想。
什麼是服務容器
服務容器是用來管理類依賴與執行依賴注入的工具。Laravel框架中就是使用服務容器來實現 控制反轉 和 依賴注入 。
什麼是控制反轉(IoC)和依賴注入(DI)
控制反轉(IoC) 就是說把建立物件的 控制權 進行轉移,以前建立物件的主動權和建立時機是由自己把控的,而現在這種權力轉移到第三方,也就是 Laravel 中的容器。
依賴注入(DI)則是幫助容器實現在執行中動態的為物件提供提依賴的資源。
概念容易不太容易讓人理解,舉個例子:
//我們構建一個人的類和一個狗的類
class People
{
public $dog = null;
public function __construct()
{
$this->dog = new Dog();
}
public function putDog(){
return $this->dog->dogCall();
}
}
class Dog{
public function dogCall(){
return '汪汪汪';
}
}
這個人在遛狗,突然遇到了死對頭,他於是放狗咬人
$people = new People();
$people->putDog();
在這個操作中,people類要執行putDog()
這個方法,需要依賴Dog類,一般我們像上面一樣,在people中利用建構函式來新增這個Dog依賴。如果使用控制反轉 依賴注入則是這個樣子
class People
{
public $dog = null;
public function __construct(Dog $dog)
{
$this->dog = $dog;
}
public function putDog(){
return $this->dog->dogCall();
}
}
People類通過構造引數宣告自己需要的 依賴類,由容器自動注入。這樣就實現了程式的有效解耦,好處在這就不多說了。
Laravel容器依賴注入的實現
實現原理需要了解的知識點:
閉包(匿名函式):
匿名函式(Anonymous functions),也叫閉包函式(closures),允許 臨時建立一個沒有指定名稱的函式反射:PHP 5 以上版本具有完整的反射 API,新增了對類、介面、函式、方法和擴充套件進行反向工程的能力。 此外,反射 API 提供了方法來取出函式、類和方法中的文件註釋
理解了閉包和反射的基本用法我們來看Laravel中是怎麼實現容器的,下面程式碼是我對laravel框架容器部分程式碼的簡化核心版:
class Container
{
/**
* 容器繫結,用來裝提供的例項或者 提供例項的回撥函式
* @var array
*/
public $building = [];
/**
* 註冊一個繫結到容器
*/
public function bind($abstract, $concrete = null, $shared = false)
{
if(is_null($concrete)){
$concrete = $abstract;
}
if(!$concrete instanceof Closure){
$concrete = $this->getClosure($abstract, $concrete);
}
$this->building[$abstract] = compact("concrete", "shared");
}
//註冊一個共享的繫結 單例
public function singleton($abstract, $concrete, $shared = true){
$this->bind($abstract, $concrete, $shared);
}
/**
* 預設生成例項的回撥閉包
*
* @param $abstract
* @param $concrete
* @return Closure
*/
public function getClosure($abstract, $concrete)
{
return function($c) use($abstract, $concrete){
$method = ($abstract == $concrete)? '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;
}
/**
* 獲取繫結的回撥函式
*/
public function getConcrete($abstract)
{
if(! isset($this->building[$abstract])){
return $abstract;
}
return $this->building[$abstract]['concrete'];
}
/**
* 判斷 是否 可以建立服務實體
*/
public function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
/**
* 根據例項具體名稱例項具體物件
*/
public function build($concrete)
{
if($concrete instanceof Closure){
return $concrete($this);
}
//建立反射物件
$reflector = new ReflectionClass($concrete);
if( ! $reflector->isInstantiable()){
//丟擲異常
throw new \Exception('無法例項化');
}
$constructor = $reflector->getConstructor();
if(is_null($constructor)){
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instance = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instance);
}
//通過反射解決引數依賴
public function getDependencies(array $dependencies)
{
$results = [];
foreach( $dependencies as $dependency ){
$results[] = is_null($dependency->getClass())
?$this->resolvedNonClass($dependency)
:$this->resolvedClass($dependency);
}
return $results;
}
//解決一個沒有型別提示依賴
public function resolvedNonClass(ReflectionParameter $parameter)
{
if($parameter->isDefaultValueAvailable()){
return $parameter->getDefaultValue();
}
throw new \Exception('出錯');
}
//通過容器解決依賴
public function resolvedClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
}
容器的工作流程
接著上面遛狗的例子:
//例項化容器類
$app = new Container();
//向容器中填充Dog
$app->bind('Dog','App\Dog');
//填充People
$app->bind('People', 'App\People');
//通過容器實現依賴注入,完成類的例項化;
$people = $app->make('People');
//呼叫方法
echo $people->putDog();
上面示例中我們先例項化容器類,然後使用bind()
方法 繫結介面和 生成相應的例項的閉包函式。然後使用make()
函式生成例項物件,在make()
中會呼叫 isBuildable($concrete, $abstract)
來判斷 給定的服務實體($concrete
引數)是否可以建立,可以建立 就會呼叫 build($concrete)
函式 ,build($concrete)
函式會判斷傳的引數是 是 閉包 還是 具體類名 ,如果是閉包則直接執行,如果是具體類名的話,則通過反射獲取該類的建構函式所需的依賴,完成例項化。
重點理解 下面這幾個函式中 反射的用法,應該就很好理解了
build($concrete)
getDependencies(array $dependencies)
resolvedNonClass(ReflectionParameter $parameter)
resolvedClass(ReflectionParameter $parameter)
最後
IoC 理解起來是有點難度,可能文中描述讓你感覺不是很清楚,可以將文中程式碼 在php中用debug觀察 執行狀態。
理解了容器的具體實現原理,再去看Laravel中的相關實現,就會感覺豁然開朗。