這篇文章主要講解了“怎么創(chuàng)建PHP DI容器”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么創(chuàng)建PHP DI容器”吧!
創(chuàng)新互聯(lián)長(zhǎng)期為近千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為寶清企業(yè)提供專業(yè)的網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì),寶清網(wǎng)站改版等技術(shù)服務(wù)。擁有十載豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
先開個(gè)車,為大家舉個(gè)栗子:
class Driver{ public function drive() { $car = new Car(); echo '老司機(jī)正在駕駛', $car->getCar(), PHP_EOL; } }class Car{ protected $name = '普通汽車'; public function getCar() { return $this->name; } }
有兩個(gè)類,Driver和Car,老司機(jī)Driver有個(gè)方法driver,在調(diào)用的時(shí)候首先得整輛車$car,然后發(fā)車。大多數(shù)同學(xué)都寫過這樣或者類似的代碼,這樣的代碼單看沒啥毛病,挺正常的。但是,如果我要換輛車,開普通車撩不到妹。
class Benz extends Car{ protected $name = '奔馳'; }
這時(shí)候就需要做一個(gè)比較惡心的操作了,得改老司機(jī)的代碼了。(老司機(jī):我做錯(cuò)了什么?換輛車還得讓我重學(xué)駕照……)。因此我們需要把讓Car為外界注入,將Driver和Car解耦,不是老司機(jī)自己開車的時(shí)候還得自己去造車。于是就有了下面的結(jié)果
class Driver{ protected $car; public function __construct(Car $car) { $this->car = $car; } public function drive() { echo '老司機(jī)正在駕駛', $this->car->getCar(), PHP_EOL; } }
此時(shí)Driver和Car兩個(gè)類已經(jīng)解耦,這兩個(gè)類的依賴,依靠上層代碼去管理。此時(shí),老司機(jī)會(huì)這樣“開車”:
$car = new Car(); $driver = new Driver($car); $driver->drive();
此時(shí),我們創(chuàng)建Driver依賴的實(shí)例,并注入。上面的例子,我們實(shí)現(xiàn)了依賴注入,不過是手動(dòng)的,寫起來感覺還是不爽。這么繁重的活怎么能手動(dòng)來做呢,得讓程序自己去做。于是乎,DI容器誕生。
依賴注入與IoC模式類似工廠模式,是一種解決調(diào)用者和被調(diào)用者依賴耦合關(guān)系的模式。它解決了對(duì)象之間的依賴關(guān)系,使得對(duì)象只依賴IoC/DI容器,不再直接相互依賴,實(shí)現(xiàn)松耦合,然后在對(duì)象創(chuàng)建時(shí),由IoC/DI容器將其依賴(Dependency)的對(duì)象注入(Inject)其內(nèi),這樣做可以最大程度實(shí)現(xiàn)松耦合。依賴注入說白一點(diǎn),就是容器將某個(gè)類依賴的其他類的實(shí)例注入到這個(gè)類的實(shí)例中。
這段話可能說的有點(diǎn)抽象,回到剛才的例子吧。剛剛我手動(dòng)完成了依賴注入,比較麻煩,如果一個(gè)大型的項(xiàng)目這樣做肯定會(huì)覺得很繁瑣,而且不夠優(yōu)雅。因此我們需要有一位總管代替我們?nèi)ジ蛇@個(gè),這個(gè)總管就是容器。類的依賴管理全部交給容器去完成。因此,一般來說容器是一個(gè)全局的對(duì)象,大家共有的。
寫一個(gè)功能,我們首先需要分析問題,因此我們先要明白,對(duì)于一個(gè)簡(jiǎn)單的DI容器需要哪些功能,這直接關(guān)系到我們代碼的編寫。對(duì)于一個(gè)簡(jiǎn)單的容器,至少需要滿足以下幾點(diǎn):
創(chuàng)建所需類的實(shí)例
完成依賴管理(DI)
可以獲取單例的實(shí)例
全局唯一
綜上,我們的容器類大約長(zhǎng)這樣:
class Container{ /** * 單例 * @var Container */ protected static $instance; /** * 容器所管理的實(shí)例 * @var array */ protected $instances = []; private function __construct(){} private function __clone(){} /** * 獲取單例的實(shí)例 * @param string $class * @param array ...$params * @return object */ public function singleton($class, ...$params) {} /** * 獲取實(shí)例(每次都會(huì)創(chuàng)建一個(gè)新的) * @param string $class * @param array ...$params * @return object */ public function get($class, ...$params) {} /** * 工廠方法,創(chuàng)建實(shí)例,并完成依賴注入 * @param string $class * @param array $params * @return object */ protected function make($class, $params = []) {} /** * @return Container */ public static function getInstance() { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } }
大體骨架已經(jīng)確定,接下來進(jìn)入最核心的make方法:
protected function make($class, $params = []){ //如果不是反射類根據(jù)類名創(chuàng)建 $class = is_string($class) ? new ReflectionClass($class) : $class; //如果傳的入?yún)⒉粸榭?,則根據(jù)入?yún)?chuàng)建實(shí)例 if (!empty($params)) { return $class->newInstanceArgs($params); } //獲取構(gòu)造方法 $constructor = $class->getConstructor(); //獲取構(gòu)造方法參數(shù) $parameterClasses = $constructor ? $constructor->getParameters() : []; if (empty($parameterClasses)) { //如果構(gòu)造方法沒有入?yún)?,直接?chuàng)建 return $class->newInstance(); } else { //如果構(gòu)造方法有入?yún)ⅲ⑦f歸創(chuàng)建依賴類實(shí)例 foreach ($parameterClasses as $parameterClass) { $paramClass = $parameterClass->getClass(); $params[] = $this->make($paramClass); } //最后根據(jù)創(chuàng)建的參數(shù)創(chuàng)建實(shí)例,完成依賴的注入 return $class->newInstanceArgs($params); } }
為了容器的易用,我做了一些完善:
實(shí)現(xiàn)ArrayAccess接口,使單例實(shí)例可以直接通過array的方式獲取,如果該實(shí)例沒有,則創(chuàng)建
重寫__get方法,更方便的獲取
最終版:
class Container implements ArrayAccess{ /** * 單例 * @var Container */ protected static $instance; /** * 容器所管理的實(shí)例 * @var array */ protected $instances = []; private function __construct(){} private function __clone(){} /** * 獲取單例的實(shí)例 * @param string $class * @param array ...$params * @return object */ public function singleton($class, ...$params) { if (isset($this->instances[$class])) { return $this->instances[$class]; } else { $this->instances[$class] = $this->make($class, $params); } return $this->instances[$class]; } /** * 獲取實(shí)例(每次都會(huì)創(chuàng)建一個(gè)新的) * @param string $class * @param array ...$params * @return object */ public function get($class, ...$params) { return $this->make($class, $params); } /** * 工廠方法,創(chuàng)建實(shí)例,并完成依賴注入 * @param string $class * @param array $params * @return object */ protected function make($class, $params = []) { //如果不是反射類根據(jù)類名創(chuàng)建 $class = is_string($class) ? new ReflectionClass($class) : $class; //如果傳的入?yún)⒉粸榭?,則根據(jù)入?yún)?chuàng)建實(shí)例 if (!empty($params)) { return $class->newInstanceArgs($params); } //獲取構(gòu)造方法 $constructor = $class->getConstructor(); //獲取構(gòu)造方法參數(shù) $parameterClasses = $constructor ? $constructor->getParameters() : []; if (empty($parameterClasses)) { //如果構(gòu)造方法沒有入?yún)?,直接?chuàng)建 return $class->newInstance(); } else { //如果構(gòu)造方法有入?yún)?,迭代并遞歸創(chuàng)建依賴類實(shí)例 foreach ($parameterClasses as $parameterClass) { $paramClass = $parameterClass->getClass(); $params[] = $this->make($paramClass); } //最后根據(jù)創(chuàng)建的參數(shù)創(chuàng)建實(shí)例,完成依賴的注入 return $class->newInstanceArgs($params); } } /** * @return Container */ public static function getInstance() { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } public function __get($class) { if (!isset($this->instances[$class])) { $this->instances[$class] = $this->make($class); } return $this->instances[$class]; } public function offsetExists($offset) { return isset($this->instances[$offset]); } public function offsetGet($offset) { if (!isset($this->instances[$offset])) { $this->instances[$offset] = $this->make($offset); } return $this->instances[$offset]; } public function offsetSet($offset, $value) { } public function offsetUnset($offset) { unset($this->instances[$offset]); } }
現(xiàn)在借助容器我們寫一下上面的代碼:
$driver = $app->get(Driver::class); $driver->drive();//output:老司機(jī)正在駕駛普通汽車復(fù)制代碼
就這么簡(jiǎn)單,老司機(jī)就能發(fā)車。這里默認(rèn)注入的是Car的實(shí)例,如果需要開奔馳,那只需要這樣:
$benz = $app->get(Benz::class); $driver = $app->get(Driver::class, $benz); $driver->drive();//output:老司機(jī)正在駕駛奔馳復(fù)制代碼
按照PSR-11的要求,依賴注入容器需要實(shí)現(xiàn)Psr\Container\ContainerInterface接口,這里只是演示并未去實(shí)現(xiàn),因?yàn)槟切枰隤sr依賴庫(kù),比較麻煩,其實(shí)也很簡(jiǎn)單,只是多了幾個(gè)方法,有興趣的可以自己去了解下PSR-11的要求(傳送門)。
這里只是實(shí)現(xiàn)了一個(gè)非常簡(jiǎn)陋的DI容器,實(shí)際中還需要考慮很多,而且這里的容器功能上還很簡(jiǎn)陋。還有一些坑沒處理,比如出現(xiàn)循環(huán)依賴怎么處理、延遲加載的機(jī)制……
感謝各位的閱讀,以上就是“怎么創(chuàng)建PHP DI容器”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)怎么創(chuàng)建PHP DI容器這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!