依赖注入模块

从零开始编写一个PHP框架 系列的《依赖注入模块》
项目地址:terse
前言 关于依赖注入,相信小伙伴们都知道它的作用,因为我们要实现控制反转,使代码松耦合,易维护。
stackoverflow 上有个问题:如何向一个五岁小孩解释依赖注入,里面最高分的回答,很有道理。
When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn't want you to have. You might even be looking for something we don't even have or which has expired.
What you should be doing is stating a need, "I need something to drink with lunch," and then we will make sure you have something when you sit down to eat.
当你自己从冰箱里取出东西时,你可能会引发问题。 你可能会把门打开,你可能会得到妈妈或爸爸不希望你拥有的东西。 您甚至可能正在寻找我们甚至没有或已经过期的东西。
你应该做的是说明需要,“我需要在午餐时喝点东西”,然后当你坐下来吃饭时我们会确保你有东西。
简而言之,就是我们创造了一个叮当猫,一旦我们想要得到某样东西或者使用某个功能时,只需要向叮当猫索取就行。
需求分析
  • 需要创建一个类(叮当猫),去管理我们需要注入的类。
  • 需要有注册功能
    注册动作(创建一个又一个服务)
    服务管理
  • 需要有获取功能
    获取已经初始化的类(配置类等)
    获取新初始化的类(模型类等)
创建一个依赖注入类 这里使用了 final,因为我们确定这个类是不会被继承和重写的。

注册部分 关于注册一个服务,我们会有两个场景。
第一个场景,只是单纯的注册一个服务,每次使用都是注册时的状态。
第二个场景,注册的服务进行了一系列初始化,并且在使用时的改变需要继续向下流转,比如 配置类
针对上述两种情况,我们用两个方法去区分他们。

注册的动作有了,可是,服务暂时没地方存储,所以我们需要一个 Service 类。

根据注册时的需求,仔细分析一下需要定义的属性,如下:
/** * 服务别名 * * @var string */ protected $_name; /** * 服务定义 * * @var mixed */ protected $_definition; /** * 是否是静态的共享服务 * * @var bool */ protected $_shared; /** * 实例化(可以理解为单例模式) * * @var mixed */ protected $_instance;

现在,完善一下这个服务类:
_name = (string)$name; $this->_definition = $definition; $this->_shared = (bool)$shared; } }

再次完善依赖注入类:
_services[$name] = new Service($name, $definition, false); }/** * 设置静态服务, 该服务获取时只实例化一次 * * @param string $name * @param mixed$definition */ public function setShared($name, $definition) { $this->_services[$name] = new Service($name, $definition, true); } }

到这里,依赖注入管理器的注册部分就OK了,下面继续写使用部分。
使用部分 跟注册部分类似,使用的时候,也需要根据不同情况获取不同的服务。
比如,在注册时,注册的是一个普通服务(非静态),而在获取时有需求,这时就需要进行区分。
为了给外部判断是否存在某一个服务,这里提供了 has 方法。
_services[$name]); }/** * 获取服务 * * @paramstring $name * @paramarray$params * @return mixed */ public function get($name, array $params = []) { if (!$this->has($name)) { return null; } $service = $this->_services[$name]; return $service->resolve($params, $this); }/** * 获取静态服务 * * @paramstring $name * @paramarray$params * @return mixed */ public function getShared($name, array $params = []) { if (!$this->has($name)) { return null; } if (isset($this->_instances[$name])) { return $this->_instances[$name]; } $instance = $this->get($name, $params); $this->_instances[$name] = $instance; return $instance; } }

在这里,我们看到了在获取时,用到了 Serviceresolve 方法。在这里我们需要针对多种情况进行分析。
第一种,字符串类型:
在注册时,我们可能会通过如下的方式来注册某个服务。
$di->set('toolString', 'I am a tool.'); $di->set('toolClass', 'ToolClass'); $di->set('toolFoo', 'ToolFoo');

如上所述,当第二个参数的类型是 string 时,可能就是要输出一个字符串,也有可能代表的是一个类名,也有可能代表的是一个方法名。
所以在获取的时候,我们需要对其进行分析。
_shared && $this->_instance) { return $this->_instance; }$definition = $this->_definition; $type = 'string'; if (is_string($definition)) { $type = $this->stringParse(); } ... }/** * 解析字符串 * * @return string */ public function stringParse() { $definition = $this->_definition; if (class_exists($definition)) { return 'classString'; } if (function_exists($definition)) { return 'function'; } return 'string'; } }

分析完成后,针对不同的情况来进行相应的初始化和输出。
_shared && $this->_instance) { return $this->_instance; }$definition = $this->_definition; $type = 'string'; if (is_string($definition)) { $type = $this->stringParse(); }$instance = null; switch ($type) { case 'function': // 先绑定 $this 的作用域 $definition = \Closure::bind($definition, $di); // 调用函数 $instance = call_user_func_array($definition, $params); break; case 'classString': // 利用反射,实例化类 $class = new \ReflectionClass($definition); $instance = $class->newInstanceArgs($params); break; case 'class': case 'string': default: $instance = $definition; break; }// 如果是静态的,则需要保存 if ($this->_shared) { $this->_instance = $instance; }return $instance; }/** * 解析字符串 * * @return string */ public function stringParse() { $definition = $this->_definition; if (class_exists($definition)) { return 'classString'; } if (function_exists($definition)) { return 'function'; } return 'string'; } }

关于反射和绑定 $this 作用域,大家可以自行查阅相关资料,这里就不多做阐述。
第二种,函数类型。
$di->set('foo', function () { echo $this->get('toolString'); });

这一类是属于比较好实现的一类,不过最好还是通过相关方法做一个辨别比较靠谱。
... if (is_object($definition)) { $type = $this->objectParse($params); } .../** * 解析对象 * * @return string */ protected function objectParse() { $definition = $this->_definition; if ($definition instanceof \Closure) { return 'function'; } return 'class'; }

可能有部分同学会遇到 $this 不存在 get 方法的报错。话说,还记得上面那个 \Closure::bind 不?因为我们需要改变函数内部 $this 的作用域后,才可以使用上述方法。
第三种,实例化后的类。
$config = new Config(); $di->set('config', $config);

同样的,处理方法同第二种。
三种情况都已经描述完毕。不过我还是要说一下我的建议,在可能的情况下,不要使用字符串的方式,因为不可控因素比较多。
最后,上一版完整的 Service 类。
*/ final class Service { /** * 服务别名 * * @var string */ protected $_name; /** * 服务定义 * * @var mixed */ protected $_definition; /** * 是否是静态的共享服务 * * @var bool */ protected $_shared; /** * 实例化(可以理解为单例模式) * * @var mixed */ protected $_instance; /** * 服务构造函数 * * @param string$name * @param mixed$definition * @param boolean $shared */ function __construct($name, $definition, $shared) { $this->_name = (string)$name; $this->_definition = $definition; $this->_shared = (bool)$shared; }/** * 设置是否只实例化一次 * * @param string $shared */ public function setShared($shared) { $this->_shared = (bool)$shared; }/** * 是否shared * * @return boolean */ public function isShared() { return $this->_shared; }/** * 服务解析 * * @paramarray$params * @param\Terse\Di\Injectable$di * @return mixed */ public function resolve(array $params, $di) { if ($this->_shared && $this->_instance) { return $this->_instance; }$definition = $this->_definition; $type = 'string'; if (is_object($definition)) { $type = $this->objectParse($params); } else if (is_string($definition)) { $type = $this->stringParse($params); }$instance = null; switch ($type) { case 'function': $definition = \Closure::bind($definition, $di); $instance = call_user_func_array($definition, $params); break; case 'classString': $class = new \ReflectionClass($definition); $instance = $class->newInstanceArgs($params); break; case 'class': case 'string': default: $instance = $definition; break; }if ($this->_shared) { $this->_instance = $instance; } return $instance; }/** * 解析字符串 * * @return string */ protected function stringParse() { $definition = $this->_definition; if (class_exists($definition)) { return 'classString'; } if (function_exists($definition)) { return 'function'; } return 'string'; }/** * 解析对象 * * @return string */ protected function objectParse() { $definition = $this->_definition; if ($definition instanceof \Closure) { return 'function'; } return 'class'; } }

完善 有了 注册使用 两个功能外,还需要一些其它功能,比如:初始化一些默认服务删除一个服务获取当前单例 等功能。
*/ final class Injectable { /** * 服务 * * @var array */ protected $_services = []; /** * 静态服务实例化 * * @var array */ protected $_instances = []; /** * 当前实例 * * @var Terse\Di\Injectable */ protected static $_default; function __construct() { $this->_services = []; if (!self::$_default) { self::$_default = $this; } }/** * 获取默认 * * @return Terse\Di\Injectable */ public static function getDefault() { return self::$_default; }/** * 设置服务 * * @param string $name * @param mixed $definition */ public function set($name, $definition) { $this->_services[$name] = new Service($name, $definition, false); }/** * 设置静态服务, 该服务获取时只实例化一次 * * @param string $name * @param mixed $definition */ public function setShared($name, $definition) { $this->_services[$name] = new Service($name, $definition, true); }/** * 是否存在服务 * * @paramstring$name * @return boolean */ public function has($name) { return isset($this->_services[$name]); }/** * 获取服务 * * @paramstring $name * @paramarray$params * @return mixed */ public function get($name, array $params = []) { if (!$this->has($name)) { return null; } $service = $this->_services[$name]; return $service->resolve($params, $this); }/** * 获取静态服务 * * @paramstring $name * @paramarray$params * @return mixed */ public function getShared($name, array $params = []) { if (!$this->has($name)) { return null; } if (isset($this->_instances[$name])) { return $this->_instances[$name]; } $instance = $this->get($name, $params); $this->_instances[$name] = $instance; return $instance; }/** * 移除服务 * * @paramstring $name */ public function remove($name) { if ($this->has($name)) { unset($this->_services[$name]); } if (isset($this->_instances[$name])) { unset($this->_instances[$name]); } } }

总结 【依赖注入模块】到这里,依赖注入管理器的模块已经完成。下一步计划,准备编写 Config 类。

    推荐阅读