摘要:標(biāo)量參數(shù)關(guān)聯(lián)傳值依賴是自動(dòng)解析注入的,剩余的標(biāo)量參數(shù)則可以通過(guò)關(guān)聯(lián)傳值,這樣比較靈活,沒(méi)必要把默認(rèn)值的參數(shù)放在函數(shù)參數(shù)最尾部。
更新:github(給個(gè)小星星呀)
-- 2018-4-11:優(yōu)化服務(wù)綁定方法 ::bind 的類型檢查模式
借助 PHP 反射機(jī)制實(shí)現(xiàn)的一套 依賴自動(dòng)解析注入 的 IOC/DI 容器,可以作為 Web MVC 框架 的應(yīng)用容器
1、依賴的自動(dòng)注入:你只需要在需要的位置注入你需要的依賴即可,運(yùn)行時(shí)容器會(huì)自動(dòng)解析依賴(存在子依賴也可以自動(dòng)解析)將對(duì)應(yīng)的實(shí)例注入到你需要的位置。
2、依賴的單例注入:某些情況下我們需要保持依賴的全局單例特性,比如 Web 框架中的 Request 依賴,我們需要將整個(gè)請(qǐng)求響應(yīng)周期中的所有注入 Request 依賴的位置同步為在路由階段解析完請(qǐng)求體的 Request 實(shí)例,這樣我們?cè)谌魏挝恢枚伎梢栽L問(wèn)全局的請(qǐng)求體對(duì)象。
3、依賴的契約注入:比如我們依賴某 Storage,目前使用 FileStorage 來(lái)實(shí)現(xiàn),后期發(fā)現(xiàn)性能瓶頸,要改用 RedisStorage 來(lái)實(shí)現(xiàn),如果代碼中大量使用 FileStorage 作為依賴注入,這時(shí)候就需要花費(fèi)精力去改代碼了。我們可以使用接口 Storage 作為契約,將具體的實(shí)現(xiàn)類 FileStorage / RedisStorage 通過(guò)容器的綁定機(jī)制關(guān)聯(lián)到 Storage 上,依賴注入 Storage,后期切換存儲(chǔ)引擎只需要修改綁定即可。
4、標(biāo)量參數(shù)關(guān)聯(lián)傳值:依賴是自動(dòng)解析注入的,剩余的標(biāo)量參數(shù)則可以通過(guò)關(guān)聯(lián)傳值,這樣比較靈活,沒(méi)必要把默認(rèn)值的參數(shù)放在函數(shù)參數(shù)最尾部。這點(diǎn)我還是蠻喜歡 python 的函數(shù)傳值風(fēng)格的。
function foo($name, $age = 27, $sex) { // php 沒(méi)辦法 foo($name = "big cat", $sex = "male") 這樣傳值 // 只能 foo("big cat", 27, "male") 傳值... // python 可以 foo(name = "big cat", sex = "male") 很舒服 }
但這也使得我的容器不支持位序傳值,必須保證運(yùn)行參數(shù)的鍵名與運(yùn)行方法的參數(shù)名準(zhǔn)確的關(guān)聯(lián)映(有默認(rèn)值的參數(shù)可以省略),我想著并沒(méi)有什么不方便的地方吧,我不喜歡給 $bar 參數(shù)傳遞個(gè) $foo 變量。
容器源碼$provider, "singleton" => $singleton, ]; } /** * 獲取類實(shí)例 * 通過(guò)反射獲取構(gòu)造參數(shù) * 返回對(duì)應(yīng)的類實(shí)例 * @param [type] $class_name [description] * @return [type] [description] */ private static function getInstance($class_name) { //方法參數(shù)分為 params 和 default_values //如果一個(gè)開(kāi)放構(gòu)造類作為依賴注入傳入它類,我們應(yīng)該將此類注冊(cè)為全局單例服務(wù) $params = static::getParams($class_name); return (new ReflectionClass($class_name))->newInstanceArgs($params["params"]); } /** * 反射方法參數(shù)類型 * 對(duì)象參數(shù):構(gòu)造對(duì)應(yīng)的實(shí)例 同時(shí)檢查是否為單例模式的實(shí)例 * 標(biāo)量參數(shù):返回參數(shù)名 索引路由參數(shù)取值 * 默認(rèn)值參數(shù):檢查路由參數(shù)中是否存在本參數(shù) 無(wú)則取默認(rèn)值 * @param [type] $class_name [description] * @param string $method [description] * @return [type] [description] */ private static function getParams($class_name, $method = "__construct") { $params_set["params"] = array(); $params_set["default_values"] = array(); //反射檢測(cè)類是否顯示聲明或繼承父類的構(gòu)造方法 //若無(wú)則說(shuō)明構(gòu)造參數(shù)為空 if ($method == "__construct") { $classRf = new ReflectionClass($class_name); if (! $classRf->hasMethod("__construct")) { return $params_set; } } //反射方法 獲取參數(shù) $methodRf = new ReflectionMethod($class_name, $method); $params = $methodRf->getParameters(); if (! empty($params)) { foreach ($params as $key => $param) { if ($paramClass = $param->getClass()) {// 對(duì)象參數(shù) 獲取對(duì)象實(shí)例 $param_class_name = $paramClass->getName(); if (array_key_exists($param_class_name, static::$dependencyServices)) {// 是否為注冊(cè)的服務(wù) if (static::$dependencyServices[$param_class_name]["singleton"]) {// 單例模式直接返回已注冊(cè)的實(shí)例 $params_set["params"][] = static::$dependencyServices[$param_class_name]["provider"]; } else {// 非單例則返回提供者的新的實(shí)例 $params_set["params"][] = static::getInstance(static::$dependencyServices[$param_class_name]["provider"]); } } else {// 沒(méi)有做綁定注冊(cè)的類 $params_set["params"][] = static::getInstance($param_class_name); } } else {// 標(biāo)量參數(shù) 獲取變量名作為路由映射 包含默認(rèn)值的記錄默認(rèn)值 $param_name = $param->getName(); if ($param->isDefaultValueAvailable()) {// 是否包含默認(rèn)值 $param_default_value = $param->getDefaultValue(); $params_set["default_values"][$param_name] = $param_default_value; } $params_set["params"][] = $param_name; } } } return $params_set; } /** * 容器的運(yùn)行入口 主要負(fù)責(zé)加載類方法,并將運(yùn)行所需的標(biāo)量參數(shù)做映射和默認(rèn)值處理 * @param [type] $class_name 運(yùn)行類 * @param [type] $method 運(yùn)行方法 * @param array $params 運(yùn)行參數(shù) * @return [type] 輸出 */ public static function run($class_name, $method, array $params = array()) { if (! class_exists($class_name)) { throw new Exception($class_name . "not found!", 4040); } if (! method_exists($class_name, $method)) { throw new Exception($class_name . "::" . $method . " not found!", 4041); } // 獲取要運(yùn)行的類 $classInstance = static::getInstance($class_name); // 獲取要運(yùn)行的方法的參數(shù) $method_params = static::getParams($class_name, $method); // 關(guān)聯(lián)傳入的運(yùn)行參數(shù) $method_params = array_map(function ($param) use ($params, $method_params) { if (is_object($param)) {// 對(duì)象參數(shù) 以完成依賴解析的具體實(shí)例 return $param; } // 以下為關(guān)聯(lián)傳值 可通過(guò)參數(shù)名映射的方式關(guān)聯(lián)傳值 可省略含有默認(rèn)值的參數(shù) if (array_key_exists($param, $params)) {// 映射傳遞路由參數(shù) return $params[$param]; } if (array_key_exists($param, $method_params["default_values"])) {// 默認(rèn)值 return $method_params["default_values"][$param]; } throw new Exception($param . " is necessary parameters", 4042); // 路由中沒(méi)有的則包含默認(rèn)值 }, $method_params["params"]); // 運(yùn)行 return call_user_func_array([$classInstance, $method], $method_params); } }演示所需的依賴類
// 它將被以單例模式注入 全局的所有注入點(diǎn)都使用的同一實(shí)例 class Foo { public $msg = "foo nothing to say!"; public function index() { $this->msg = "foo hello, modified by index method!"; } } // 它將以普通依賴模式注入 各注入點(diǎn)會(huì)分別獲取一個(gè)實(shí)例 class Bar { public $msg = "bar nothing to say!"; public function index() { $this->msg = "bar hello, modified by index method!"; } } // 契約注入 interface StorageEngine { public function info(); } // 契約實(shí)現(xiàn) class FileStorageEngine implements StorageEngine { public $msg = "file storage engine!" . PHP_EOL; public function info() { $this->msg = "file storage engine!" . PHP_EOL; } } // 契約實(shí)現(xiàn) class RedisStorageEngine implements StorageEngine { public $msg = "redis storage engine!" . PHP_EOL; public function info() { $this->msg = "redis storage engine!" . PHP_EOL; } }演示所需的運(yùn)行類
// 具體的運(yùn)行類 class BigCatController { public $foo; public $bar; // 這里自動(dòng)注入一次 Foo 和 Bar 的實(shí)例 public function __construct(Foo $foo, Bar $bar) { $this->foo = $foo; $this->bar = $bar; } // 這里的參數(shù)你完全可以亂序的定義(我故意寫的很亂序),你只需保證 route 參數(shù)中存在對(duì)應(yīng)的必要參數(shù)即可 // 默認(rèn)值參數(shù)可以直接省略 public function index($name = "big cat", Foo $foo, $sex = "male", $age, Bar $bar, StorageEngine $se) { // Foo 為單例模式注入 $this->foo $foo 是同一實(shí)例 $this->foo->index(); echo $this->foo->msg . PHP_EOL; echo $foo->msg . PHP_EOL; echo "------------------------------" . PHP_EOL; // Bar 為普通模式注入 $this->bar $bar 為兩個(gè)不同的 Bar 的實(shí)例 $this->bar->index(); echo $this->bar->msg . PHP_EOL; echo $bar->msg . PHP_EOL; echo "------------------------------" . PHP_EOL; // 契約注入 具體看你為契約者綁定了哪個(gè)具體的實(shí)現(xiàn)類 // 我們綁定的 RedisStorageEngine 所以這里注入的是 RedisStorageEngine 的實(shí)例 $se->info(); echo $se->msg; echo "------------------------------" . PHP_EOL; // 返回個(gè)值 return "name " . $name . ", age " . $age . ", sex " . $sex . PHP_EOL; } }運(yùn)行
// 路由信息很 MVC 吧 $route = [ "controller" => BigCatController::class, // 運(yùn)行的類 "action" => "index", // 運(yùn)行的方法 "params" => [ // 運(yùn)行的參數(shù) "name" => "big cat", "age" => 27 // sex 有默認(rèn)值 不傳 ] ]; try { // 依賴的單例注冊(cè) IOCContainer::singleton(Foo::class, new Foo()); // 依賴的契約注冊(cè) StorageEngine 相當(dāng)于契約者 注冊(cè)關(guān)聯(lián)具體的實(shí)現(xiàn)類 // IOCContainer::bind(StorageEngine::class, FileStorageEngine::class); IOCContainer::bind(StorageEngine::class, RedisStorageEngine::class); // 運(yùn)行 $result = IOCContainer::run($route["controller"], $route["action"], $route["params"]); echo $result; } catch (Exception $e) { echo $e->getMessage(); }運(yùn)行結(jié)果
foo hello, modified by index method! foo hello, modified by index method! ------------------------------ bar hello, modified by index method! bar nothing to say! ------------------------------ redis storage engine! ------------------------------ name big cat, age 27, sex male
簡(jiǎn)單的實(shí)現(xiàn)了像 laraval 的 IOC 容器的特性,但比它多一項(xiàng)(可能也比較雞肋)標(biāo)量參數(shù)的關(guān)聯(lián)傳值,不過(guò)我這功能也限定死了你傳入的參數(shù)必須與函數(shù)定義的參數(shù)名相關(guān)聯(lián),可我還是覺(jué)得能充分的填補(bǔ)默認(rèn)參數(shù)不放在參數(shù)尾就無(wú)法跳過(guò)的強(qiáng)迫癥問(wèn)題.....
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/23216.html
摘要:代碼這就是控制反轉(zhuǎn)模式。是變量有默認(rèn)值則設(shè)置默認(rèn)值是一個(gè)類,遞歸解析有默認(rèn)值則返回默認(rèn)值從容器中取得以上代碼的原理參考官方文檔反射,具有完整的反射,添加了對(duì)類接口函數(shù)方法和擴(kuò)展進(jìn)行反向工程的能力。 PHP程序員如何理解依賴注入容器(dependency injection container) 背景知識(shí) 傳統(tǒng)的思路是應(yīng)用程序用到一個(gè)Foo類,就會(huì)創(chuàng)建Foo類并調(diào)用Foo類的方法,假如這...
摘要:前言最近在使用框架,看了下他的源碼,發(fā)現(xiàn)有很多地方也用到了依賴注入控制反轉(zhuǎn),覺(jué)得有必要和大家簡(jiǎn)單聊一聊什么是依賴注入以及怎么使用它。概念依賴注入和控制反轉(zhuǎn)是對(duì)同一件事情的不同描述,從某個(gè)方面講,就是它們描述的角度不同。 前言 最近在使用ThinkPHP5框架,看了下他的源碼,發(fā)現(xiàn)有很多地方也用到了依賴注入(控制反轉(zhuǎn)),覺(jué)得有必要和大家簡(jiǎn)單聊一聊什么是依賴注入以及怎么使用它。 簡(jiǎn)介 I...
摘要:依賴注入容器管理應(yīng)用程序中的全局對(duì)象包括實(shí)例化處理依賴關(guān)系。為了解決這樣的問(wèn)題,我們?cè)俅位氐饺肿?cè)表創(chuàng)建組件。參考文章程序員如何理解依賴注入容器補(bǔ)充很多代碼背后,都是某種哲學(xué)思想的體現(xiàn)。 思想 思想是解決問(wèn)題的根本思想必須轉(zhuǎn)換成習(xí)慣構(gòu)建一套完整的思想體系是開(kāi)發(fā)能力成熟的標(biāo)志——《簡(jiǎn)單之美》(前言) . 成功的軟件項(xiàng)目就是那些提交產(chǎn)物達(dá)到或超出客戶的預(yù)期的項(xiàng)目,而且開(kāi)發(fā)過(guò)程符合時(shí)間和費(fèi)...
摘要:簡(jiǎn)單來(lái)說(shuō),是一個(gè)輕量級(jí)的控制反轉(zhuǎn)和面向切面的容器框架。變成的支持提供面向切面編程,可以方便的實(shí)現(xiàn)對(duì)程序進(jìn)行權(quán)限攔截,運(yùn)行監(jiān)控等功能。用于反射創(chuàng)建對(duì)象,默認(rèn)情況下調(diào)用無(wú)參構(gòu)造函數(shù)。指定對(duì)象的作用范圍。 1.Spring介紹 1.1 Spring概述 Spring是一個(gè)開(kāi)源框架,Spring是于2003 年興起的一個(gè)輕量級(jí)的Java 開(kāi)發(fā)框架,由Rod Johnson 在其著作Expert...
摘要:維基百科該原則規(guī)定高層次的模塊不應(yīng)該依賴與低層次的模塊,兩者都應(yīng)該依賴于抽象接口。依賴反轉(zhuǎn)原則則顛倒這種依賴關(guān)系,并以上面提到的兩個(gè)規(guī)定作為指導(dǎo)思想。維基百科這些話的意思就是將依賴對(duì)象的創(chuàng)建和綁定轉(zhuǎn)移到被依賴對(duì)象類的外部來(lái)實(shí)現(xiàn)。 在這個(gè)標(biāo)題中,除了 JS 是亂入之外,其它的幾個(gè)詞匯都是存在一個(gè)共同點(diǎn)的,那就是依賴。 那么,依賴是什么呢? 比如,現(xiàn)在我正在寫這篇博客文,但是我得在電腦上編...
閱讀 2234·2019-08-30 15:53
閱讀 2463·2019-08-30 12:54
閱讀 1208·2019-08-29 16:09
閱讀 734·2019-08-29 12:14
閱讀 761·2019-08-26 10:33
閱讀 2486·2019-08-23 18:36
閱讀 2962·2019-08-23 18:30
閱讀 2125·2019-08-22 17:09