Laravel學(xué)習(xí)筆記之Container源碼解析
說(shuō)明:本文主要學(xué)習(xí)Laravel中Container的源碼,主要學(xué)習(xí)Container的綁定和解析過(guò)程,和解析過(guò)程中的依賴解決。分享自己的研究心得,希望對(duì)別人有所幫助。實(shí)際上Container的綁定主要有三種方式:bind(),singleton(),instance(),且singleton()只是一種'shared' = true的bind(),這些已經(jīng)在Laravel學(xué)習(xí)筆記之IoC Container實(shí)例化源碼解析聊過(guò),其實(shí)現(xiàn)方法并不復(fù)雜。當(dāng)Service通過(guò)Service Provider綁定到Container中后,當(dāng)需要該Service時(shí),是需要Container幫助自動(dòng)解析make()。OK,下面聊聊自動(dòng)解析過(guò)程,研究下Container是如何在自動(dòng)解析Service時(shí)解決該Service的依賴問(wèn)題的。
開(kāi)發(fā)環(huán)境: Laravel5.3 + PHP7 + OS X 10.11
PHPUnit測(cè)試下綁定
在聊解析過(guò)程前,先測(cè)試下\Illuminate\Container\Container中綁定的源碼,這里測(cè)試下bind(),singleton(),instance()三個(gè)綁定方式:
- <?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;
- }
- }
這里測(cè)試了bind()綁定閉包,綁定接口和對(duì)應(yīng)實(shí)現(xiàn),依賴解析這三個(gè)feature,singleton()測(cè)試了是否為單例綁定一個(gè)feature,instance()測(cè)試了已存在對(duì)象綁定這個(gè)feature,測(cè)試結(jié)果5個(gè)tests都通過(guò):
關(guān)于在PHPStorm中配置PHPUnit可參考這篇:Laravel學(xué)習(xí)筆記之基于PHPStorm編輯器的Laravel開(kāi)發(fā)
make()源碼解析
從以上testcase知道,make()是負(fù)責(zé)從Container中解析出service的,而且在testBindDependencyResolution()這個(gè)test中,還能發(fā)現(xiàn)當(dāng)ContainerNestedDependentStub::class有構(gòu)造依賴時(shí),Container也會(huì)自動(dòng)去解析這個(gè)依賴并注入ContainerNestedDependentStub::class的構(gòu)造函數(shù)中,這個(gè)依賴是ContainerDependentStub::class,而這個(gè)依賴又有自己的依賴IContainerStub::class,從斷言語(yǔ)句$this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub);知道,Container又自動(dòng)解析了這個(gè)依賴,所有這一切都不需要我們手動(dòng)去解析,全都是Container自動(dòng)化解析的。
這一切Container是怎么做到的?實(shí)際上并不復(fù)雜,解決依賴只是用了PHP的Reflector反射機(jī)制來(lái)實(shí)現(xiàn)的。先看下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對(duì)應(yīng)綁定的$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()這個(gè)test
- // 這種方式稱為'自動(dòng)補(bǔ)全'綁定
- if (! isset($this->bindings[$abstract])) {
- return $abstract;
- }
- return $this->bindings[$abstract]['concrete'];
- }
- protected function isBuildable($concrete, $abstract)
- {
- return $concrete === $abstract || $concrete instanceof Closure;
- }
從以上源碼可知道如果綁定的是閉包或者'自動(dòng)補(bǔ)全'綁定($concrete = null),則需要build()這個(gè)閉包或類名,轉(zhuǎn)換成對(duì)應(yīng)的實(shí)例。如果是'接口實(shí)現(xiàn)'這種方式綁定,則需要再一次調(diào)用make()并經(jīng)過(guò)getConcrete后$abstract = $concrete,然后符合isBuildable()的條件,進(jìn)入build()函數(shù)內(nèi)。所以以上的PHPUnit的測(cè)試用例中不管什么方式的綁定,都要進(jìn)入build()函數(shù)內(nèi)編譯出相應(yīng)對(duì)象實(shí)例。當(dāng)編譯出對(duì)象后,檢查是否是共享的,以及是否要觸發(fā)回調(diào),以及標(biāo)記該對(duì)象已經(jīng)被解析。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 = [])
- {
- // 如果是閉包直接執(zhí)行閉包并返回,e.g. PHPUnit的這個(gè)test:testBindClosure()
- if ($concrete instanceof Closure) {
- return $concrete($this, $parameters);
- }
- // 如這個(gè)test:testBindInterfaceToImplement(),這里的$concrete = ContainerImplementationStub::class類名稱,
- // 則使用反射ReflectionClass來(lái)探測(cè)ContainerImplementationStub這個(gè)類的構(gòu)造函數(shù)和構(gòu)造函數(shù)的依賴
- $reflector = new ReflectionClass($concrete);
- // 如果ContainerImplementationStub不能實(shí)例化,這應(yīng)該是接口或抽象類,再或者就是ContainerImplementationStub的構(gòu)造函數(shù)是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;
- // 獲取構(gòu)造函數(shù)的反射
- $constructor = $reflector->getConstructor();
- // 如果構(gòu)造函數(shù)是空,說(shuō)明沒(méi)有任何依賴,直接new返回
- if (is_null($constructor)) {
- array_pop($this->buildStack);
- return new $concrete;
- }
- // 獲取構(gòu)造函數(shù)的依賴,返回ReflectionParameter[]
- $dependencies = $constructor->getParameters();
- $parameters = $this->keyParametersByArgument(
- $dependencies, $parameters
- );
- // 然后就是獲取相關(guān)依賴,如testBindDependencyResolution()這個(gè)test中,
- // ContainerNestedDependentStub::class是依賴于ContainerDependentStub::class的
- $instances = $this->getDependencies(
- $dependencies, $parameters
- );
- array_pop($this->buildStack);
- return $reflector->newInstanceArgs($instances);
- }
從源碼可知道,比較麻煩的是當(dāng)ContainerNestedDependentStub::class的構(gòu)造函數(shù)有依賴ContainerDependentStub::class時(shí),通過(guò)getDependencies()來(lái)解決的,看下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,說(shuō)明是基本類型,如'int','string' and so on.
- elseif (is_null($dependency)) {
- $dependencies[] = $this->resolveNonClass($parameter);
- }
- // 如果是類名,如ContainerDependentStub::class,則resolveClass去解析成對(duì)象
- else {
- $dependencies[] = $this->resolveClass($parameter);
- }
- }
- return $dependencies;
- }
通過(guò)上面注釋,看下resolveClass()的源碼:
- protected function resolveClass(ReflectionParameter $parameter)
- {
- try {
- // $parameter->getClass()->name返回的是類名,如ContainerNestedDependentStub依賴于$containerDependentStub
- // $containerDependentStub的typehint是ContainerDependentStub,所以類名是'ContainerDependentStub'
- // 然后遞歸繼續(xù)make(ContainerDependentStub::class)
- // 又和PHPUnit中這個(gè)測(cè)試$this->container->make(ContainerNestedDependentStub::class)相類似了
- // ContainerNestedDependentStub又依賴于IContainerStub::class,
- // IContainerStub::class是綁定于ContainerImplementationStub::class
- // 直到ContainerImplementationStub沒(méi)有依賴或者是構(gòu)造函數(shù)是基本屬性,
- // ***build()結(jié)束
- return $this->make($parameter->getClass()->name);
- } catch (BindingResolutionException $e) {
- if ($parameter->isOptional()) {
- return $parameter->getDefaultValue();
- }
- throw $e;
- }
- }
從以上代碼注釋直到build()是個(gè)遞歸過(guò)程,A類依賴于B類,B類依賴于C類和D類,那就從A類開(kāi)始build,發(fā)現(xiàn)依賴于B類,再?gòu)腃ontainer中解析make()即再build()出B類,發(fā)現(xiàn)依賴于C類,再make() and build(),發(fā)現(xiàn)B類又同時(shí)依賴于D類,再make() and build(),以此類推直到?jīng)]有依賴或依賴基本屬性,解析結(jié)束。這樣一步步解析完后,發(fā)現(xiàn)Container的解析make()并不是很神秘很復(fù)雜中的過(guò)程。
從以上源碼發(fā)現(xiàn)PHP的反射Reflector是個(gè)很好用的技術(shù),這里給出個(gè)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);
打印結(jié)果太長(zhǎng)了,就不粘貼了??梢钥聪翽HP官方文檔:Reflector
總結(jié):本文學(xué)習(xí)了下Container的核心功能:service resolve的過(guò)程,并學(xué)習(xí)了service的依賴是如何被自動(dòng)解析的。遇到好的心得再分享,到時(shí)見(jiàn)。