1. 前言
2. 项目代码结构
3. 容器完整代码
3.4.1 解决类构造函数依赖
3.4.2 解决 callable 的参数依赖
3.1 容器主要提供方法
3.2 符合psr-11标准
3.3 容器的基本存储
3.4 自动依赖解决
4. 未完..不一定续
1. 前言在看了一些容器实现代码后, 就手痒想要自己实现一个, 因此也就有了本文接下来的内容.
首先, 实现的容器需要具有以下几点特性:
符合psr-11标准
实现基本的容器存储功能
具有自动依赖解决能力
本项目代码由github托管
可使用composer进行安装 composer require yjx/easy-di
2. 项目代码结构|-src |-exception |-instantiateexception.php (实现psr\container\containerexceptioninterface) |-invalidargumentexception.php (实现psr\container\containerexceptioninterface) |-unknownidentifierexception.php (实现psr\container\notfoundexceptioninterface) |-container.php # 容器|-tests |-unittest |-containertest.php
3. 容器完整代码代码版本 v1.0.1
<?php namespace easydi; use easydi\exception\unknownidentifierexception; use easydi\exception\invalidargumentexception; use easydi\exception\instantiateexception; use psr\container\containerexceptioninterface; use psr\container\containerinterface;use psr\container\notfoundexceptioninterface; class container implements containerinterface{ /** * 保存 参数, 已实例化的对象 * @var array */ private $instance = []; private $shared = []; private $raw = []; private $params = []; /** * 保存 定义的 工厂等 * @var array */ private $binding = []; public function __construct() { $this->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: "\\xxx\\xxx"=>"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 = [])
从容器中获取
params可优先参与到条目实例化过程中的依赖注入
call(callable $function, array $params=[])
利用容器来调用callable, 由容器自动注入依赖.
unset(string $id)
从容器中移除$id对应条目
3.2 符合psr-11标准easydi(本容器)实现了 psr\container\containerinterface 接口, 提供 has($id) 和 get($id, $params=[]) 两个方法用于判断及获取条目.
对于无法解析的条目识别符, 则会抛出异常(实现了 notfoundexceptioninterface 接口).
3.3 容器的基本存储容器可用于保存 不被解析的条目, 及自动解析的条目.
不被解析的条目
主要用于保存 配置参数, 已实例化对象, 不被解析的闭包 等
自动解析的条目
在 get(...) 时会被容器自动解析, 若是 闭包 则会自动调用, 若是 类名 则会实例化, 若是 别名 则会解析其对应的条目.
3.4 自动依赖解决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(...) 方法.
3.4.2 解决 callable 的参数依赖使用 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 easydi\container();// 输出: 'test'echo $c->call('testfunc')."\n"; // 输出: '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 上维护, 该博文后续视评论需求来决定是否补充.
相关推荐:
几行代码轻松实现php文件打包下载zip
predis如何实现phpredis的pconnect方法
以上就是实现php的自动依赖注入容器 easydi容器的详细内容。