在阅读该文章前建议先阅读Laravel学习笔记之IOC容器源码分析文章了解下Laravel的IOC容器是如何实现的
在Laravel的文档中提到了如何在服务提供者ServerProvider中绑定对象到容器。
我们可以通过bind方法注册一个绑定
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
singleton 方法绑定一个只需要解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个实例:
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app->make('HttpClient'));
});
还可以用instance方法将已存在的对象绑定到容器中
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);
从容器中解析对象
$fooBar = $this->app->make('HelpSpot\API');
我们具体分析一下他们的源码,看看有什么区别
instance()
public function instance($abstract, $instance)
{
$abstract = $this->normalize($abstract);//normalize函数对传进来的$abstract判断是否为字符串,如果是字符串,开头有\就去掉,不是字符串就直接返回
//这里判断传递进来是否为数组,比如['app'=>'Illuminate\Container\Container']
if (is_array($abstract)) {
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);//存放到aliases[]数组中
}
unset($this->aliases[$abstract]);
$bound = $this->bound($abstract);//判断bindings、instances、aliases数组中是否已经存在当前对象
$this->instances[$abstract] = $instance;//将对象丢进instances数组中
if ($bound) {
//如果已经注册过了,执行reboundCallbacks的回调函数
$this->rebound($abstract);
}
}
instance的作用是将已有的实例注册到容器中
bind()方法
public function bind($abstract, $concrete = null, $shared = false)
{
$abstract = $this->normalize($abstract);//normalize函数对传进来的$abstract判断是否为字符串,如果是字符串,开头有\就去掉,不是字符串就直接返回
$concrete = $this->normalize($concrete);//同上
//这里判断传递进来是否为数组
if (is_array($abstract)) {
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);//存放到aliases[]数组中
}
//删除instances、aliases已经注册过的
$this->dropStaleInstances($abstract);
//这里如果没有传递$concrete变量$concrete变量默认为别名
if (is_null($concrete)) {
$concrete = $abstract;
}
//如果$concrete不是一个闭包,就封装成闭包返回,该闭包会判断$abstract和$concrete是否相等,相等就调用容器中的build方法,否则调用容器的make方法解析
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
//保存在bindings当中
$this->bindings[$abstract] = compact('concrete', 'shared');
//如果已经注册过了,执行reboundCallbacks的回调函数
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
我们来看build方法,make方法放到最后分析
public function build($concrete, array $parameters = [])
{
//如果参数$concrete是一个闭包,那么执行这个闭包并且返回
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
//不是闭包的话,说明是一个类名,这里利用反射方法
$reflector = new ReflectionClass($concrete);
//如果这个类不能被实例化,抛出异常信息
if (! $reflector->isInstantiable()) {
if (! empty($this->buildStack)) {
$previous = implode(', ', $this->buildStack);
$message = "Target [$concrete] is not instantiable while building [$previous].";
} else {
$message = "Target [$concrete] is not instantiable.";
}
throw new BindingResolutionException($message);
}
//将类存放到buildStack数组
$this->buildStack[] = $concrete;
//得到构造函数
$constructor = $reflector->getConstructor();
//如果构造函数为空,说明没有依赖,可以直接实例化返回
if (is_null($constructor)) {
//从buildStack中删除这个类
array_pop($this->buildStack);
return new $concrete;
}
//利用php反射获取构造函数的参数
$dependencies = $constructor->getParameters();
//keyParametersByArgument函数将已经存在的参数整合到$parameters变量
$parameters = $this->keyParametersByArgument(
$dependencies, $parameters
);
//这里getDependencies函数会去遍历参数,调用make函数实例化依赖的类并返回,不是类就抛出错误
$instances = $this->getDependencies(
$dependencies, $parameters
);
//从buildStack中删除这个类
array_pop($this->buildStack);
//返回实例化后的类
return $reflector->newInstanceArgs($instances);
}
可以看出build的主要作用是用来执行闭包函数,或者实例化类名后返回
所以最终bind方法会在bindings[]中存放如下内容:
bindings['events']=>[
'concrete'=>$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make('Illuminate\Contracts\Queue\Factory');
});
}),
'shared'=>false
]
singleton函数其实就是调用了bind方法,不过$shared参数为true;
也就是说bindings[]中存放内容如下:
bindings['events']=>[
'concrete'=>$this->app->singleton('events', function ($app) {
return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
return $app->make('Illuminate\Contracts\Queue\Factory');
});
}),
'shared'=>true
]
最后我们来看make()函数
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($this->normalize($abstract));
//如果该实例已经存在,直接返回,也就是instances注册的实例
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
//通过别名获取之前bind方法保存在bindings[]数组中存放的闭包函数
$concrete = $this->getConcrete($abstract);
//判断$concrete是否是闭包函数,或者$concrete==$abstract 满足的话执行build函数,否则执行make函数(这里其实在判断传的是闭包还是类名还是绑定接口到实现的方式)
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete, $parameters);//build函数上面已经分析过了,这里$object已经被build函数生成了实例了
} else {
//如果是绑定接口到实现的方式,那么再执行make函数,把实现类的类名传入,这次调用build生成的就是实现类的实例
$object = $this->make($concrete, $parameters);
}
//这里应该是执行一些额外设置的回调函数
foreach ($this->getExtenders($abstract) as $extender) {
$object = $extender($object, $this);
}
//如果是否是单例注册,放到instances,下次执行make直接返回已经实例化好的对象
if ($this->isShared($abstract)) {
$this->instances[$abstract] = $object;
}
//运行回调函数
$this->fireResolvingCallbacks($abstract, $object);
//标记已经实例化
$this->resolved[$abstract] = true;
//返回实例
return $object;
}
完。
本文地址:https://www.blear.cn/article/Laravel-instance-bind-singleton-make-source
转载时请以链接形式注明出处
评论