本文實(shí)例講述了PHP依賴注入與Ioc容器。分享給大家供大家參考,具體如下:
背景
在很多編程語言(例如java)開發(fā)中,程序員在某個(gè)類中需要依賴其它類的方法,則通常是new一個(gè)依賴類再調(diào)用類實(shí)例的方法,這種開發(fā)存在的問題是new的類實(shí)例不好統(tǒng)一管理,一旦有修改,牽扯的類會(huì)很多。
最早在java的spring提出了依賴注入的思想,即依賴類不由程序員實(shí)例化,而是通過spring容器幫我們new指定實(shí)例并且將實(shí)例注入到需要該對(duì)象的類中。目前許多主流PHP框架也使用了依賴注入容器,如ThinkPHP、Laravel等。
一、概念
1、容器:字面上理解就是裝東西的東西。常見的變量、對(duì)象屬性等都可以算是容器。一個(gè)容器能夠裝什么,全部取決于你對(duì)該容器的定義。當(dāng)然,現(xiàn)在我們討論的是這樣一種容器,它存放的不是文本、數(shù)值,而是對(duì)象、對(duì)象的描述(類、接口)或者是提供對(duì)象的回調(diào)(閉包),通過這種容器,我們得以實(shí)現(xiàn)許多高級(jí)的功能,其中最常提到的,就是 “解耦”、“依賴注入”。
2、IoC - Inversion of Control 控制反轉(zhuǎn)
控制反轉(zhuǎn)是從容器的角度在描述,即:容器控制應(yīng)用程序,由容器反向的向應(yīng)用程序注入應(yīng)用程序所需要的外部資源。
3、DI - Dependency Injection 依賴注入
依賴注入是從應(yīng)用程序的角度在描述,可以把依賴注入,即:應(yīng)用程序依賴容器創(chuàng)建并注入它所需要的外部資源。
備注:依賴注入和控制反轉(zhuǎn)說的是同一個(gè)東西,是一種設(shè)計(jì)模式,這種設(shè)計(jì)模式用來減少程序間的耦合,從某個(gè)方面講,就是它們描述的角度不同。
二、依賴注入的原理
一般情況下,當(dāng)存在類與類之間的依賴關(guān)系的時(shí)候,我們都是通過直接實(shí)例化的方式進(jìn)行調(diào)用。一旦出現(xiàn)多層依賴,這種方式的耦合程度就很高,在需要修改其中一個(gè)類的時(shí)候,會(huì)牽扯很多依賴它的類的修改,因此對(duì)代碼的改動(dòng)會(huì)比較大。
下面簡單舉一個(gè)A->B->C三層依賴的關(guān)系解釋怎么運(yùn)用依賴注入來解耦,提高開發(fā)效率。
而依賴注入方式如下:
解析:
常規(guī)寫法里面,一旦C類需要作出改變,或者B類的調(diào)用需要改變成D類的時(shí)候,還需要考慮到依賴自己的B類,即還需要對(duì)B類作出修改。
依賴注入的思想就是即用即實(shí)例,反轉(zhuǎn)類與類之間的控制關(guān)系,實(shí)現(xiàn)由調(diào)用類A類控制后續(xù)的依賴關(guān)系,這樣可以讓B類隨意的更改所需依賴和實(shí)例化的類(C類或D類),達(dá)到解耦的目的。
三、常用的依賴注入方式:
1、構(gòu)造方法注入;2、set屬性注入;3、靜態(tài)工廠方法注入;
上述的例子使用的就是構(gòu)造方法注入的方式,將對(duì)象作為參數(shù)傳遞到構(gòu)造方法中;同樣的set屬性注入也是相類似的方法,不同的僅僅是在set一個(gè)類的成員的屬性時(shí)傳遞這個(gè)對(duì)象參數(shù),在此就不一一舉例了。
除此之外,還有靜態(tài)工廠方法注入的方式,這種方法與靜態(tài)工廠方法類似。
我們知道靜態(tài)工廠方法就是通過一個(gè)類來管理需要實(shí)例化的多個(gè)相似的類,該類會(huì)定義一個(gè)方法用于獲取需要實(shí)例化的對(duì)象,而具體要實(shí)例化哪個(gè)對(duì)象就依賴于傳遞進(jìn)來的對(duì)象名參數(shù)了。
對(duì)于靜態(tài)工廠方式的注入,與一般的靜態(tài)工廠方法不同之處在于這個(gè)傳進(jìn)來的參數(shù)是一個(gè)已經(jīng)實(shí)例化過的對(duì)象。
?php
class IoC
{
protected static $registry = [];
public static function bind($name, Callable $resolver) //傳入類名和類對(duì)象實(shí)例
{
static::$registry[$name] = $resolver;
}
public static function make($name) //靜態(tài)工廠方法
{
if (isset(static::$registry[$name])) {
$resolver = static::$registry[$name];
return $resolver(); //實(shí)例化
}
throw new Exception('Alias does not exist in the IoC registry.');
}
}
總而言之,三種方式傳遞的都是實(shí)例化對(duì)象,只是不同之處在于傳遞的位置分別為構(gòu)造方法、set屬性、靜態(tài)工廠方法而已。
四、依賴注入容器(Ioc容器)
大多數(shù)時(shí)侯,在使用依賴注入方式解耦組件時(shí),并不需要用到容器。
當(dāng)一段程序需要實(shí)例化的類太多或者依賴太多的時(shí)候,重復(fù)依賴注入的代碼是比較繁瑣的事情,例如以下情況:
當(dāng)產(chǎn)生以上關(guān)系的時(shí)候,依賴注入的代碼會(huì)比較混亂,而且存在重復(fù),更有可能在調(diào)用一個(gè)一般方法時(shí)new一個(gè)不需要的類,產(chǎn)生冗余。
此時(shí)需要使用容器,使用依賴注入容器后的思路是應(yīng)用程序需要到A類,就從容器內(nèi)取得A類。具體是容器創(chuàng)建C類,再創(chuàng)建B類并把C注入,再創(chuàng)建A類,并把B類注入,應(yīng)用程序調(diào)用A類方法, A類調(diào)用B類方法,接著做些其它工作.總之容器負(fù)責(zé)實(shí)例化,注入依賴,處理依賴關(guān)系等工作。
對(duì)于實(shí)際開發(fā)中復(fù)雜多變的代碼環(huán)境,我們并不能完全知道現(xiàn)在的類在未來會(huì)擴(kuò)展成什么情況,因此我們需要在有新的依賴類加入的時(shí)候,通過容器去實(shí)現(xiàn)實(shí)例化該類的方法。因此,在實(shí)例化未知類的時(shí)候,最能探索一個(gè)類的內(nèi)部結(jié)構(gòu)和實(shí)例化的方法就是利用反射,由此可知,反射是容器管理各個(gè)依賴類的核心。我們可以通過實(shí)例來了解容器的內(nèi)部實(shí)現(xiàn):
三個(gè)存在依賴關(guān)系的類:文件testClass.php
?php //依賴關(guān)系:Company->Department->Group
class Group
{
public function doSomething()
{
echo __CLASS__.":".'hello', '|';
}
}
class Department
{
private $group;
public function __construct(Group $group)
{
$this->group = $group;
}
public function doSomething()
{
$this->group->doSomething();
echo __CLASS__.":".'hello', '|';
}
}
class Company
{
private $department;
public function __construct(Department $department)
{
$this->department = $department;
}
public function doSomething()
{
$this->department->doSomething();
echo __CLASS__.":".'hello', '|';
}
}
Ioc容器的內(nèi)部實(shí)現(xiàn):
?php
class Container
{
private $s = array();
public function __set($k, $c)
{
$this->s[$k] = $c;
}
public function __get($k)
{
return $this->build($this->s[$k]);
}
/**
* 自動(dòng)綁定(Autowiring)自動(dòng)解析(Automatic Resolution)
*
* @param string $className
* @return object
* @throws Exception
*/
public function build($className)
{
// 如果是匿名函數(shù)(Anonymous functions),也叫閉包函數(shù)(closures)
if ($className instanceof Closure) {
// 執(zhí)行閉包函數(shù),并將結(jié)果
return $className($this);
}
/*通過反射獲取類的內(nèi)部結(jié)構(gòu),實(shí)例化類*/
$reflector = new ReflectionClass($className);
// 檢查類是否可實(shí)例化, 排除抽象類abstract和對(duì)象接口interface
if (!$reflector->isInstantiable()) {
throw new Exception("Can't instantiate this.");
}
/** @var ReflectionMethod $constructor 獲取類的構(gòu)造函數(shù) */
$constructor = $reflector->getConstructor();
// 若無構(gòu)造函數(shù),直接實(shí)例化并返回
if (is_null($constructor)) {
return new $className;
}
// 取構(gòu)造函數(shù)參數(shù),通過 ReflectionParameter 數(shù)組返回參數(shù)列表
$parameters = $constructor->getParameters();
// 遞歸解析構(gòu)造函數(shù)的參數(shù)
$dependencies = $this->getDependencies($parameters);
// 創(chuàng)建一個(gè)類的新實(shí)例,給出的參數(shù)將傳遞到類的構(gòu)造函數(shù)。
return $reflector->newInstanceArgs($dependencies);
}
/**
* @param array $parameters
* @return array
* @throws Exception
*/
public function getDependencies($parameters)
{
$dependencies = [];
/** @var ReflectionParameter $parameter */
foreach ($parameters as $parameter) {
/** @var ReflectionClass $dependency */
$dependency = $parameter->getClass();
if (is_null($dependency)) {
// 是變量,有默認(rèn)值則設(shè)置默認(rèn)值
$dependencies[] = $this->resolveNonClass($parameter);
} else {
// 是一個(gè)類,遞歸解析
$dependencies[] = $this->build($dependency->name);
}
}
return $dependencies;
}
/**
* @param ReflectionParameter $parameter
* @return mixed
* @throws Exception
*/
public function resolveNonClass($parameter)
{
// 有默認(rèn)值則返回默認(rèn)值
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
throw new Exception('I have no idea what to do here.');
}
}
require_once "./testclass.php"; //開始測(cè)試,先測(cè)試已知依賴關(guān)系的情況
$c = new Container();
$c->department = 'Department';
$c->company = function ($c) {
return new Company($c->department);
};
// 從容器中取得company
$company = $c->company;
$company->doSomething(); //輸出: Group:hello|Department:hello|Company:hello|
// 測(cè)試未知依賴關(guān)系,直接使用的方法
$di = new Container();
$di->company = 'Company';
$company = $di->company;
$company->doSomething();//輸出: Group:hello|Department:hello|Company:hello|
我們可以通過一張圖解釋Ioc容器的內(nèi)部邏輯:
五、總結(jié)
IOC的基本概念是:不創(chuàng)建對(duì)象,但是描述創(chuàng)建它們的方式。在代碼中不直接與對(duì)象和服務(wù)連接,但在配置文件中描述哪一個(gè)組件需要哪一項(xiàng)服務(wù)。Spring容器負(fù)責(zé)將這些聯(lián)系在一起。也就是說,Spring的IOC負(fù)責(zé)管理各種對(duì)象的創(chuàng)建、清除以及它們之間的聯(lián)系。
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門教程》、《PHP數(shù)組(Array)操作技巧大全》、《PHP基本語法入門教程》、《PHP運(yùn)算與運(yùn)算符用法總結(jié)》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫操作入門教程》及《php常見數(shù)據(jù)庫操作技巧匯總》
希望本文所述對(duì)大家PHP程序設(shè)計(jì)有所幫助。
您可能感興趣的文章:- 詳解php命令注入攻擊
- php使用exec shell命令注入的方法講解
- 淺析PHP反序列化中過濾函數(shù)使用不當(dāng)導(dǎo)致的對(duì)象注入問題
- PHP依賴注入容器知識(shí)點(diǎn)淺析
- PHP使用PDO實(shí)現(xiàn)mysql防注入功能詳解
- PHP防止sql注入小技巧之sql預(yù)處理原理與實(shí)現(xiàn)方法分析
- php+laravel依賴注入知識(shí)點(diǎn)總結(jié)
- php依賴注入知識(shí)點(diǎn)詳解
- php中的依賴注入實(shí)例詳解
- thinkphp5.1框架容器與依賴注入實(shí)例分析
- php反射學(xué)習(xí)之依賴注入示例
- CTF命令執(zhí)行及繞過技巧