摘要:年月日前言在看了一些容器實現代碼后就手癢想要自己實現一個因此也就有了本文接下來的內容首先實現的容器需要具有以下幾點特性符合標準實現基本的容器存儲功能具有自動依賴解決能力本項目代碼由托管可使用進行安裝項目代碼結構實現實現實現
[TOC]
Last-Modified: 2019年5月10日16:15:36
1. 前言在看了一些容器實現代碼后, 就手癢想要自己實現一個, 因此也就有了本文接下來的內容.
首先, 實現的容器需要具有以下幾點特性:
符合PSR-11標準
實現基本的容器存儲功能
具有自動依賴解決能力
本項目代碼由GitHub托管
可使用Composer進行安裝 composer require yjx/easy-di
2. 項目代碼結構|-src |-Exception |-InstantiateException.php (實現PsrContainerContainerExceptionInterface) |-InvalidArgumentException.php (實現PsrContainerContainerExceptionInterface) |-UnknownIdentifierException.php (實現PsrContainerNotFoundExceptionInterface) |-Container.php # 容器 |-tests |-UnitTest |-ContainerTest.php3. 容器完整代碼
代碼版本 v1.0.1
raw(ContainerInterface::class, $this); $this->raw(self::class, $this); } /** * Finds an entry of the container by its identifier and returns it. * * @param string $id Identifier of the entry to look for. * * @throws NotFoundExceptionInterface No entry was found for **this** identifier. * @throws ContainerExceptionInterface Error while retrieving the entry. * * @return mixed Entry. */ public function get($id, $parameters = [], $shared=false) { if (!$this->has($id)) { throw new UnknownIdentifierException($id); } if (array_key_exists($id, $this->raw)) { return $this->raw[$id]; } if (array_key_exists($id, $this->instance)) { return $this->instance[$id]; } $define = array_key_exists($id, $this->binding) ? $this->binding[$id] : $id; if ($define instanceof Closure) { $instance = $this->call($define, $parameters); } else { // string $class = $define; $params = (empty($this->params[$id]) ? [] : $this->params[$id]) + $parameters; // Case: "xxxxxx"=>"abc" if ($id !== $class && $this->has($class)) { $instance = $this->get($class, $params); } else { $dependencies = $this->getClassDependencies($class, $params); if (is_null($dependencies) || empty($dependencies)) { $instance = $this->getReflectionClass($class)->newInstanceWithoutConstructor(); } else { $instance = $this->getReflectionClass($class)->newInstanceArgs($dependencies); } } } if ($shared || (isset($this->shared[$id]) && $this->shared[$id])) { $this->instance[$id] = $instance; } return $instance; } /** * @param callback $function * @param array $parameters * @return mixed * @throws InvalidArgumentException 傳入錯誤的參數 * @throws InstantiateException */ public function call($function, $parameters=[], $shared=false) { //參考 http://php.net/manual/zh/function.call-user-func-array.php#121292 實現解析$function $class = null; $method = null; $object = null; // Case1: function() {} if ($function instanceof Closure) { $method = $function; } elseif (is_array($function) && count($function)==2) { // Case2: [$object, $methodName] if (is_object($function[0])) { $object = $function[0]; $class = get_class($object); } elseif (is_string($function[0])) { // Case3: [$className, $staticMethodName] $class = $function[0]; } if (is_string($function[1])) { $method = $function[1]; } } elseif (is_string($function) && strpos($function, "::") !== false) { // Case4: "class::staticMethod" list($class, $method) = explode("::", $function); } elseif (is_scalar($function)) { // Case5: "functionName" $method = $function; } else { throw new InvalidArgumentException("Case not allowed! Invalid Data supplied!"); } try { if (!is_null($class) && !is_null($method)) { $reflectionFunc = $this->getReflectionMethod($class, $method); } elseif (!is_null($method)) { $reflectionFunc = $this->getReflectionFunction($method); } else { throw new InvalidArgumentException("class:$class method:$method"); } } catch (ReflectionException $e) { // var_dump($e->getTraceAsString()); throw new InvalidArgumentException("class:$class method:$method", 0, $e); } $parameters = $this->getFuncDependencies($reflectionFunc, $parameters); if ($reflectionFunc instanceof ReflectionFunction) { return $reflectionFunc->invokeArgs($parameters); } elseif ($reflectionFunc->isStatic()) { return $reflectionFunc->invokeArgs(null, $parameters); } elseif (!empty($object)) { return $reflectionFunc->invokeArgs($object, $parameters); } elseif (!is_null($class) && $this->has($class)) { $object = $this->get($class, [], $shared); return $reflectionFunc->invokeArgs($object, $parameters); } throw new InvalidArgumentException("class:$class method:$method, unable to invoke."); } /** * @param $class * @param array $parameters * @throws ReflectionException */ protected function getClassDependencies($class, $parameters=[]) { // 獲取類的反射類 $reflectionClass = $this->getReflectionClass($class); if (!$reflectionClass->isInstantiable()) { throw new InstantiateException($class); } // 獲取構造函數反射類 $reflectionMethod = $reflectionClass->getConstructor(); if (is_null($reflectionMethod)) { return null; } return $this->getFuncDependencies($reflectionMethod, $parameters, $class); } protected function getFuncDependencies(ReflectionFunctionAbstract $reflectionFunc, $parameters=[], $class="") { $params = []; // 獲取構造函數參數的反射類 $reflectionParameterArr = $reflectionFunc->getParameters(); foreach ($reflectionParameterArr as $reflectionParameter) { $paramName = $reflectionParameter->getName(); $paramPos = $reflectionParameter->getPosition(); $paramClass = $reflectionParameter->getClass(); $context = ["pos"=>$paramPos, "name"=>$paramName, "class"=>$paramClass, "from_class"=>$class]; // 優先考慮 $parameters if (isset($parameters[$paramName]) || isset($parameters[$paramPos])) { $tmpParam = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$paramPos]; if (gettype($tmpParam) == "object" && !is_a($tmpParam, $paramClass->getName())) { throw new InstantiateException($class."::".$reflectionFunc->getName(), $parameters + ["__context"=>$context, "tmpParam"=>get_class($tmpParam)]); } $params[] = $tmpParam; // $params[] = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$pos]; } elseif (empty($paramClass)) { // 若參數不是class類型 // 優先使用默認值, 只能用于判斷用戶定義的函數/方法, 對系統定義的函數/方法無效, 也同樣無法獲取默認值 if ($reflectionParameter->isDefaultValueAvailable()) { $params[] = $reflectionParameter->getDefaultValue(); } elseif ($reflectionFunc->isUserDefined()) { throw new InstantiateException("UserDefined. ".$class."::".$reflectionFunc->getName()); } elseif ($reflectionParameter->isOptional()) { break; } else { throw new InstantiateException("SystemDefined. ".$class."::".$reflectionFunc->getName()); } } else { // 參數是類類型, 優先考慮解析 if ($this->has($paramClass->getName())) { $params[] = $this->get($paramClass->getName()); } elseif ($reflectionParameter->allowsNull()) { $params[] = null; } else { throw new InstantiateException($class."::".$reflectionFunc->getName()." {$paramClass->getName()} "); } } } return $params; } protected function getReflectionClass($class, $ignoreException=false) { static $cache = []; if (array_key_exists($class, $cache)) { return $cache[$class]; } try { $reflectionClass = new ReflectionClass($class); } catch (Exception $e) { if (!$ignoreException) { throw new InstantiateException($class, 0, $e); } $reflectionClass = null; } return $cache[$class] = $reflectionClass; } protected function getReflectionMethod($class, $name) { static $cache = []; if (is_object($class)) { $class = get_class($class); } if (array_key_exists($class, $cache) && array_key_exists($name, $cache[$class])) { return $cache[$class][$name]; } $reflectionFunc = new ReflectionMethod($class, $name); return $cache[$class][$name] = $reflectionFunc; } protected function getReflectionFunction($name) { static $closureCache; static $cache = []; $isClosure = is_object($name) && $name instanceof Closure; $isString = is_string($name); if (!$isString && !$isClosure) { throw new InvalidArgumentException("$name can"t get reflection func."); } if ($isString && array_key_exists($name, $cache)) { return $cache[$name]; } if ($isClosure) { if (is_null($closureCache)) { $closureCache = new SplObjectStorage(); } if ($closureCache->contains($name)) { return $closureCache[$name]; } } $reflectionFunc = new ReflectionFunction($name); if ($isString) { $cache[$name] = $reflectionFunc; } if ($isClosure) { $closureCache->attach($name, $reflectionFunc); } return $reflectionFunc; } /** * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * * `has($id)` returning true does not mean that `get($id)` will not throw an exception. * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`. * * @param string $id Identifier of the entry to look for. * * @return bool */ public function has($id) { $has = array_key_exists($id, $this->binding) || array_key_exists($id, $this->raw) || array_key_exists($id, $this->instance); if (!$has) { $reflectionClass = $this->getReflectionClass($id, true); if (!empty($reflectionClass)) { $has = true; } } return $has; } public function needResolve($id) { return !(array_key_exists($id, $this->raw) && (array_key_exists($id, $this->instance) && $this->shared[$id])); } public function keys() { return array_unique(array_merge(array_keys($this->raw), array_keys($this->binding), array_keys($this->instance))); } public function instanceKeys() { return array_unique(array_keys($this->instance)); } public function unset($id) { unset($this->shared[$id], $this->binding[$id], $this->raw[$id], $this->instance[$id], $this->params[$id]); } public function singleton($id, $value, $params=[]) { $this->set($id, $value, $params, true); } /** * 想好定義數組, 和定義普通項 * @param $id * @param $value * @param bool $shared */ public function set($id, $value, $params=[], $shared=false) { if (is_object($value) && !($value instanceof Closure)) { $this->raw($id, $value); return; } elseif ($value instanceof Closure) { // no content } elseif (is_array($value)) { $value = [ "class" => $id, "params" => [], "shared" => $shared ] + $value; if (!isset($value["class"])) { $value["class"] = $id; } $params = $value["params"] + $params; $shared = $value["shared"]; $value = $value["class"]; } elseif (is_string($value)) { // no content } $this->binding[$id] = $value; $this->shared[$id] = $shared; $this->params[$id] = $params; } public function raw($id, $value) { $this->unset($id); $this->raw[$id] = $value; } public function batchRaw(array $data) { foreach ($data as $key=>$value) { $this->raw($key, $value); } } public function batchSet(array $data, $shared=false) { foreach ($data as $key=>$value) { $this->set($key, $value, $shared); } } }3.1 容器主要提供方法
容器提供方法:
raw(string $id, mixed $value)
適用于保存參數, $value可以是任何類型, 容器不會對其進行解析.
set(string $id, Closure|array|string $value, array $params=[], bool $shared=false)
定義服務
singleton(string $id, Closure|array|string $value, array $params=[])
等同調用set($id, $value, $params, true)
has(string $id)
判斷容器是否包含$id對應條目
get(string $id, array $params = [])
從容器中獲取$id對應條目, 可選參數$params可優先參與到條目實例化過程中的依賴注入
call(callable $function, array $params=[])
利用容器來調用callable, 由容器自動注入依賴.
unset(string $id)
從容器中移除$id對應條目
3.2 符合PSR-11標準EasyDI(本容器)實現了 PsrContainerContainerInterface 接口, 提供 has($id) 和 get($id, $params=[]) 兩個方法用于判斷及獲取條目.
對于無法解析的條目識別符, 則會拋出異常(實現了 NotFoundExceptionInterface 接口).
3.3 容器的基本存儲容器可用于保存 不被解析的條目, 及自動解析的條目.
不被解析的條目
主要用于保存 配置參數, 已實例化對象, 不被解析的閉包 等
自動解析的條目
在 get(...) 時會被容器自動解析, 若是 閉包 則會自動調用, 若是 類名 則會實例化, 若是 別名 則會解析其對應的條目.
EasyDI 在調用 閉包 及 實例化 類 已經 調用函數/方法(call()) 時能夠自動注入所需的依賴, 其中實現的原理是使用了PHP自帶的反射API.
此處主要用到的反射API如下:
ReflectionClass
ReflectionFunction
ReflectionMethod
ReflectionParameter
3.4.1 解決類構造函數依賴解析的一般步驟:
獲取類的反射類 $reflectionClass = new ReflectionClass($className)
判斷能夠實例化 $reflectionClass->isInstantiable()
若能實例化, 則獲取對應的構造函數的反射方法類 $reflectionMethod = $reflectionClass->getConstructor()
3.1. 若返回null, 則表示無構造函數可直接跳到*步驟6* 3.2 若返回ReflectionMethod實例, 則開始解析其參數
獲取構造函數所需的所有依賴參數類 $reflectionParameters = $reflectionMethod->getParameters
逐個解析依賴參數 $reflectionParameter
5.1 獲取參數對應名及位置 `$reflectionParameter->getName()`, `$reflectionParameter->getClass()` 5.2 獲取參數對應類型 `$paramClass = $reflectionParameter->getClass()` 5.2.1 若本次解析手動注入了依賴參數, 則根據參數位置及參數名直接使用傳入的依賴參數 Eg. `$container->get($xx, [1=>123, "e"=>new Exception()])` 5.2.2 若參數是標量類型, 若參數有默認值(`$reflectionParameter->isDefaultValueAvailable()`)則使用默認值, 否則拋出異常(無法處理該依賴) 5.2.3 若參數是 *class* 類型, 若容器可解析該類型, 則由容器自動實例化 `$this->get($paramClass->getName())`, 若無法解析但該參數允許null, 則傳入null值, 否則拋出異常(無法處理來依賴)
若依賴參數為空則調用 $reflectionClass->newInstanceWithoutConstructor(), 否則調用 $reflectionClass->newInstanceArgs($dependencies); //$dependencies為步驟5中構造的依賴參數數組
具體完整代碼請參照容器類的 getClassDependencies(...) 方法.
使用 call(...) 來調用 可調用 時, 自動解決依賴同樣類似上述過程, 只是需要區分是 類函數, 類靜態方法 還是 普通方法, 并相應的使用不同的反射類來解析,
具體完整代碼請參照容器類的 call(...) 方法
class UserManager { private $mailer; public function __construct(Mailer $mailer) { $this->mailer = $mailer; } public function register($email, $password) { // The user just registered, we create his account // ... // We send him an email to say hello! $this->mailer->mail($email, "Hello and welcome!"); } public function quickSend(Mailer $mailer, $email, $password) { $mailer->mail($email, "Hello and welcome!"); } } function testFunc(UserManager $manager) { return "test"; } // 實例化容器 $c = new EasyDIContainer(); // 輸出: "test" echo $c->call("testFunc")." "; // 輸出: "test" echo $c->call(function (UserManager $tmp) { return "test"; }); // 自動實例化UserManager對象 [$className, $methodName] $c->call([UserManager::class, "register"], ["password"=>123, "email"=>"1@1.1"]); // 自動實例化UserManager對象 $methodFullName $c->call(UserManager::class."::"."register", ["password"=>123, "email"=>"1@1.1"]); // 調用類的靜態方法 [$className, $staticMethodName] $c->call([UserManager::class, "quickSend"], ["password"=>123, "email"=>"1@1.1"]); // 使用字符串調用類的靜態方法 $staticMethodFullName $c->call(UserManager::class."::"."quickSend", ["password"=>123, "email"=>"1@1.1"]); // [$obj, $methodName] $c->call([new UserManager(new Mailer()), "register"], ["password"=>123, "email"=>"1@1.1"]); // [$obj, $staticMethodName] $c->call([new UserManager(new Mailer()), "quickSend"], ["password"=>123, "email"=>"1@1.1"]);4. 未完..不一定續
暫時寫到此處.
后續項目最新代碼直接在 GitHub 上維護, 該博文后續視評論需求來決定是否補充.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31438.html
摘要:標量參數關聯傳值依賴是自動解析注入的,剩余的標量參數則可以通過關聯傳值,這樣比較靈活,沒必要把默認值的參數放在函數參數最尾部。 更新:github(給個小星星呀) -- 2018-4-11:優化服務綁定方法 ::bind 的類型檢查模式 借助 PHP 反射機制實現的一套 依賴自動解析注入 的 IOC/DI 容器,可以作為 Web MVC 框架 的應用容器 1、依賴的自動注入:你只需要...
摘要:現在我們就可以在構造函數或者任何其他通過服務容器注入依賴項的地方使用類型提示注入接口創建一個新的類實例,此處將注入的實例。自動解析構造函數所需的依賴的服務容器實現了接口。 簡單的服務容器 一個簡單的 php 5.3 依賴注入容器。 項目地址:https://github.com/godruoyi/easy-container Why 目前比較流行的 PHP 容器: Pimple La...
摘要:在構造函數中注入依賴性在中作為服務的控制器這是痛苦的,當你有個以上的依賴項,你的構造函數是行樣板代碼在屬性中注入依賴性這是我們建議的解決方案。 PHP-DI是用PHP編寫的、強大的和實用的、框架無關的依賴注入容器。這是一個關于如何使用PHP-DI和依賴注入的最佳實踐指南。 文章來源于PHP-DI,作者:Matthieu Napoli和貢獻者。PHP-DI是用PHP編寫的、強大的和實用的...
摘要:工廠模式,依賴轉移當然,實現控制反轉的方法有幾種。其實我們稍微改造一下這個類,你就明白,工廠類的真正意義和價值了。雖然如此,工廠模式依舊十分優秀,并且適用于絕大多數情況。 此篇文章轉載自laravel-china,chongyi的文章https://laravel-china.org/top...原文地址: http://www.insp.top/learn-lar... ,轉載務必保...
摘要:而依賴倒置原則的思想是,上層不應該依賴下層,應依賴接口。上面通過構造函數注入對象的方式,就是最簡單的依賴注入當然注入不僅可以通過構造函數注入,也可以通過屬性注入,上面你可以通過一個來動態為這個屬性賦值。 依賴倒置和控制反轉是一種編程思想,而依賴注入就是通過服務容器實現這種面向接口或者是面向抽象編程的思想 概念理解 依賴倒置原則 依賴倒置是一種軟件設計思想,在傳統軟件中,上層代碼依賴于下...
閱讀 3650·2021-09-22 15:15
閱讀 3555·2021-08-12 13:24
閱讀 1309·2019-08-30 15:53
閱讀 1816·2019-08-30 15:43
閱讀 1179·2019-08-29 17:04
閱讀 2792·2019-08-29 15:08
閱讀 1573·2019-08-29 13:13
閱讀 3084·2019-08-29 11:06