php 容器实现,PHP 依赖注入容器实现
在看 Laravel 文檔的時候發(fā)現(xiàn)入門指南的下一章便是核心架構(gòu),對于我這種按部就班往下讀的同學(xué)這簡直是勸退篇。各種之前沒有接觸過的概念砸得人頭暈,容器便是其中之一。不過在拜讀過幾篇文章后也逐漸理解了容器的作用,所以特此總結(jié)一番。
0x01 為何要有容器?
這個問題可以也可以替換為「容器解決了什么問題?」。在此之前我們需要理解依賴注入這個概念,可以看一下這篇文章: 簡單解釋什么是 依賴注入 和 控制反轉(zhuǎn)
。在實踐依賴注入的時候我們會遇到一個問題,這里我將通過示例代碼解釋,代碼如下:
class Bread
{
}
class Bacon
{
}
class Hamburger
{
protected $materials;
public function __construct(Bread $bread, Bacon $bacon)
{
$this->materials = [$bread, $bacon];
}
}
class Cola
{
}
class Meal
{
protected $food;
protected $drink;
public function __construct(Hamburger $hamburger, Cola $cola)
{
$this->food = $hamburger;
$this->drink = $cola;
}
}
復(fù)制代碼
上面是按照依賴注入實現(xiàn)的一段代碼,我們可以看見套餐類(Meal)依賴漢堡類(Hamburger)和可樂類(Cola),并且漢堡類又依賴于面包類(Bread)和培根類(Bacon)。通過依賴注入能達(dá)到松耦合的效果但是這也使得實例化一個有多個依賴的類會變得十分麻煩,下面這段代碼是實例化一個套餐類的示例:
$bread = new Bread();
$bacon = new Bacon();
$hamburger = new Hamburger($bread, $bacon);
$cola = new Cola();
$meal = new Meal($hamburger, $cola);
復(fù)制代碼
可以看見為了獲得一個套餐對象,我們需要先實例化該對象的依賴,如果依賴還存在依賴,我們還需要在實例化依賴的依賴……為了解決這個問題容器就應(yīng)運而生了,容器的定位就是「管理類的依賴和執(zhí)行依賴注入的工具」。通過容器我們可以將實例化這個過程給自動化,比如我們可以直接用一行代碼獲取套餐對象:
$container->get(Meal::class);
復(fù)制代碼
0x01 簡單容器的實現(xiàn)
下面這段代碼是一個簡單容器的實現(xiàn):
class Container
{
/**
* @var Closure[]
*/
protected $binds = [];
/**
* Bind class by closure.
*
* @param string $class
* @param Closure $closure
* @return $this
*/
public function bind(string $class, Closure $closure)
{
$this->binds[$class] = $closure;
return $this;
}
/**
* Get object by class
*
* @param string $class
* @param array $params
* @return object
*/
public function make(string $class, array $params = [])
{
if (isset($this->binds[$class])) {
return ($this->binds[$class])->call($this, $this, ...$params);
}
return new $class(...$params);
}
}
復(fù)制代碼
這個容器只有兩個方法 bind
和 make
, bind
方法將一個類名和一個閉包進(jìn)行綁定,然后 make
方法將執(zhí)行指定類名對應(yīng)的閉包,并返回該閉包的返回值。我們通過容器的使用示例加深理解:
$container = new Container();
$container->bind(Hamburger::class, function (Container $container) {
$bread = $container->make(Bread::class);
$bacon = $container->make(Bacon::class);
return new Hamburger($bread, $bacon);
});
$container->bind(Meal::class, function (Container $container) {
$hamburger = $container->make(Hamburger::class);
$cola = $container->make(Cola::class);
return new Meal($hamburger, $cola);
});
// 輸出 Meal
echo get_class($container->make(Meal::class));
復(fù)制代碼
通過上面這個例子我們可以知道 bind
方法傳遞的是一個「返回類名對應(yīng)的實例化對象」的閉包,而且該閉包還接收該容器作為參數(shù),所以我們還可以在該閉包內(nèi)使用容器獲取依賴。上面這段代碼雖然看起來似乎比使用 new
關(guān)鍵字還復(fù)雜,但實際上對每一個類,我們只需要 bind
一次即可。以后每次需要該對象直接用 make
方法即可,在我們的工程中肯定會節(jié)省很多代碼量。
0x02 通過反射強化容器
「反射」官方手冊php.net/manual/zh/b…
在上面的的簡單容器的例子里,我們還需要通過 bind
方法寫好實例化的「腳本」,那我們試想有沒有一種方法能夠直接生成我們需要的實例呢?其實通過「反射」并在構(gòu)造函數(shù)指定參數(shù)的「類型提示類」我們就能實現(xiàn)自動解決依賴的功能。因為通過反射我們可以獲取指定類構(gòu)造函數(shù)所需要的參數(shù)和參數(shù)類型,所以我們的容器可以自動解決這些依賴。示例代碼如下:
/**
* Get object by class
*
* @param string $class
* @param array $params
* @return object
*/
public function make(string $class, array $params = [])
{
if (isset($this->binds[$class])) {
return ($this->binds[$class])->call($this, $this, ...$params);
}
return $this->resolve($abstract);
}
/**
* Get object by reflection
*
* @param $abstract
* @return object
* @throws ReflectionException
*/
protected function resolve($abstract)
{
// 獲取反射對象
$constructor = (new ReflectionClass($abstract))->getConstructor();
// 構(gòu)造函數(shù)未定義,直接實例化對象
if (is_null($constructor)) {
return new $abstract;
}
// 獲取構(gòu)造函數(shù)參數(shù)
$parameters = $constructor->getParameters();
$arguments = [];
foreach ($parameters as $parameter) {
// 獲得參數(shù)的類型提示類
$paramClassName = $parameter->getClass()->name;
// 參數(shù)沒有類型提示類,拋出異常
if (is_null($paramClassName)) {
throw new Exception('Fail to get instance by reflection');
}
// 實例化參數(shù)
$arguments[] = $this->make($paramClassName);
}
return new $abstract(...$arguments);
}
復(fù)制代碼
以上代碼基于只是修改了原容器類的 make
方法, binds
數(shù)組中沒有找到指定類綁定的閉包后執(zhí)行 resolve
方法。其中 resolve
方法只是簡單的通過反射獲取指定類的構(gòu)造函數(shù)并將其依賴實例化,最后實例化指定類。到了這一步以后我們實例化套餐類就真的只需要一行代碼了,連配置都不用:-D。
$container->make(Meal::class);
復(fù)制代碼
當(dāng)然現(xiàn)在這個容器還是相當(dāng)簡陋的,因為如果指定類依賴標(biāo)量值(比如:字符串,數(shù)組,數(shù)值等非對象類型)會直接拋出異常,也無法指定部分依賴并且如果依賴的是接口的話還會出錯/(ㄒoㄒ)/~~,但這些功能都在一些成熟的容器庫都有。如果感興趣可以去看它們的源代碼,這里我推薦看 Pipmle
這個項目。
0x03 總結(jié)
本文主要介紹了容器的應(yīng)用場景并實現(xiàn)了一個簡單的容器,通過使用容器我們能夠很方便的解決依賴注入帶來的問題。但是容器也并不是沒有缺點,因為大部分容器都應(yīng)用了反射技術(shù),這會帶來較大的性能消耗而且通過容器間接生成的實例 IDE 往往不能識別它的類型,所以就不會有自動提示(可以通過寫文檔注釋解決)。不過個人感覺引入容器其實還是利大于弊滴(純屬個人感覺)!
總結(jié)
以上是生活随笔為你收集整理的php 容器实现,PHP 依赖注入容器实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php try catch 作用域,ph
- 下一篇: php怎么删除表数据,php怎样删除数据