服务容器绑定bind绑定欢迎关注我的博客:www.leoyang90.cnbind绑定是服务容器最常用的绑定方式。我们在一篇文章中讨论了绑定的三种类型:绑定自身、绑定闭包、绑定接口,今天这篇文章主要从源码上讲解Ioc服务容器是如何绑定的。/***向容器注册绑定。**@paramstring|array$abstract*@param\Closure|string|null$concrete*@parambool$shared*@returnvoid*/publicfunctionbind($abstract,$concrete=null,$shared=false){//如果没有给出具体类型,我们将简单地将具体类型设置为//抽象类型。之后,具体类型将被注册为共享//而不必在两个参数中声明它们的类。$this->dropStaleInstances($abstract);如果(is_null($concrete)){$concrete=$abstract;}//如果工厂不是闭包,这意味着它只是一个类名,//绑定到这个容器中的抽象类型,我们将把它包装起来//在它自己的闭包中给我们更多的方便延伸。if(!$concreteinstanceofClosure){$concrete=$this->getClosure($abstract,$concrete);$this->bindings[$abstract]=compact('concrete','shared');//如果抽象type已经在此容器中解析,我们将触发//反弹侦听器,以便任何已经解析的对象//都可以通过侦听器回调更新其对象副本。如果($this->resolved($abstract)){$this->rebound($abstract);}}从源码可以看出,服务端的绑定有以下几个步骤:去掉原来的注册,去掉当前绑定接口原来的实现单例对象,以及原来的别名,为实现绑定做准备新的实施。添加闭包。如果实现类不是闭包(绑定自身或绑定接口),则创建一个用于延迟加载的闭包。登记。将闭包函数和单例变量存储在绑定数组中,以便在解析期间使用。打回来。如果绑定接口已经被解析,则调用回调函数调整解析对象。移除原有注册dropStaleInstances用于移除当前接口的原有注册和别名。它负责清除绑定的别名和单例对象的实例,然后修改绑定:);}添加闭包getClosure的作用是为注册的非闭包实现额外的闭包,它有两个作用:延迟加载服务容器在getClosure中,为每个绑定的类包含一层闭包,这样闭包实际上会只在服务容器解析时运行,实现懒加载功能。递归绑定对于服务容器,绑定可以是递归的,例如:$app->bind(A::class,B::class);$app->bind(B::class,C::class);$app->bind(C::class,function(){returnnewC;})对于A类,我们可以直接解析A得到B类,但是如果仅此而已,服务容器直接使用反射如果创建B类,很可能会创建失败,因为类B很可能是一个接口,接口B绑定了其他的实现类。您必须知道接口不能被实例化。因此,服务容器需要递归解析A。这就是getClosure的功能。它使用make函数在闭包中绑定所有可能的递归绑定,这样在解析make(A::class)时,就得到了闭包make。(B::class),make(B::class)会得到闭包make(C::class),make(C::class)最终才能得到真正的实现。对于自绑定的情况,由于没有递归,会直接在闭包中使用build函数创建对象。(如果你还用make,会死循环)==$concrete){return$container->build($concrete);}返回$container->makeWith($concrete,$parameters);};}注册是将注册的接口及其实现添加到绑定数组中,其中compact()函数创建一个包含变量名及其值的数组。创建后的结果为:$bindings[$abstract]=['concrete'=>$concrete,'shared'=>$shared]注册回调后,还需要检查当前注册的接口是否已经实例化。如果已经被服务容器实例化,则必须调用回调函数。(如果有回调函数)resolved()函数用来判断当前接口是否解析完成。在判断之前,先获取接口的最终服务名:}返回isset($this->resolved[$abstract])||isset($this->instances[$abstract]);}publicfunctionisAlias($name){returnisset($this->aliases[$name]);}getAlias()函数采用递归的方式获取最终的别名的服务名称:publicfunctiongetAlias($abstract){if(!isset($this->aliases[$abstract])){return$abstract;}if($this->aliases[$abstract]===$abstract){thrownewLogicException("[{$abstract}]是自身的别名。");}return$this->getAlias($this->aliases[$abstract]);}如果当前接口已经被解析,则必须运行回调函数:protectedfunctionrebound($abstract){$instance=$this->制作($摘要);foreach($this->getReboundCallbacks($abstract)as$callback){call_user_func($callback,$this,$instance);}}}保护函数getReboundCallbacks($abstract){如果(isset($this->reboundCallbacks[$abstract])){返回$this->reboundCallbacks[$abstract];}return[];}reboundCallbacks从哪里来?这就是Laravel的核心——rebindingpublicfunctionrebinding($abstract,Closure$callback){$this->reboundCallbacks[$abstract=$this->getAlias($abstract)][]=$callbackIoc服务容器中提到的文章;如果($this->bound($abstract)){返回$this->make($abstract);}}值得注意的是:重绑定函数不仅绑定了回调函数,还顺便解析了接口抽象,因为只有解析完,下次注册时才会调用回调函数。bind($abstract,$concrete,true);}instancebinding不分析接口,直接给一个接口的实例作为单例对象。从下面可以看出,主要的工作是去除abstractAliases数组和aliases数组中接口的痕迹,防止make函数继续根据别名解析而make错误。如果当前接口曾经被注册过,则调用回调函数。公共函数实例($abstract,$instance){$this->removeAbstractAlias($abstract);$isBound=$this->bound($abstract);取消设置($this->aliases[$abstract]);$this->instances[$abstract]=$instance;如果($isBound){$this->rebound($abstract);}}受保护函数removeAbstractAlias($searched){if(!isset($this->aliases[$searched])){return;}foreach($this->abstractAliasesas$abstract=>$aliases){foreach($aliasesas$index=>$alias){if($alias==$searched){unset($this->abstractAliases[$abstract][$索引]);}}}}publicfunctionbound($abstract){返回isset($this->bindings[$abstract])||isset($this->实例[$abstract])||$this->isAlias($abstract);}上下文绑定上下文绑定一般用于依赖注入。当我们使用依赖注入来自动实例化对象时,服务容器实际上是通过反射机制来实例化构造函数的,在这个过程中,实例化的对象就是下面具体的,构造函数number的参数接口是抽象的,参数接口的实际实现是implementation。例如:$this->app->when(PhotoController::class)->needs(Filesystem::class)->give(function(){returnStorage::disk('local');});这里实例化的对象具体是PhotoController,构造函数的参数接口抽象是Filesystem。参数接口的实际实现是Storage::disk('local')。这样,每次解析构造函数的参数接口时,都会判断context中concrete[concrete][abstract](即concrete[PhotoController::class][Filesystem::class])对应的contextbinding当前上下文数组如果存在则直接从数组中取出,如果不存在则按正常方式解析。值得注意的是,concrete和abstract都使用了getAlias函数来保证最后得到的不是别名。公共函数when($concrete){returnnewContextualBindingBuilder($this,$this->getAlias($concrete));}publicfunction__construct(Container$container,$concrete){$this->concrete=$concrete;$this->container=$container;}publicfunctionneeds($abstract){$this->needs=$abstract;返回$this;}publicfunctiongive($implementation){$this->container->addContextualBinding($this->concrete,$this->needs,$implementation);}publicfunctionaddContextualBinding($concrete,$abstract,$implementation){$this->contextual[$concrete][$this->getAlias($abstract)]=$implementation;}tagbinding标签绑定比较简单。绑定过程就是在标签和接口之间创建一个对应的数组。在解析过程中,所有的接口都是根据tag进行解析的。公共功能标签($abstracts,$tags){$tags=is_array($tags)?$标签:array_slice(func_get_args(),1);foreach($tagsas$tag){if(!isset($this->tags[$tag])){$this->tags[$tag]=[];}foreach((array)$abstractsas$abstract){$this->tags[$tag][]=$abstract;}}}数组绑定当使用数组进行绑定时($app()[A::class]=B::class),服务容器会调用offsetSet函数:publicfunctionoffsetSet($key,$value){$this->bind($key,$valueinstanceofClosure?$value:function()use($value){return$value;});}extend扩展有两种,一种是针对实例注册的对象,在这种情况下,它会立即生效并更新之前实例化的对象;另一种情况,非实例化注册对象,那么闭包函数会被放到extenders数组中,只有下次实例化该对象时才会起作用:publicfunctionextend($abstract,Closure$closure){$abstract=$this->getAlias($abstract);如果(isset($this->instances[$abstract])){$this->instances[$abstract]=$closure($this->instances[$abstract],$this);$this->rebound($摘要);}else{$this->extenders[$abstract][]=$closure;如果($this->resolved()){$this->反弹($abstract);}}}服务器事件服务器的事件注册依赖于resolving函数和afterResolving函数。这两个函数维护了globalResolvingCallbacks、resolvingCallbacks、globalAfterResolvingCallbacks和afterResolvingCallbacks数组。这些数组存储事件的回调闭包函数。每当对象被解析时,就会遍历这些数组,触发事件:}if(is_null($callback)&&$abstractinstanceofClosure){$this->globalResolvingCallbacks[]=$abstract;}else{$this->resolvingCallbacks[$abstract][]=$callback;}}publicfunctionafterResolving($abstract,Closure$callback=null){if(is_string($abstract)){$abstract=$this->getAlias($abstract);}if($abstractinstanceofClosure&&is_null($callback)){$this->globalAfterResolvingCallbacks[]=$abstract;}else{$this->afterResolvingCallbacks[$abstract][]=$callback;}}用StackEdit编写。
