热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PHPIOC/DI容器依赖自动注入/依赖单例注入/依赖契约注入/参数关联传值

更新:github--2018-4-11:优化服务绑定方法::bind的类型检查模式借助PHP反射机制实现的一套依赖自动解析注入的IOCDI容器

更新:github

-- 2018-4-11:优化服务绑定方法 ::bind 的类型检查模式

借助 PHP 反射机制实现的一套 依赖自动解析注入 的 IOC/DI 容器,可以作为 Web MVC 框架 的应用容器

1、依赖的自动注入:你只需要在需要的位置注入你需要的依赖即可,运行时容器会自动解析依赖(存在子依赖也可以自动解析)将对应的实例注入到你需要的位置。

2、依赖的单例注入:某些情况下我们需要保持依赖的全局单例特性,比如 Web 框架中的 Request 依赖,我们需要将整个请求响应周期中的所有注入 Request 依赖的位置同步为在路由阶段解析完请求体的 Request 实例,这样我们在任何位置都可以访问全局的请求体对象。

3、依赖的契约注入:比如我们依赖某 Storage,目前使用 FileStorage 来实现,后期发现性能瓶颈,要改用 RedisStorage 来实现,如果代码中大量使用 FileStorage 作为依赖注入,这时候就需要花费精力去改代码了。我们可以使用接口 Storage 作为契约,将具体的实现类 FileStorage / RedisStorage 通过容器的绑定机制关联到 Storage 上,依赖注入 Storage,后期切换存储引擎只需要修改绑定即可。

4、标量参数关联传值:依赖是自动解析注入的,剩余的标量参数则可以通过关联传值,这样比较灵活,没必要把默认值的参数放在函数参数最尾部。这点我还是蛮喜欢 python 的函数传值风格的。

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') 很舒服
}

但这也使得我的容器不支持位序传值,必须保证运行参数的键名与运行方法的参数名准确的关联映(有默认值的参数可以省略),我想着并没有什么不方便的地方吧,我不喜欢给 $bar 参数传递个 $foo 变量。

容器源码

/*----------------------------------------------------------------------------------------------------| @author big cat|----------------------------------------------------------------------------------------------------| IOC 容器| 1、自动解析依赖 自动的对依赖进行解析,实例化,注入| /------------------------------------------------------------------------------| | 比如你用 Redis 或 File 做引擎存储 Session,可以定义一个顶层契约接口 Storage| 2、契约注入---------| 将具体的实现类 RedisStorage or FileStorage 的实例绑定到此契约| | 依赖此契约进行注入 后期可以灵活的更换或者扩展新的存储引擎| \-------------------------------------------------------------------------------| 3、单例注入 可以将依赖绑定为单例,实现此依赖的同步| 4、关联参数传值 标量参数采用关联传值,可设定默认值| 备注:关联传参才舒服, ($foo = 'foo', $bar), 跳过 foo 直接给 bar 传值多舒服|-----------------------------------------------------------------------------------------------------| public static methods:| singleton // 单例服务绑定| bind // 服务绑定| run // 运行容器| private static methods:| getParam // 获取依赖参数| getInstance // 获取依赖实例|-----------------------------------------------------------------------------------------------------*/class IOCContainer
{/*** 注册到容器内的依赖--服务* 可以通过 singleton($alias, $instance) 绑定全局单例依赖* 可以通过 bind($alias, $class_name) 绑定顶层契约依赖* 容器解析依赖时会优先检查是否为注册的内部依赖 如不是则加载外部依赖类实例化后注入* @var array*/public static $dependencyServices = array();/*** 单例模式服务注册* 将具体的实例绑定到服务 整个生命周期中此服务的各处依赖注入都用此实例* @param [type] $service 绑定的服务别名* @param [type] $provider 服务提供者:具体的实例或可实例的类* @return [type] [description]*/public static function singleton($service, $provider){static::bind($service, $provider, true);}/*** 服务注册* 注册依赖服务到容器内 容器将优先使用此类服务 可以实现契约注入* 契约注入:A Interface 可以作为 B Class 和 C Class 的代理人(契约者)注入 B Class 或 C Class 的实例* 具体看你绑定的谁 可以灵活切换底层具体的实现代码* @param [type] $service [description]* @param [type] $provider [description]* @param boolean $singleton [description]* @return [type] [description]*/public static function bind($service, $provider, $singleton = false){// 单例绑定服务提供者必须为服务的实例 以便全局单例绑定if ($singleton && ! is_object($provider)) {throw new Exception("service provider must be an instance of $provider!", 4041);}// 若非单例则校验服务提供者是否存在if (! $singleton && ! class_exists($provider)) {throw new Exception("service provider not exists!", 4042);}// singleton 标识服务是否为单例模式// 单例场景则 provider 为具体的实例 否则为某提供者类static::$dependencyServices[$service] = ['provider' => $provider,'singleton' => $singleton,];}/*** 获取类实例* 通过反射获取构造参数* 返回对应的类实例* @param [type] $class_name [description]* @return [type] [description]*/private static function getInstance($class_name){//方法参数分为 params 和 default_values//如果一个开放构造类作为依赖注入传入它类,我们应该将此类注册为全局单例服务$params = static::getParams($class_name);return (new ReflectionClass($class_name))->newInstanceArgs($params['params']);}/*** 反射方法参数类型* 对象参数:构造对应的实例 同时检查是否为单例模式的实例* 标量参数:返回参数名 索引路由参数取值* 默认值参数:检查路由参数中是否存在本参数 无则取默认值* @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();//反射检测类是否显示声明或继承父类的构造方法//若无则说明构造参数为空if ($method == '__construct') {$classRf = new ReflectionClass($class_name);if (! $classRf->hasMethod('__construct')) {return $params_set;}}//反射方法 获取参数$methodRf = new ReflectionMethod($class_name, $method);$params = $methodRf->getParameters();if (! empty($params)) {foreach ($params as $key => $param) {if ($paramClass = $param->getClass()) {// 对象参数 获取对象实例$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 {// 标量参数 获取变量名作为路由映射 包含默认值的记录默认值$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;}/*** 容器的运行入口 主要负责加载类方法,并将运行所需的标量参数做映射和默认值处理* @param [type] $class_name 运行类* @param [type] $method 运行方法* @param array $params 运行参数* @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);// 获取要运行的方法的参数$method_params = static::getParams($class_name, $method);// 关联传入的运行参数$method_params = array_map(function ($param) use ($params, $method_params) {if (is_object($param)) {// 对象参数 以完成依赖解析的具体实例return $param;}// 以下为关联传值 可通过参数名映射的方式关联传值 可省略含有默认值的参数if (array_key_exists($param, $params)) {// 映射传递路由参数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();
}// 契约实现
class FileStorageEngine implements StorageEngine
{public $msg = "file storage engine!" . PHP_EOL;public function info(){$this->msg = "file storage engine!" . PHP_EOL;}
}// 契约实现
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;}// 这里的参数你完全可以乱序的定义(我故意写的很乱序),你只需保证 route 参数中存在对应的必要参数即可// 默认值参数可以直接省略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;// 契约注入 具体看你为契约者绑定了哪个具体的实现类// 我们绑定的 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' => [ // 运行的参数'name' => 'big cat','age' => 27 // sex 有默认值 不传]
];try {// 依赖的单例注册IOCContainer::singleton(Foo::class, new Foo());// 依赖的契约注册 StorageEngine 相当于契约者 注册关联具体的实现类// 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

简单的实现了像 laraval 的 IOC 容器的特性,但比它多一项(可能也比较鸡肋)标量参数的关联传值,不过我这功能也限定死了你传入的参数必须与函数定义的参数名相关联,可我还是觉得能充分的填补默认参数不放在参数尾就无法跳过的强迫症问题.....



推荐阅读
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • Python开源库和第三方包的常用框架及库
    本文介绍了Python开源库和第三方包中常用的框架和库,包括Django、CubicWeb等。同时还整理了GitHub中最受欢迎的15个Python开源框架,涵盖了事件I/O、OLAP、Web开发、高性能网络通信、测试和爬虫等领域。 ... [详细]
  • python限制递归次数(python最大公约数递归)
    本文目录一览:1、python为什么要进行递归限制 ... [详细]
  • 十大经典排序算法动图演示+Python实现
    本文介绍了十大经典排序算法的原理、演示和Python实现。排序算法分为内部排序和外部排序,常见的内部排序算法有插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。文章还解释了时间复杂度和稳定性的概念,并提供了相关的名词解释。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 近期,某用户在重启RAC一个节点的数据库实例时,发现启动速度非常慢。同时业务部门反馈连接RAC存活节点的业务也受影响。通过对日志的分析, ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 从零基础到精通的前台学习路线
    随着互联网的发展,前台开发工程师成为市场上非常抢手的人才。本文介绍了从零基础到精通前台开发的学习路线,包括学习HTML、CSS、JavaScript等基础知识和常用工具的使用。通过循序渐进的学习,可以掌握前台开发的基本技能,并有能力找到一份月薪8000以上的工作。 ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
  • 本文讨论了在ASP中创建RazorFunctions.cshtml文件时出现的问题,即ASP.global_asax不存在于命名空间ASP中。文章提供了解决该问题的代码示例,并详细解释了代码中涉及的关键概念,如HttpContext、Request和RouteData等。通过阅读本文,读者可以了解如何解决该问题并理解相关的ASP概念。 ... [详细]
author-avatar
Joql
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有