這篇文章主要介紹了關於Laravel服務容器(IocContainer)的解讀,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
Laravel的核心是IocContainer, 文檔中稱其為“服務容器”,服務容器是一個用於管理類依賴和執行依賴注入的強大工具,Laravel中的功能模組比如 Route、Eloquent ORM、Request、Response等等等等,實際上都是與核心無關的類別模組提供的,這些類從註冊到執行個體化,最終被我們所使用,其實都是 laravel 的服務容器負責的。
如果對服務容器是什麼沒有清晰概念的話推薦一篇博文來瞭解一下服務容器的來龍去脈:laravel神奇的服務容器
服務容器中有兩個概念控制反轉(IOC)和依賴注入(DI):
依賴注入和控制反轉是對同一件事情的不同描述,它們描述的角度不同。依賴注入是從應用程式的角度在描述,應用程式依賴容器建立並注入它所需要的外部資源。而控制反轉是從容器的角度在描述,容器控制應用程式,由容器反向的嚮應用程式注入應用程式所需要的外部資源。
在Laravel中架構把內建的各種服務綁定到服務容器,我們也可以綁定自訂服務到容器。當應用程式需要使用某一個服務時,服務容器會講服務解析出來同時自動解決服務之間的依賴然後交給應用程式使用。
本篇就來探討一下Laravel中的服務綁定和解析是如何?的
服務綁定
常用的綁定服務到容器的方法有instance, bind, singleton, alias。下面我們分別來看一下。
instance
將一個已存在的對象綁定到服務容器裡,隨後通過名稱解析該服務時,容器將總返回這個綁定的執行個體。
$api = new HelpSpot\API(new HttpClient);$this->app->instance('HelpSpot\Api', $api);
會把對象註冊到服務容器的$instnces屬性裡
[ 'HelpSpot\Api' => $api//$api是API類的對象,這裡簡寫了 ]
bind
綁定服務到服務容器
有三種綁定方式:
1.綁定自身$this->app->bind('HelpSpot\API', null);2.綁定閉包$this->app->bind('HelpSpot\API', function () { return new HelpSpot\API();});//閉包直接提供類實現方式$this->app->bind('HelpSpot\API', function ($app) { return new HelpSpot\API($app->make('HttpClient'));});//閉包返回需要依賴注入的類3. 綁定介面和實現$this->app->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');
針對第一種情況,其實在bind方法內部會在綁定服務之前通過getClosure()
為服務產生閉包,我們來看一下bind方法源碼。
public function bind($abstract, $concrete = null, $shared = false){ $abstract = $this->normalize($abstract); $concrete = $this->normalize($concrete); //如果$abstract為數組類似['Illuminate/ServiceName' => 'service_alias'] //抽取別名"service_alias"並且註冊到$aliases[]中 //注意:數組綁定別名的方式在5.4中被移除,別名綁定請使用下面的alias方法 if (is_array($abstract)) { list($abstract, $alias) = $this->extractAlias($abstract); $this->alias($abstract, $alias); } $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } //如果只提供$abstract,則在這裡為其產生concrete閉包 if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); if ($this->resolved($abstract)) { $this->rebound($abstract); }}protected function getClosure($abstract, $concrete){ // $c 就是$container,即服務容器,會在回調時傳遞給這個變數 return function ($c, $parameters = []) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; return $c->$method($concrete, $parameters); };}
bind把服務註冊到服務容器的$bindings屬性裡類似這樣:
$bindings = [ 'HelpSpot\API' => [//閉包綁定 'concrete' => function ($app, $paramters = []) { return $app->build('HelpSpot\API'); }, 'shared' => false//如果是singleton綁定,這個值為true ] 'Illuminate\Tests\Container\IContainerContractStub' => [//介面實現綁定 'concrete' => 'Illuminate\Tests\Container\ContainerImplementationStub', 'shared' => false ]]
singleton
public function singleton($abstract, $concrete = null){ $this->bind($abstract, $concrete, true);}
singleton 方法是bind方法的變種,綁定一個只需要解析一次的類或介面到容器,然後接下來對於容器的調用該服務將會返回同一個執行個體
alias
把服務和服務別名註冊到容器:
public function alias($abstract, $alias){ $this->aliases[$alias] = $this->normalize($abstract);}
alias 方法在上面講bind方法裡有用到過,它會把把服務別名和服務類的對應關係註冊到服務容器的$aliases屬性裡。
例如:
$this->app->alias('\Illuminate\ServiceName', 'service_alias');
綁定完服務後在使用時就可以通過
$this->app->make('service_alias');
將服務物件解析出來,這樣make的時候就不用寫那些比較長的類名稱了,對make方法的使用體驗上有很大提升。
服務解析
make: 從服務容器中解析出服務物件,該方法接收你想要解析的類名或介面名作為參數
/** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed */public function make($abstract, array $parameters = []){ //getAlias方法會假定$abstract是綁定的別名,從$aliases找到映射的真實類型名 //如果沒有映射則$abstract即為真實類型名,將$abstract原樣返回 $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 { //如果時介面實現這種綁定方式,通過介面拿到實現後需要再make一次才能 //滿足isBuildable的條件 ($abstract === $concrete) $object = $this->make($concrete, $parameters); } foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } //如果服務是以singleton方式註冊進來的則,把構建好的服務物件放到$instances裡, //避免下次使用時重新構建 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; } // 如果是$abstract之前沒有註冊類實現到服務容器裡,則服務容器會認為$abstract本身就是介面的類實現 if (! isset($this->bindings[$abstract])) { return $abstract; } return $this->bindings[$abstract]['concrete'];}protected function isBuildable($concrete, $abstract){ return $concrete === $abstract || $concrete instanceof Closure;}
通過對make方法的梳理我們發現,build方法的職能是構建解析出來的服務的對象的,下面看一下構建對象的具體流程。(構建過程中用到了PHP類的反射來實現服務的依賴注入)
public function build($concrete, array $parameters = []){ // 如果是閉包直接執行閉包並返回(對應閉包綁定) if ($concrete instanceof Closure) { return $concrete($this, $parameters); } // 使用反射ReflectionClass來對實作類別進行反向工程 $reflector = new ReflectionClass($concrete); // 如果不能執行個體化,這應該是介面或抽象類別,再或者就是建構函式是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 ); // 構建建構函式需要的依賴 $instances = $this->getDependencies( $dependencies, $parameters ); array_pop($this->buildStack); return $reflector->newInstanceArgs($instances);}//擷取依賴protected function getDependencies(array $parameters, array $primitives = []){ $dependencies = []; foreach ($parameters as $parameter) { $dependency = $parameter->getClass(); // 某一依賴值在$primitives中(即build方法的$parameters參數)已提供 // $parameter->name返回參數名 if (array_key_exists($parameter->name, $primitives)) { $dependencies[] = $primitives[$parameter->name]; } elseif (is_null($dependency)) { // 參數的ReflectionClass為null,說明是基本類型,如'int','string' $dependencies[] = $this->resolveNonClass($parameter); } else { // 參數是一個類的對象, 則用resolveClass去把對象解析出來 $dependencies[] = $this->resolveClass($parameter); } } return $dependencies;}//解析出依賴類的對象protected function resolveClass(ReflectionParameter $parameter){ try { // $parameter->getClass()->name返回的是類名(參數在typehint裡聲明的類型) // 然後遞迴繼續make(在make時發現依賴類還有其他依賴,那麼會繼續make依賴的依賴 // 直到所有依賴都被解決了build才結束) return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; }}
服務容器就是laravel的核心, 它通過依賴注入很好的替我們解決對象之間的相互依賴關係,而又通過控制反轉讓外部來來定義具體的行為(Route, Eloquent這些都是外部模組,它們自己定義了行為規範,這些類從註冊到執行個體化給你使用才是服務容器負責的)。
一個類要被容器所能夠提取,必須要先註冊至這個容器。既然 laravel 稱這個容器叫做服務容器,那麼我們需要某個服務,就得先註冊、綁定這個服務到容器,那麼提供服務並綁定服務至容器的東西,就是服務提供器(ServiceProvider)。服務提供者主要分為兩個部分,register(註冊) 和 boot(引導、初始化)由於篇幅問題關於Laravel服務提供器的內容請看另一篇Laravel核心解讀--服務提供器(ServiceProvider)。
以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!