详解Laravel服务容器的绑定与解析
前言
老实说,第一次老大让我看laravel框架手册的那天早上,我是很绝望的,因为真的没接触过,对我这种渣渣来说,laravel的入门门槛确实有点高了,但还是得硬着头皮看下去(虽然到现在我还有很多没看懂,也没用过)。
后面慢慢根据公司项目的代码对laravel也慢慢熟悉起来了,但还是停留在一些表面的功能,例如依赖注入,ORM操作,用户认证这些和我项目业务逻辑相关的操作,然后对于一些架构基础的,例如服务提供器,服务容器,中间件,Redis等这些一开始就要设置好的东西,我倒是没实际操作过(因为老大一开始就做好了),所以看手册还是有点懵。
所以有空的时候逛逛论坛,搜下Google就发现许多关于laravel核心架构的介绍,以及如何使用的网站(确实看完后再去看手册就好理解多了),下面就根据一个我觉得不错的网站上面的教学来记录一下laravel核心架构的学习
网站地址:https://laraweb.net/这是一个日本的网站,我觉得挺适合新手的,内容用浏览器翻译过来就ok了,毕竟日文直翻过来很好理解的
关于服务容器
手册上是这样介绍的:Laravel服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入这个花俏名词实质上是指:类的依赖项通过构造函数,或者某些情况下通过「setter」方法「注入」到类中。。。。。。(真的看不懂啥意思)
服务容器是用于管理类(服务)的实例化的机制。直接看看服务容器怎么用
1.在服务容器中注册类(bind)
$this->app->bind('sender','MailSender'); //$this->app成为服务容器。
2.从服务容器生成类(make)
$sender=$this->app->make('sender'); //从服务容器($this->app)创建一个sender类。
在这种情况下,将返回MailSender的实例。
这是服务容器最简单的使用,下面是对服务容器的详细介绍
laravel容器基本认识
一开始,index.php文件加载Composer生成定义的自动加载器,然后从bootstrap/app.php脚本中检索Laravel应用程序的实例。Laravel本身采取的第一个动作是创建一个application/servicecontainer的实例。
$app=newIlluminate\Foundation\Application( dirname(__DIR__) );
这个文件在每一次请求到达laravel框架都会执行,所创建的$app即是laravel框架的应用程序实例,它在整个请求生命周期都是唯一的。laravel提供了很多服务,包括认证,数据库,缓存,消息队列等等,$app作为一个容器管理工具,负责几乎所有服务组件的实例化以及实例的生命周期管理。当需要一个服务类来完成某个功能的时候,仅需要通过容器解析出该类型的一个实例即可。从最终的使用方式来看,laravel容器对服务实例的管理主要包括以下几个方面:
- 服务的绑定与解析
- 服务提供者的管理
- 别名的作用
- 依赖注入
先了解如何在代码中获取到容器实例,再学习上面四个关键
如何在代码中获取到容器实例
第一种是
$app=app(); //app这个辅助函数定义在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php
里面,,这个文件定义了很多help函数,并且会通过composer自动加载到项目中。
所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()。
第二种是
Route::get('/',function(){ dd(App::basePath()); return''; }); //这个其实是用到Facade,中文直译貌似叫门面,在config/app.php中,
有一节数组aliases专门用来配置一些类型的别名,第一个就是'App'=>Illuminate\Support\Facades\App::class,
具体的Google一下laravel有关门面的具体实现方式
第三种是
在服务提供者里面直接使用$this->app。服务提供者后面还会介绍,现在只是引入。因为服务提供者类都是由laravel容器实例化的,这些类都继承自Illuminate\Support\ServiceProvider,它定义了一个实例属性$app:
abstractclassServiceProvider { protected$app;
laravel在实例化服务提供者的时候,会把laravel容器实例注入到这个$app上面。所以我们在服务提供者里面,始终能通过$this->$app访问到laravel容器实例,而不需要再使用app()函数或者AppFacade了。
如何理解服务绑定与解析
浅义层面理解,容器既然用来存储对象,那么就要有一个对象存入跟对象取出的过程。这个对象存入跟对象取出的过程在laravel里面称为服务的绑定与解析。
app()->bind('service','thisisservice1'); app()->bind('service2',[ 'hi'=>function(){ //sayhi } ]); classService{ } app()->bind('service3',function(){ returnnewService(); });
还有一个单例绑定singleton,是bind的一种特殊情况(第三个参数为true),绑定到容器的对象只会被解析一次,之后的调用都返回相同的实例
publicfunctionsingleton($abstract,$concrete=null) { $this->bind($abstract,$concrete,true); }
在绑定的时候,我们可以直接绑定已经初始化好的数据(基本类型、数组、对象实例),还可以用匿名函数来绑定。用匿名函数的好处在于,这个服务绑定到容器以后,并不会立即产生服务最终的对象,只有在这个服务解析的时候,匿名函数才会执行,此时才会产生这个服务对应的服务实例。
实际上,当我们使用singleton,bind方法以及数组形式,(这三个方法是后面要介绍的绑定的方法),进行服务绑定的时候,如果绑定的服务形式,不是一个匿名函数,也会在laravel内部用一个匿名函数包装起来,这样的话,不轮绑定什么内容,都能做到前面介绍的懒初始化的功能,这对于容器的性能是有好处的。这个可以从bind的源码中看到一些细节:
if(!$concreteinstanceofClosure){ $concrete=$this->getClosure($abstract,$concrete); }
看看bind的底层代码
publicfunctionbind($abstract,$concrete=null,$shared=false)
第一个参数服务绑定名称,第二个参数服务绑定的结果(也就是闭包,得到实例),第三个参数就表示这个服务是否在多次解析的时候,始终返回第一次解析出的实例(也就是单例绑定singleton)。
服务绑定还可以通过数组的方式:
app()['service']=function(){ returnnewService(); };
绑定大概就这些,接下来看解析,也就是取出来用
$service=app()->make('service');
这个方法接收两个参数,第一个是服务的绑定名称和服务绑定名称的别名,如果是别名,那么就会根据服务绑定名称的别名配置,找到最终的服务绑定名称,然后进行解析;第二个参数是一个数组,最终会传递给服务绑定产生的闭包。
看源码:
/** *Resolvethegiventypefromthecontainer. * *@paramstring$abstract *@paramarray$parameters *@returnmixed */ publicfunctionmake($abstract,array$parameters=[]) { return$this->resolve($abstract,$parameters); } /** *Resolvethegiventypefromthecontainer. * *@paramstring$abstract *@paramarray$parameters *@returnmixed */ protectedfunctionresolve($abstract,$parameters=[]) { $abstract=$this->getAlias($abstract); $needsContextualBuild=!empty($parameters)||!is_null( $this->getContextualConcrete($abstract) ); //Ifaninstanceofthetypeiscurrentlybeingmanagedasasingletonwe'll //justreturnanexistinginstanceinsteadofinstantiatingnewinstances //sothedevelopercankeepusingthesameobjectsinstanceeverytime. if(isset($this->instances[$abstract])&&!$needsContextualBuild){ return$this->instances[$abstract]; } $this->with[]=$parameters; $concrete=$this->getConcrete($abstract); //We'rereadytoinstantiateaninstanceoftheconcretetyperegisteredfor //thebinding.Thiswillinstantiatethetypes,aswellasresolveanyof //its"nested"dependenciesrecursivelyuntilallhavegottenresolved. if($this->isBuildable($concrete,$abstract)){ $object=$this->build($concrete); }else{ $object=$this->make($concrete); } //Ifwedefinedanyextendersforthistype,we'llneedtospinthroughthem //andapplythemtotheobjectbeingbuilt.Thisallowsfortheextension //ofservices,suchaschangingconfigurationordecoratingtheobject. foreach($this->getExtenders($abstract)as$extender){ $object=$extender($object,$this); } //Iftherequestedtypeisregisteredasasingletonwe'llwanttocacheoff //theinstancesin"memory"sowecanreturnitlaterwithoutcreatingan //entirelynewinstanceofanobjectoneachsubsequentrequestforit. if($this->isShared($abstract)&&!$needsContextualBuild){ $this->instances[$abstract]=$object; } $this->fireResolvingCallbacks($abstract,$object); //Beforereturning,wewillalsosettheresolvedflagto"true"andpopoff //theparameteroverridesforthisbuild.Afterthosetwothingsaredone //wewillbereadytoreturnbackthefullyconstructedclassinstance. $this->resolved[$abstract]=true; array_pop($this->with); return$object; }
第一步:
$needsContextualBuild=!empty($parameters)||!is_null( $this->getContextualConcrete($abstract) );
该方法主要是区分,解析的对象是否有参数,如果有参数,还需要对参数做进一步的分析,因为传入的参数,也可能是依赖注入的,所以还需要对传入的参数进行解析;这个后面再分析。
第二步:
if(isset($this->instances[$abstract])&&!$needsContextualBuild){ return$this->instances[$abstract]; }
如果是绑定的单例,并且不需要上面的参数依赖。我们就可以直接返回$this->instances[$abstract]。
第三步:
$concrete=$this->getConcrete($abstract); ... /** *Gettheconcretetypeforagivenabstract. * *@paramstring$abstract *@returnmixed$concrete */ protectedfunctiongetConcrete($abstract) { if(!is_null($concrete=$this->getContextualConcrete($abstract))){ return$concrete; } //Ifwedon'thavearegisteredresolverorconcreteforthetype,we'lljust //assumeeachtypeisaconcretenameandwillattempttoresolveitasis //sincethecontainershouldbeabletoresolveconcretesautomatically. if(isset($this->bindings[$abstract])){ return$this->bindings[$abstract]['concrete']; } return$abstract; }
这一步主要是先从绑定的上下文找,是不是可以找到绑定类;如果没有,则再从$bindings[]中找关联的实现类;最后还没有找到的话,就直接返回$abstract本身。
//We'rereadytoinstantiateaninstanceoftheconcretetyperegisteredfor //thebinding.Thiswillinstantiatethetypes,aswellasresolveanyof //its"nested"dependenciesrecursivelyuntilallhavegottenresolved. if($this->isBuildable($concrete,$abstract)){ $object=$this->build($concrete); }else{ $object=$this->make($concrete); } ... /** *Determineifthegivenconcreteisbuildable. * *@parammixed$concrete *@paramstring$abstract *@returnbool */ protectedfunctionisBuildable($concrete,$abstract) { return$concrete===$abstract||$concreteinstanceofClosure; }
如果之前找到的$concrete返回的是$abstract值,或者$concrete是个闭包,则执行$this->build($concrete),否则,表示存在嵌套依赖的情况,则采用递归的方法执行$this->make($concrete),直到所有的都解析完为止。
$this->build($concrete) /** *Instantiateaconcreteinstanceofthegiventype. * *@paramstring$concrete *@returnmixed * *@throws\Illuminate\Contracts\Container\BindingResolutionException */ publicfunctionbuild($concrete) { //IftheconcretetypeisactuallyaClosure,wewilljustexecuteitand //handbacktheresultsofthefunctions,whichallowsfunctionstobe //usedasresolversformorefine-tunedresolutionoftheseobjects. //如果传入的是闭包,则直接执行闭包函数,返回结果 if($concreteinstanceofClosure){ return$concrete($this,$this->getLastParameterOverride()); } //利用反射机制,解析该类。 $reflector=newReflectionClass($concrete); //Ifthetypeisnotinstantiable,thedeveloperisattemptingtoresolve //anabstracttypesuchasanInterfaceofAbstractClassandthereis //nobindingregisteredfortheabstractionssoweneedtobailout. if(!$reflector->isInstantiable()){ return$this->notInstantiable($concrete); } $this->buildStack[]=$concrete; //获取构造函数 $constructor=$reflector->getConstructor(); //Iftherearenoconstructors,thatmeanstherearenodependenciesthen //wecanjustresolvetheinstancesoftheobjectsrightaway,without //resolvinganyothertypesordependenciesoutofthesecontainers. //如果没有构造函数,则表明没有传入参数,也就意味着不需要做对应的上下文依赖解析。 if(is_null($constructor)){ //将build过程的内容pop,然后直接构造对象输出。 array_pop($this->buildStack); returnnew$concrete; } //获取构造函数的参数 $dependencies=$constructor->getParameters(); //Oncewehavealltheconstructor'sparameterswecancreateeachofthe //dependencyinstancesandthenusethereflectioninstancestomakea //newinstanceofthisclass,injectingthecreateddependenciesin. //解析出所有上下文依赖对象,带入函数,构造对象输出 $instances=$this->resolveDependencies( $dependencies ); array_pop($this->buildStack); return$reflector->newInstanceArgs($instances); }
总结
以上所述是小编给大家介绍的Laravel服务容器的绑定与解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。