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