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