在开始之前要明确一个概念,不管是设计模式,还是依赖注入等等,都是为了实现模块化.所谓模块化就是希望一个软件是由很多子模块组成的,这些模块之间的依赖程度尽量的低,也就是如果系统中不需要某一个功能,那么只要移除这个功能所对应的模块就可以了.
那么,我们今天要说的服务容器就是为了实现上面的功能.你应该听过,Laravel中的服务容器其本质上是一个IoC容器,但是好像队IoC又不是很了解,讲来讲去优点很多,功能很强劲.但是不懂原理怎么用都不踏实啊.所以,这里我们自己来实现一个IoC容器,洞察其本质.
> 在开始之前,先说明一点,阅读本篇文章至少要保证有一下的基础知识:
>
> * php反射用法
>
> * 闭包的use用法
>
> 如果不懂上面的内容,请先补充.避免阅读代码时候产生的不适感.
Container.php
~~~
<?php
class Container
{
//用于提供实例的回调函数,真正的容器还会装实例等其他内容.从而实现单列等高级功能
public $binding = [];
//绑定接口和生成相应实例的回调函数
public function bind($abstract, $concrete = null, $shared = false)
{
if (!$concrete instanceof Closure) {
//如果提供的参数不是回调函数则产生默认的回调函数
$concrete = $this->getClosure($abstract, $concrete);
}
$this->binding[$abstract] = compact('concrete', 'shared');
}
//默认生成的实例的回调函数
protected function getClosure($abstract, $concrete)
{
//生成实例的回调函数,$c一般为IOC容器对象,在调用回调生成实例时提供
//即build函数中的$concrete($this)
return function ($c) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
//调用的是容器的build或make方法生成的实例
return $c->$method($concrete);
};
}
//生成实例对象,首先要解决接口和要实例化类之间的依赖关系
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
} else {
$object = $this->make($concrete);
}
return $object;
}
public function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
//获取绑定的回调函数
protected function getConcrete($abstract)
{
if (!isset($this->binding[$abstract])) {
return $abstract;
}
return $this->binding[$abstract]['concrete'];
}
//实例化对象
public function build($concrete) {
if($concrete instanceof Closure) {
return $concrete($this);
}
//反射...
$reflector = new ReflectionClass($concrete);
if(!$reflector->isInstantiable()) {
echo $message = "Target [$concrete] is not instantiable";
}
//获取要实例化对象的构造函数
$constructor = $reflector->getConstructor();
//没有定义构造函数,只有默认的构造函数,说明构造函数参数个数为空
if(is_null($constructor)) {
return new $concrete;
}
//获取构造函数所需要的所有参数
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
//从给出的数组参数在中实例化对象
return $reflector->newInstanceArgs($instances);
}
/**
* 获取构建类所需要的所有依赖,级构造函数所需要的参数 ,
*/
protected function getDependencies($paramters) {
$dependencies = [];
foreach ($paramters as $paramter) {
//获取到参数名称.
$dep = $paramter->getClass();
if(is_null($dep)){
$dependencies = null;
}else{
$dependencies[] = $this->resolveClass($paramter);
}
}
return (array)$dependencies;
}
/**
* 实例化 构造函数中所需要的参数.
*/
protected function resolveClass(ReflectionParameter $parameter) {
$name = $parameter->getClass()->name;
return $this->make($name);
}
}
~~~
这就是一个IoC容器的实现代码.乍一看,很麻烦.其实真的蛮麻烦的 =_=,如果是第一次接触的话,并不是那么好消化,这里再给出使用IoC容器的代码
~~~
<?php
require __DIR__ . '/Container.php';
interface TrafficTool
{
public function go();
}
class Train implements TrafficTool
{
public function go()
{
echo "train....";
}
}
class Leg implements TrafficTool
{
public function go()
{
echo "leg..";
}
}
class Traveller
{
/**
* @var Leg|null|Train
* 旅行工具
*/
protected $_trafficTool;
public function __construct(TrafficTool$trafficTool)
{
$this->_trafficTool = $trafficTool;
}
public function visitTibet()
{
$this->_trafficTool->go();
}
}
//实例化IoC容器
$app = new Container();
//绑定某一功能到IoC
$app->bind('TrafficTool', 'Train');
$app->bind('travellerA', 'Traveller');
// 实例化对象
$tra = $app->make('travellerA');
$tra->visitTibet();
~~~
运行例子发现会输出:train...这个例子假设旅行者去青藏旅行,可以坐火车(train)或者走路(leg)去青藏.
好了,其实这样子本篇文章就可以结束了,因为所有的答案都在IoC容器的实现中, 但是为了可以更好的理解上面的代码,我们继续往下分析.
首先,希望你可以运行一下上面的代码,虽然简单的运行代码并不会帮助你理解代码,但是一个可以运行的例子会让人比较踏实,能够更有把握的理解代码.
在深入每一行代码之前,我们从整体上来分析,IoC解决了一个什么问题?简单点说,就是我们再实例化对象的时候不用使用new了,有了IoC容器之后,我们调用make函数就可以实例化出一个对象了.然而,你发现,Traveller的构造函数是需要一个参数的,可是我们好像并没有提供这个参数?
这就是IoC强大之处了, 调用make实例化对象的时候,容器会使用反射功能,去分析我们要实例化对象的构造函数,获取构造函数所需的每个参数,然后分别去实例化这些参数,如果实例化这些参数也要参数,那么就再去实例化参数的参数.....=_=.到最后成功实例化我们所需要的traveller了.在Container的build函数就是使用反射来实例化对象.
但是,有一个问题了,IoC容器怎么知道实例化Traveller的时候需要的参数train,而不是leg?
其实,IoC容器什么都不知道,IoC会实例化哪些对象都是通过bind函数告诉IoC的,上面的例子两次调用bind函数,就是告诉Ioc可以实例化的对象有Train和Traveller. 再通俗讲就是:当需要当我们需要TrafficTool这个服务的时候去实例化Train这个类,需要一个travellerA的旅行者的时候去实例化Traveller类.而Train这个就是travellerA就是去青藏的方式. 这样子如果想要走路去青藏的话只要把$app->bind('Visit', 'Train');改为$app->bind('Visit', 'Leg');就可以.
可是,这上面的这些有什么意义?直接$tra = new Traveller($trafficTool)来实例化对象好像也没有什么不好的.
>使用new来实例化对象的时候,会产生依赖.比如上面$tra = new Traveller($trafficTool),这说明我们要创建一个Traveller之前得有一个$trafficTool,即Traveller依赖于trafficTool.当使用new来实例化Traveller的时候,Traveller和trafficTool之间就产生了耦合.这样,这两个组件就没办法分开了.
而使用IoC是怎么解决这个问题的,之前说过,如果想要如果想要走路去青藏的话只要把$app->bind('Visit', 'Train');改为$app->bind('Visit', 'Leg');就可以.这样子,使用何种方式去青藏,我们可以自由的选择.
我们站在Laravel框架设计者的角度去想,设计者肯定希望一个框架提供的功能越多越好,但是又要保证强大的同时又不会限制使用者.最好可以保证使用者想实现什么奇怪的需求都可以.那么功能强大但是又不局限的最好方法就是什么都不做,提供一个强大的IoC容器.所有需要实现的功能都变成一个个服务,需要什么服务就把服务注册(即调用bind函数)到IoC中,然后让IoC去管理依赖.
开发者想到一个变态的需求:走路去青藏,那么只要你实现了走路去青藏这个功能,然后把这个功能当做一个服务注册到IoC中,以后你需要这个服务的时候IoC就帮你实例化这个服务.当开发者回归正常之后觉得还是坐火车去吧,于是不注册走路这个功能,实现坐火车的功能,然后注册这个功能.下次IoC实例化的时候就是实例化坐火车这个功能了.
好了,剩下的部分就是一行一行的阅读Container的代码了,Laravel框架中的服务容器代码也是这个样子,只是功能更加强悍.但是核心是一样的,上面的代码懂了以后再使用Laravel框架就会更加游刃有余了.
文章虽短.但是内容很多.尤其是代码,虽然可能只是短短的一个例子,但是包含了很多内容.值得好好分析,这里放个彩蛋:Traveller中构造函数参数类似为TrafficTool,是一个接口.但是实例化的是Train.这里体现了设计模式的一个原则
> 面对接口编程,而不是面对实现编程.
- 配置
- composer安装
- composer用法
- composer版本约束表达
- phpstorm
- sftp文件同步
- php类型约束
- laradock
- 配置文件缓存详解
- git
- 自定义函数
- 核心概念
- IOC
- 服务提供者
- Facade
- 契约
- 生命周期
- 路由
- 请求
- 命名路由
- 路由分组
- 资源路由
- 控制器路由
- 响应宏
- 响应
- Command
- 创建命令
- 定时任务
- console路由
- 执行用户自定义的定时任务
- artisan命令
- 中间件
- 创建中间件
- 使用中间件
- 前置和后置
- 详细介绍
- 访问次数限制
- 为 VerifyCsrfToken 添加过滤条件
- 单点登录
- 事件
- 创建
- ORM
- 简介
- DB类
- 配置
- CURD
- queryScope和setAttribute
- 查看sql执行过程
- 关联关系
- 一对一
- 一对多
- 多对多
- 远程关联
- 多态一对多
- 多态多对多
- 关联数据库的调用
- withDefault
- 跨模型更新时间戳
- withCount,withSum ,withAvg, withMax,withMin
- SQL常见操作
- 模型事件
- 模型事件详解
- 模型事件与 Observer
- deleted 事件未被触发
- model validation
- ORM/代码片段
- Repository模式
- 多重where语句
- 中间表类型转换
- Collection集合
- 新增的一些方法
- 常见用法
- 求和例子
- 机场登机例子
- 计算github活跃度
- 转化评论格式
- 计算营业额
- 创建lookup数组
- 重新组织出表和字段关系并且字段排序
- 重构循环
- 其他例子
- 其他问题一
- 去重
- 第二个数组按第一个数组的键值排序
- 搜索ES
- 安装
- 表单
- Request
- sessiom
- Response
- Input
- 表单验证
- 简介
- Validator
- Request类
- 接口中的表单验证
- Lumen 中自定义表单验证返回消息
- redis
- 广播事件
- 发布订阅
- 队列
- 守护进程
- redis队列的坑
- beanstalkd
- rabbitmq
- redis队列
- 日志模块
- 错误
- 日志详解
- 数据填充与迁移
- 生成数据
- 数据填充seed
- migrate
- 常见错误
- Blade模板
- 流程控制
- 子视图
- URL
- 代码片段
- Carbon时间类
- 一些用法
- 邮件
- 分页
- 加密解密
- 缓存
- 文件上传
- 优化
- 随记
- 嵌套评论
- 判断字符串是否是合法的 json 字符串
- 单元测试
- 计算出两个日期的diff
- 自定义一个类文件让composer加载
- 时间加减
- 对象数组互转方法
- 用户停留过久自动退出登录
- optional 辅助方法
- 文件下载
- Api
- Dingo api
- auth.basic
- api_token
- Jwt-Auth
- passport
- Auth
- Authentication 和 Authorization
- Auth Facade
- 授权策略
- Gates
- composer包
- debug包
- idehelp包
- image处理
- 验证码
- jq插件
- 第三方登录
- 第三方支付
- log显示包
- 微信包
- xss过滤
- Excel包
- MongoDB
- php操作
- 聚合查询
- 发送带附件邮件
- 中文转拼音包
- clockwork网页调试
- emoji表情
- symfony组件
- swooletw/laravel-swoole
- 常见问题
- 跨域问题
- Laravel队列优先级的一个坑
- cache:clear清除缓存问题
- .env无法读取
- 源码相关基础知识
- __set和__get
- 依赖注入、控制反转和依赖倒置原则
- 控制反转容器(Ioc Container)
- 深入服务容器
- call_user_func
- compact
- 中间件简易实现
- array_reduce
- 中间件实现代码
- Pipeline管道操作
- composer自动加载
- redis延时队列
- 了解laravel redis队列
- cli
- 源码解读
- Facade分析
- Facade源码分析
- IOC服务容器
- 中间件原理
- 依赖注入浅析
- 微信
- 微信公众号
- 常用接收消息
- 6大接收接口
- 常用被动回复消息
- 接口调用凭证
- 自定义菜单
- 新增素材
- 客服消息
- 二维码
- 微信语音
- LBS定位
- 网页授权
- JSSDK
- easywechat
- 小程序
- 小程序配置app.json