1. 程式人生 > 其它 >PHP 實現簡易 IOC 容器

PHP 實現簡易 IOC 容器

前言
好的設計會提高程式的可複用性和可維護性,也間接的提高了開發人員的生產力。今天,我們就來說一下在很多框架中都使用的依賴注入。

概念
要搞清楚什麼是依賴注入如何依賴注入,首先我們要明確一些概念。

IOC (Inversion of Control) 控制反轉:
遵循依賴倒置原則的一種程式碼設計方案,依賴的建立 (控制) 由主動變為被動 (反轉)。

DI (Dependency Injection) 依賴注入:
控制反轉的一種具體實現方法。通過引數的方式從外部傳入依賴,將依賴的建立由主動變為被動 (實現了控制反轉)。

實現程式碼
首先看依賴沒有倒置時的一段程式碼


class Controller
{
   
protected $service; public function __construct() { // 主動建立依賴(內部直接例項化) $this->service = new Service(12, 13); } } class Service { protected $model; protected $count; public function __construct($param1, $param2) { $this->count = $param1 + $param2
; // 主動建立依賴(內部直接例項化) $this->model = new Model('table'); } } class Model { protected $table; public function __construct($table) { $this->table = $table; } } $controller = new Controller; 依賴關係是 Controller 依賴 Service,Service 依賴 Model。從控制的角度來看,Controller 主動建立依賴 Service,Service 主動建立依賴 Model。依賴是由需求方內部產生的,需求方需要關心依賴的具體實現。這樣的設計使程式碼耦合性變高,每次底層發生改變(如引數變動),頂層就必須修改程式碼。 接下來使用依賴注入實現控制反轉,使依賴關係倒置:
class Controller { protected $service; // 依賴被動傳入。申明要 Service 類的例項 (抽象介面) public function __construct(Service $service) { $this->service = $service; } } class Service { protected $model; protected $count; // 依賴被動傳入(以引數的形式傳入) public function __construct(Model $model, $param1, $param2) { $this->count = $param1 + $param2; $this->model = $model; } } class Model { protected $table; public function __construct($table) { $this->table = $table; } } $model = new Model('table'); $service = new Service($model, 12, 13); $controller = new Controller($service); 將依賴通過引數的方式從外部傳入(即依賴注入),控制的角度上依賴的產生從主動建立變為被動注入,依賴關係變為了依賴於抽象介面而不依賴於具體實現。此時的程式碼得到了解耦,提高了可維護性。 如何依賴注入,自動注入依賴 有了上面的一些理論基礎,我們大致瞭解了依賴注入是什麼,能幹什麼。 不過雖然上面的程式碼可以進行依賴注入了,但是依賴還是需要手動建立。我們可不可以建立一個工廠類,用來幫我們進行自動依賴注入呢?OK,我們需要一個 IOC 容器。 實現一個簡單的 IOC 容器 依賴注入是以建構函式引數的形式傳入的,想要自動注入: 我們需要知道需求方需要哪些依賴,使用反射來獲得 只有類的例項會被注入,其它引數不受影響 如何自動進行注入呢?當然是 PHP 自帶的反射功能! 注:關於反射是否影響效能,答案是肯定的。但是相比資料庫連線、網路請求的時延,反射帶來的效能問題在絕大多數情況下並不會成為應用的效能瓶頸。 1.雛形 首先建立 Container 類實現getInstance 方法: class Container { public static function getInstance($class_name, $params = []) { // 獲取反射例項 $reflector = new \ReflectionClass($class_name); // 獲取反射例項的構造方法 $constructor = $reflector->getConstructor(); $di_params = []; if ($constructor) { // 獲取反射例項構造方法的形參 /** var_dump($constructor->getParameters()); array (size=2) 0 => object(ReflectionParameter)[3] public 'name' => string 'a' (length=1) 1 => object(ReflectionParameter)[4] public 'name' => string 'count' (length=5) */ foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果引數是一個類,建立例項 $di_params[] = new $class->name; } } } $di_params = array_merge($di_params, $params); // 建立例項 return $reflector->newInstanceArgs($di_params); } } // 有了 getInstance 方法,我們可以試一下自動注入依賴 class A { public $count = 100; } class B { protected $count = 1; public function __construct(A $a, $count) { $this->count = $a->count + $count; } public function getCount() { return $this->count; } } $b = Container::getInstance(B::class, [10]); var_dump($b->getCount()); // result is 110 2.進階 雖然上面的程式碼可以進行自動依賴注入了,但是問題是隻能構注入一層。如果 A 類也有依賴怎麼辦呢? 我們需要修改一下程式碼: class Container { public static function getInstance($class_name, $params = []) { // 獲取反射例項 $reflector = new ReflectionClass($class_name); // 獲取反射例項的構造方法 $constructor = $reflector->getConstructor(); // 獲取反射例項構造方法的形參 $di_params = []; if ($constructor) { foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果引數是一個類,建立例項,並對例項進行依賴注入 $di_params[] = self::getInstance($class->name); // 遞迴建立例項 } } } $di_params = array_merge($di_params, $params); // 建立例項 return $reflector->newInstanceArgs($di_params); } } // 新增 C類 class C { public $count = 20; } class A { public $count = 100; public function __construct(C $c) { $this->count += $c->count; } } class B { protected $count = 1; public function __construct(A $a, $count) { $this->count = $a->count + $count; } public function getCount() { return $this->count; } } $b = Container::getInstance(B::class, [10]); var_dump($b->getCount()); // result is 130 上述程式碼使用遞迴完成了多層依賴的注入關係,程式中依賴關係層級一般不會特別深,遞迴不會造成記憶體遺漏問題。 3.單例 有些類會貫穿在程式生命週期中被頻繁使用,為了在依賴注入中避免不停的產生新的例項,我們需要 IOC 容器支援單例模式,已經是單例的依賴可以直接獲取,節省資源。 為 Container 增加單例相關方法: class Container { protected static $_singleton = []; // 新增一個例項到單例 public static function singleton($instance) { if ( ! is_object($instance)) { throw new InvalidArgumentException("Object need!"); } $class_name = get_class($instance); // singleton not exist, create if ( ! array_key_exists($class_name, self::$_singleton)) { self::$_singleton[$class_name] = $instance; } } // 獲取一個單例例項 public static function getSingleton($class_name) { return array_key_exists($class_name, self::$_singleton) ? self::$_singleton[$class_name] : NULL; } // 銷燬一個單例例項 public static function unsetSingleton($class_name) { self::$_singleton[$class_name] = NULL; } // 改造 getInstance 方法 public static function getInstance($class_name, $params = []) { // 獲取反射例項 $reflector = new ReflectionClass($class_name); // 獲取反射例項的構造方法 $constructor = $reflector->getConstructor(); $di_params = []; if ($constructor) { // 獲取反射例項構造方法的形參 foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果依賴是單例,則直接獲取,反之建立例項 $singleton = self::getSingleton($class->name); $di_params[] = $singleton ? $singleton : self::getInstance($class->name); } } } $di_params = array_merge($di_params, $params); // 建立例項 return $reflector->newInstanceArgs($di_params); } } 4.以依賴注入的方式執行方法 類之間的依賴注入解決了,我們還需要一個以依賴注入的方式執行方法的功能,可以注入任意方法的依賴。這個功能在實現路由分發到控制器方法時很有用。 class Container { protected static $_singleton = []; // 新增一個例項到單例 public static function singleton($instance) { if ( ! is_object($instance)) { throw new InvalidArgumentException("Object need!"); } $class_name = get_class($instance); // singleton not exist, create if ( ! array_key_exists($class_name, self::$_singleton)) { self::$_singleton[$class_name] = $instance; } } // 獲取一個單例例項 public static function getSingleton($class_name) { return array_key_exists($class_name, self::$_singleton) ? self::$_singleton[$class_name] : NULL; } // 銷燬一個單例例項 public static function unsetSingleton($class_name) { self::$_singleton[$class_name] = NULL; } // 改造 getInstance 方法 public static function getInstance($class_name, $params = []) { // 獲取反射例項 $reflector = new ReflectionClass($class_name); // 獲取反射例項的構造方法 $constructor = $reflector->getConstructor(); $di_params = []; if ($constructor) { // 獲取反射例項構造方法的形參 foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果依賴是單例,則直接獲取,反之建立例項 $singleton = self::getSingleton($class->name); $di_params[] = $singleton ? $singleton : self::getInstance($class->name); } } } $di_params = array_merge($di_params, $params); // 建立例項 return $reflector->newInstanceArgs($di_params); } //增加 run 方法 public static function run($class_name, $method, $params = [], $construct_params = []) { if ( ! class_exists($class_name)) { throw new BadMethodCallException("Class $class_name is not found!"); } if ( ! method_exists($class_name, $method)) { throw new BadMethodCallException("undefined method $method in $class_name !"); } // 獲取外層例項 new $class_name $instance = self::getInstance($class_name, $construct_params); //以下是為了獲取 $method 方法的引數 // 通過反射例項,獲取 $class_name 類的相關方法和屬性等 $reflector = new \ReflectionClass($class_name); // 獲取方法 $reflectorMethod = $reflector->getMethod($method); $di_params = []; // 查詢方法的形參 $method foreach ($reflectorMethod->getParameters() as $param) { $class = $param->getClass(); // 如果類,則例項 if ($class) { $singleton = self::getSingleton($class->name); $di_params[] = $singleton ? $singleton : self::getInstance($class->name); } } // 執行方法 return call_user_func_array([$instance, $method], array_merge($di_params, $params)); } } class A { public $count = 10; } class B { public function getCount(A $a, $count) { return $a->count + $count; } } $result = Container::run(B::class, 'getCount', [10]); var_dump($result); // result is 20 轉載地址:https://www.jianshu.com/p/d7ea1204d74d
QQ:1542385235 (PHP、Java、安卓蘋果app製作修改、頁面切圖、各類模板修改、仿站,資料庫修復、WAP製作修改 。我們團隊是專門做網站開發的,都是有3年以上工作經驗。需要後臺系統開發,網頁頁面製作,app製作,ui設計的請加我qq聯絡。非誠勿擾!!) 本人qq群也有許多的技術文件,希望可以為你提供一些幫助(非技術的勿加!)。 QQ群: 281442983 (點選連結加入群:http://jq.qq.com/?_wv=1027&k=29LoD19)