IT科技类资讯

Laravel学习笔记之Container源码解析

时间:2010-12-5 17:23:32  作者:域名   来源:人工智能  查看:  评论:0
内容摘要:说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑定主

说明:本文主要学习Laravel中Container的学习笔析源码,主要学习Container的码解绑定和解析过程,和解析过程中的学习笔析依赖解决。分享自己的码解研究心得,希望对别人有所帮助。学习笔析实际上Container的码解绑定主要有三种方式:bind(),singleton(),instance(),且singleton()只是一种shared = true的bind(),这些已经在Laravel学习笔记之IoC Container实例化源码解析聊过,学习笔析其实现方法并不复杂。码解当Service通过Service Provider绑定到Container中后,学习笔析当需要该Service时,码解是学习笔析需要Container帮助自动解析make()。OK,码解下面聊聊自动解析过程,研究下Container是学习笔析如何在自动解析Service时解决该Service的依赖问题的。

开发环境: Laravel5.3 + PHP7 + OS X 10.11

PHPUnit测试下绑定

在聊解析过程前,码解先测试下\Illuminate\Container\Container中绑定的学习笔析源码,这里测试下bind(),singleton(),instance()三个绑定方式: 

<?php namespace MyRightCapital\Container\Tests; use MyRightCapital\Container\Container; class ContainerBindTest extends \PHPUnit_Framework_TestCase {      /**      * @var Container $container      */     protected $container;     public function setUp()     {          $this->container = new Container();     }     public function testBindClosure()     {          // Arrange         $expected = Laravel is a PHP Framework.;         $this->container->bind(PHP, function () use ($expected) {              return $expected;         });         // Actual         $actual = $this->container->make(PHP);         // Assert         $this->assertEquals($expected, $actual);     }     public function testBindInterfaceToImplement()     {          // Arrange         $this->container->bind(IContainerStub::class, ContainerImplementationStub::class);         // Actual         $actual = $this->container->make(IContainerStub::class);         // Assert         $this->assertInstanceOf(IContainerStub::class, $actual);     }     public function testBindDependencyResolution()     {          // 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);     }     public function testSingleton()     {          // Arrange         $this->container->singleton(ContainerConcreteStub::class);         $expected = $this->container->make(ContainerConcreteStub::class);         // Actual         $actual = $this->container->make(ContainerConcreteStub::class);         // Assert         $this->assertSame($expected, $actual);     }     public function testInstanceExistingObject()     {          // Arrange         $expected = new ContainerImplementationStub();         $this->container->instance(IContainerStub::class, $expected);         // Actual         $actual = $this->container->make(IContainerStub::class);         // Assert         $this->assertSame($expected, $actual);     } } class ContainerConcreteStub {  } interface IContainerStub {  } class ContainerImplementationStub implements IContainerStub {  } class ContainerDependentStub {      /**      * @var \MyRightCapital\Container\Tests\IContainerStub      */     public $containerStub;     public function __construct(IContainerStub $containerStub)     {          $this->containerStub = $containerStub;     } } class ContainerNestedDependentStub {      /**      * @var \MyRightCapital\Container\Tests\ContainerDependentStub      */     public $containerDependentStub;     public function __construct(ContainerDependentStub $containerDependentStub)     {          $this->containerDependentStub = $containerDependentStub;     } } 

 这里测试了bind()绑定闭包,绑定接口和对应实现,依赖解析这三个feature,singleton()测试了是否为单例绑定一个feature,instance()测试了已存在对象绑定这个feature,测试结果5个tests都通过:

 关于在PHPStorm中配置PHPUnit可参考这篇:Laravel学习笔记之基于PHPStorm编辑器的云服务器提供商Laravel开发

make()源码解析

从以上testcase知道,make()是负责从Container中解析出service的,而且在testBindDependencyResolution()这个test中,还能发现当ContainerNestedDependentStub::class有构造依赖时,Container也会自动去解析这个依赖并注入ContainerNestedDependentStub::class的构造函数中,这个依赖是ContainerDependentStub::class,而这个依赖又有自己的依赖IContainerStub::class,从断言语句$this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub);知道,Container又自动解析了这个依赖,所有这一切都不需要我们手动去解析,全都是Container自动化解析的。

这一切Container是怎么做到的?实际上并不复杂,解决依赖只是用了PHP的Reflector反射机制来实现的。先看下make()源码: 

/**      * Resolve the given type from the container.      *      * @param  string  $abstract      * @param  array   $parameters      * @return mixed      */     public function make($abstract, array $parameters = [])     {          $abstract = $this->getAlias($this->normalize($abstract));         // 如果是instance()绑定的方式,就直接解析返回绑定的service         if (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 $extender) {              $object = $extender($object, $this);         }         if ($this->isShared($abstract)) {              $this->instances[$abstract] = $object;         }         $this->fireResolvingCallbacks($abstract, $object);         $this->resolved[$abstract] = true;         return $object;     }     protected function getConcrete($abstract)     {          if (! is_null($concrete = $this->getContextualConcrete($abstract))) {              return $concrete;         }         // 如果是$this->container->singleton(ContainerConcreteStub::class);这种方式绑定,即$concrete = null         // 则 $abstract = $concrete,高防服务器可看以上PHPUnit的testSingleton()这个test         // 这种方式称为自动补全绑定         if (! isset($this->bindings[$abstract])) {              return $abstract;         }         return $this->bindings[$abstract][concrete];     }     protected function isBuildable($concrete, $abstract)     {          return $concrete === $abstract || $concrete instanceof Closure;     }  

从以上源码可知道如果绑定的是闭包或者自动补全绑定($concrete = null),则需要build()这个闭包或类名,转换成对应的实例。如果是接口实现这种方式绑定,则需要再一次调用make()并经过getConcrete后$abstract = $concrete,然后符合isBuildable()的条件,进入build()函数内。所以以上的PHPUnit的测试用例中不管什么方式的绑定,都要进入build()函数内编译出相应对象实例。当编译出对象后,检查是否是共享的,以及是否要触发回调,以及标记该对象已经被解析。OK,看下build()的源码: 

/**      * Instantiate a concrete instance of the given type.      *      * @param  string  $concrete      * @param  array   $parameters      * @return mixed      *      * @throws \Illuminate\Contracts\Container\BindingResolutionException      */     public function build($concrete, array $parameters = [])     {          // 如果是闭包直接执行闭包并返回,e.g. PHPUnit的这个test:testBindClosure()         if ($concrete instanceof Closure) {              return $concrete($this, $parameters);         }         // 如这个test:testBindInterfaceToImplement(),这里的云南idc服务商$concrete = ContainerImplementationStub::class类名称,         // 则使用反射ReflectionClass来探测ContainerImplementationStub这个类的构造函数和构造函数的依赖         $reflector = new ReflectionClass($concrete);         // 如果ContainerImplementationStub不能实例化,这应该是接口或抽象类,再或者就是ContainerImplementationStub的构造函数是private的         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);         }         $this->buildStack[] = $concrete;         // 获取构造函数的反射         $constructor = $reflector->getConstructor();         // 如果构造函数是空,说明没有任何依赖,直接new返回         if (is_null($constructor)) {              array_pop($this->buildStack);             return new $concrete;         }         // 获取构造函数的依赖,返回ReflectionParameter[]         $dependencies = $constructor->getParameters();         $parameters = $this->keyParametersByArgument(             $dependencies, $parameters         );         // 然后就是获取相关依赖,如testBindDependencyResolution()这个test中,         // ContainerNestedDependentStub::class是依赖于ContainerDependentStub::class的         $instances = $this->getDependencies(             $dependencies, $parameters         );         array_pop($this->buildStack);         return $reflector->newInstanceArgs($instances);     }  

从源码可知道,比较麻烦的是当ContainerNestedDependentStub::class的构造函数有依赖ContainerDependentStub::class时,通过getDependencies()来解决的,看下getDependencies()的源码: 

// 这里$parameters = ReflectionParameter[]     protected function getDependencies(array $parameters, array $primitives = [])     {          $dependencies = [];         foreach ($parameters as $parameter) {              $dependency = $parameter->getClass();             // 如果某一依赖值已给,就赋值             if (array_key_exists($parameter->name, $primitives)) {                  $dependencies[] = $primitives[$parameter->name];             }              // 如果类名为null,说明是基本类型,如int,string and so on.             elseif (is_null($dependency)) {                  $dependencies[] = $this->resolveNonClass($parameter);             }              // 如果是类名,如ContainerDependentStub::class,则resolveClass去解析成对象             else {                  $dependencies[] = $this->resolveClass($parameter);             }         }         return $dependencies;     }  

通过上面注释,看下resolveClass()的源码: 

protected function resolveClass(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()结束            return $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() and build(),发现B类又同时依赖于D类,再make() and build(),以此类推直到没有依赖或依赖基本属性,解析结束。这样一步步解析完后,发现Container的解析make()并不是很神秘很复杂中的过程。

从以上源码发现PHP的反射Reflector是个很好用的技术,这里给出个test,看下Reflector能干些啥: 

<?php class ConstructorParameter {  } class ReflectorTest {      private $refletorProperty1;     protected $refletorProperty2;     public $refletorProperty3;     /**      * @var int      */     private $request;     public function __construct(int $request = 10, string $response, ConstructorParameter $constructorParameter, Closure $closure)     {          $this->request = $request;     }     private function reflectorMethod1()     {      }     protected function reflectorMethod2()     {      }     public function reflectorMethod3()     {      } } $reflector_class        = new ReflectionClass(ReflectorTest::class); $methods                = $reflector_class->getMethods(); $properties             = $reflector_class->getProperties(); $constructor            = $reflector_class->getConstructor(); $constructor_parameters = $constructor->getParameters(); foreach ($constructor_parameters as $constructor_parameter) {      $dependency = $constructor_parameter->getClass();     var_dump($dependency);     if ($constructor_parameter->isDefaultValueAvailable()) {          var_dump($constructor_parameter->getDefaultValue());     } } var_dump($methods); var_dump($properties); var_dump($constructor); var_dump($constructor_parameters);  

打印结果太长了,就不粘贴了。可以看下PHP官方文档:Reflector

总结:本文学习了下Container的核心功能:service resolve的过程,并学习了service的依赖是如何被自动解析的。遇到好的心得再分享,到时见。

copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap