说明:本文主要研究Laravel中Container的源码,主要学习Container的绑定和解析过程,以及解析过程中的依赖解析。分享自己的研究经历,希望能帮助到别人。实际上,绑定Container的方式主要有3种:bind()、singleton()、instance(),而singleton()只是一种bind()with'shared'=true。这些在Laravel学习笔记的IoCContainer实例中已经讲过源码分析,实现方法并不复杂。Service通过ServiceProvider绑定到Container后,当需要Service时,需要借助Container自动解析make()。OK,下面说说自动解析的过程,研究一下Container在自动解析Service的时候是如何解决Service的依赖问题的。开发环境:Laravel5.3+PHP7+OSX10.11PHPUnit测试绑定在说解析过程之前,先测试\Illuminate\Container\Container中绑定的源码,这里测试bind()、singleton()、instance()三种绑定方法:publicfunctiontestBindClosure(){//Arrange$expected='LaravelisaPHPFramework.';$this->container->bind('PHP',function()use($expected){return$expected;});//Actual$actual=$this->container->make('PHP');//断言$this->assertEquals($expected,$actual);}publicfunctiontestBindInterfaceToImplement(){//安排$this->container->bind(IContainerStub::class,ContainerImplementationStub::class);//实际$actual=$this->container->make(IContainerStub::class);//断言$this->assertInstanceOf(IContainerStub::class,$actual);}publicfunctiontestBindDependencyResolution(){//Arrange$this->container->bind(IContainerStub::class,ContainerImplementationStub::class);//Actual$actual=$this->container->make(ContainerNestedDependentStub::class);//Assert$this->assertInstanceOf(ContainerDependentStub::class,$actual->containerDependentStub);$this->assertInstanceOf(ContainerImplementationStub::class,$actual->containerDependentStub->containerStub);}publicfunctiontestSingleton(){//排列$this->container->singleton(ContainerConcreteStub::class);$expected=$this->container->make(ContainerConcreteStub::class);//Actual$actual=$this->container->make(ContainerConcreteStub::class);//Assert$this->assertSame($expected,$actual);}publicfunctiontestInstanceExistingObject(){//安排$expected=newContainerImplementationStub();$this->container->instance(IContainerStub::class,$expected);//Actual$actual=$this->container->make(IContainerStub::class);//断言$this->assertSame($预期,$actual);}}classContainerConcreteStub{}interfaceIContainerStub{}classContainerImplementationStubimplementsIContainerStub{}classContainerDependentStub{/***@var\MyRightCapital\Container\Tests\IContainerStub*/public$containerStub;publicfunction__container-Stub>this$containerStub{thiscontainerStub=$containerStub;}}classContainerNestedDependentStub{/***@var\MyRightCapital\Container\Tests\ContainerDependentStub*/public$containerDependentStub;publicfunction__construct(ContainerDependentStub$containerDependentStub){$this->containerDeubpendentStub=endtestedhereStub}$containerbind()绑定闭包,绑定接口和对应的实现,依赖解析这三个特性,singleton()测试一个特性是否绑定到单例,instance()测试一个已经存在的对象来绑定这个特性,测试结果5个测试全部通过:PHPStorm中配置PHPUnit可以参考这篇文章:Laravel学习笔记:基于PHPStorm的Laravel开发编辑器make()源码代码分析从上面的testcase来看,make()是负责从Container中解析服务的,而在testBindDependencyResolution()的测试中,可以发现当ContainerNestedDependentStub::class有构造依赖时,Container会自动解析依赖并注入ContainerNestedDepend在entStub::class的构造函数中,这个依赖是ContainerDependentStub::class,这个依赖有自己的依赖IContainerStub::class,从断言语句$this->assertInstanceOf(ContainerImplementationStub::class,$actual->containerDependentStub->容器存根);我知道,Container会自动再次解析这个依赖,所有这些都不需要我们手动解析,都是Container自动解析的。Container是怎么做到的?其实并不复杂,只是解决了依赖与是通过PHP的Reflector反射机制实现的。先看make()的源码:/***Resolvethegiventypefromthecontainer.**@paramstring$abstract*@paramarray$parameters*@returnmixed*/publicfunctionmake($abstract,array$parameters=[]){$abstract=$this->getAlias($this->normalize($abstract));//如果是instance()绑定方法,直接解析返回绑定的serviceif(isset($this->instances[$abstract])){return$this->instances[$abstract];}//获取绑定到$abstract的$concrete$concrete=$this->getConcrete($abstract);if($this->isBuildable($concrete,$abstract)){$object=$this->build($concrete,$parameters);}else{$object=$this->make($concrete,$parameters);}foreach($this->getExtenders($abstract)as$扩展器){$object=$extender($object,$this);}if($this->isShared($abstract)){$this->instances[$abstract]=$object;}$this->fireResolvingCallbacks($abstract,$object);$this->resolved[$abstract]=true;返回$object;}protectedfunctiongetConcrete($abstract){if(!is_null($concrete=$this->getContextualConcrete($abstract))){返回$混凝土;}//如果是$this->container->singleton(ContainerConcreteStub::class);这样绑定,也就是$concrete=null//然后是$abstract=$concrete,可以看上面PHPUnit的testSingleton()//这个方法叫做'autocomplete'绑定if(!isset($this->绑定[$abstract])){return$abstract;}return$this->bindings[$abstract]['concrete'];}protectedfunctionisBuildable($concrete,$abstract){return$concrete===$abstract||$concreteinstanceofClosure;}从上面的源码我们可以知道,如果绑定是闭包或者'自动完成'绑定($concrete=null),build()这个闭包或者类名会被转换成对应的实例。如果以‘接口实现’的方式绑定,则需要在getConcrete之后再次调用make(),$abstract=$concrete,满足isBuildable()条件,进入build()函数。因此,无论在上述PHPUnit测试用例中使用何种绑定,都必须进入build()函数编译相应的对象实例。编译对象时,检查是否共享,是否触发回调,标记对象已解析。OK,看build()的源码:,array$parameters=[]){//如果闭包直接执行闭包返回,例如PHPUnit的test:testBindClosure()if($concreteinstanceofClosure){return$concrete($this,$parameters);}//如本次测试:testBindInterfaceToImplement(),其中$concrete=ContainerImplementationStub::class类名,//使用反射ReflectionClass检测ContainerImplementationStub类的构造函数和构造函数的依赖$reflector=newReflectionClass($concrete);//如果ContainerImplementationStub无法实例化。这应该是一个接口或者抽象类,或者ContainerImplementationStub的构造函数是private的,$this->buildStack);$message="Target[$concrete]isnotinstantiablewhilebuilding[$previous]。";}else{$message="Target[$concrete]isnotinstantiable.";}thrownewBindingResolutionException($message);}$this->buildStack[]=$concrete;//获取构造函数的反射$constructor=$reflector->getConstructor();//构造函数为空表示没有依赖,直接返回if(is_null($constructor)){array_pop($this->buildStack);returnnew$concrete;}//获取构造函数的依赖并返回ReflectionParameter[]$dependencies=$constructor->getParameters();$parameters=$this->keyParametersByArgument($dependencies,$parameters);//然后就是获取相关的依赖,比如本次测试中的testBindDependencyResolution(),//ContainerNestedDependentStub::class依赖于$instances=$this->getDependencies($dependenciesContainerDependentStub::class,$parameters);array_pop($this->buildStack);return$reflector->newInstanceArgs($instances);}从源码看,ContainerNestedDependentStub::class的构造函数依赖时比较麻烦ContainerDependentStub::class,通过getDependencies()解决,看getDependencies()的源码://这里$parameters=ReflectionParameter[]protectedfunctiongetDependencies(array$parameters,array$primitives=[]){$dependencies=[];foreach($parametersas$parameter){$dependency=$parameter->getClass();//如果某个依赖值已经给出,assignif(array_key_exists($parameter->name,$primitives)){$dependencies[]=$primitives[$parameter->name];}//如果类名为null,说明是一个基础类type,比如'int','string'andsoon.elseif(is_null($dependency)){$dependencies[]=$this->resolveNonClass($parameter);}//如果是类名,比如ContainerDependentStub::class,然后resolveClass就被解析成一个对象else{$dependencies[]=$this->resolveClass($parameter);}}return$dependencies;}通过上面的注释,看resolveClass()的源码:protectedfunctionresolveClass(ReflectionParameter$parameter){try{//$parameter->getClass()->name返回类名,比如ContainerNestedDependentStub依赖于$containerDependentStub//$containerDependentStub的typehint是ContainerDependentStub,所以类名是'ContainerDependentStub'//然后递归继续make(ContainerDependentStub::class)//类似PHPUnit中的这个测试$this->container->make(ContainerNestedDependentStub::class)//ContainerNestedDependentStub依赖IContainerStub::class,//IContainerStub::class绑定到ContainerImplementationStub::class//直到ContainerImplementationStub没有依赖或者构造函数是基础属性,//***build()endsre转$this->make($parameter->getClass()->name);}catch(BindingResolutionException$e){if($parameter->isOptional()){return$parameter->getDefaultValue();}throw$e;}}从上面的代码注释到build()是一个递归过程,A类依赖B类,B类依赖C类和D类,然后从A类开始build,发现依赖B类,并且然后从Container开始分析中间的make(),然后build()生成B类,发现依赖C类,然后make()和build(),发现B类同时也依赖D类time,然后是make()和build(),以此类推,直到没有依赖或者被依赖的基本属性,一步步分析完成后,发现对Container的分析make()并不是一个非常神秘和复杂的过程。从以上源码发现PHP的反射Reflector是一个很好用的技术,这里给出来一个test,看下Reflector能干什么:request=$request;}privatefunctionreflectorMethod1(){}protectedfunctionreflectorMethod2(){}publicfunctionreflectorMethod3(){}}$reflector_class=newReflectionClass(ReflectorTest::class);$methods=$reflector_class->getMethods();$properties=$reflector_class->getProperties();$constructor=$reflector_class->getConstructor();$constructor_parameters=$constructor->getParameters();foreach($constructor_parametersas$constructor_parameter){$dependency=$constructor_parameter->getClass();var_dump($dependency);if($constructor_parameter->isDefaultValueAvailable()){var_dump($构造函数参数->getDefaultValue());}}var_dump($methods);var_dump($properties);var_dump($constructor);var_dump($constructor_parameters);打印结果太长,请勿粘贴。可以看PHP官方文档:Reflector总结:本文学习了Container的核心功能:服务解析的过程,学习了服务依赖是如何自动解析的。遇到好的经验分享一下,到时候见。
