說明:本文主要學習Laravel中Container的源碼,主要學習Container的綁定和解析過程,和解析過程當中的依賴解決。分享本身的研究心得,但願對別人有所幫助。實際上Container的綁定主要有三種方式:bind(),singleton(),instance(),且singleton()只是一種'shared' = true的bind(),這些已經在Laravel5.3之IoC Container實例化源碼解析聊過,其實現方法並不複雜。當Service經過Service Provider綁定到Container中後,當須要該Service時,是須要Container幫助自動解析make()。OK,下面聊聊自動解析過程,研究下Container是如何在自動解析Service時解決該Service的依賴問題的。php
開發環境: Laravel5.3 + PHP7 + OS X 10.11
api
在聊解析過程前,先測試下\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可參考這篇:Laravel5.2之基於PHPStorm編輯器的Laravel開發ide
從以上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(),這裏的$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()的源碼:ui
// 這裏$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()的源碼:this
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的依賴是如何被自動解析的。遇到好的心得再分享,到時見。