laravel服務(wù)容器是什么

laravel中,服務(wù)容器是一個用于管理類依賴以及實現(xiàn)依賴注入的強有力工具。當(dāng)應(yīng)用程序需要使用某一個服務(wù)時,服務(wù)容器會將服務(wù)解析出來,并同時自動解決服務(wù)之間的依賴,然后交給應(yīng)用程序使用。

laravel服務(wù)容器是什么

本教程操作環(huán)境:windows7系統(tǒng)、Laravel6版、Dell G3電腦。

什么是服務(wù)容器

Laravel 服務(wù)容器是一個用于管理類依賴以及實現(xiàn)依賴注入的強有力工具。依賴注入這個名詞表面看起來花哨,實質(zhì)上是指:通過構(gòu)造函數(shù),或者某些情況下通過「setter」方法將類依賴「注入」到類中。

Laravel中的功能模塊比如 Route、Eloquent ORM、Request、Response等等等等,實際上都是與核心無關(guān)的類模塊提供的,這些類從注冊到實例化,最終被我們所使用,其實都是 laravel 的服務(wù)容器負(fù)責(zé)的。

服務(wù)容器中有兩個概念控制反轉(zhuǎn)(IOC)和依賴注入(DI):

依賴注入和控制反轉(zhuǎn)是對同一件事情的不同描述,它們描述的角度不同。依賴注入是從應(yīng)用程序的角度在描述,應(yīng)用程序依賴容器創(chuàng)建并注入它所需要的外部資源。而控制反轉(zhuǎn)是從容器的角度在描述,容器控制應(yīng)用程序,由容器反向的向應(yīng)用程序注入應(yīng)用程序所需要的外部資源。

在Laravel中框架把自帶的各種服務(wù)綁定到服務(wù)容器,我們也可以綁定自定義服務(wù)到容器。當(dāng)應(yīng)用程序需要使用某一個服務(wù)時,服務(wù)容器會將服務(wù)解析出來,同時自動解決服務(wù)之間的依賴,然后交給應(yīng)用程序使用。

下面探討一下Laravel中的服務(wù)綁定和解析是如何實現(xiàn)的。

服務(wù)綁定

常用的綁定服務(wù)到容器的方法有instance, bind, singleton, alias。下面我們分別來看一下。

instance

將一個已存在的對象綁定到服務(wù)容器里,隨后通過名稱解析該服務(wù)時,容器將總返回這個綁定的實例。

$api?=?new?HelpSpotAPI(new?HttpClient); $this->app->instance('HelpSpotApi',?$api);

會把對象注冊到服務(wù)容器的$instnces屬性里

[ ?????'HelpSpotApi'?=>?$api//$api是API類的對象,這里簡寫了 ?]

bind

綁定服務(wù)到服務(wù)容器

有三種綁定方式:

1.綁定自身

$this->app->bind('HelpSpotAPI',?null);

2.綁定閉包

$this->app->bind('HelpSpotAPI',?function?()?{ ????return?new?HelpSpotAPI(); });//閉包直接提供類實現(xiàn)方式 $this->app->bind('HelpSpotAPI',?function?($app)?{ ????return?new?HelpSpotAPI($app->make('HttpClient')); });//閉包返回需要依賴注入的類

3. 綁定接口和實現(xiàn)

$this->app->bind('IlluminateTestsContainerIContainerContractStub',?'IlluminateTestsContainerContainerImplementationStub');

針對第一種情況,其實在bind方法內(nèi)部會在綁定服務(wù)之前通過getClosure()為服務(wù)生成閉包,我們來看一下bind方法源碼。

public?function?bind($abstract,?$concrete?=?null,?$shared?=?false) { ????$abstract?=?$this->normalize($abstract); ???? ????$concrete?=?$this->normalize($concrete); ????//如果$abstract為數(shù)組類似['Illuminate/ServiceName'?=>?'service_alias'] ????//抽取別名"service_alias"并且注冊到$aliases[]中 ????//注意:數(shù)組綁定別名的方式在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,即服務(wù)容器,會在回調(diào)時傳遞給這個變量 ????return?function?($c,?$parameters?=?[])?use?($abstract,?$concrete)?{ ????????$method?=?($abstract?==?$concrete)???'build'?:?'make';  ????????return?$c->$method($concrete,?$parameters); ????}; }

bind把服務(wù)注冊到服務(wù)容器的$bindings屬性里類似這樣:

$bindings?=?[ ????'HelpSpotAPI'?=>??[//閉包綁定 ????????'concrete'?=>?function?($app,?$paramters?=?[])?{ ????????????return?$app->build('HelpSpotAPI'); ????????}, ????????'shared'?=>?false//如果是singleton綁定,這個值為true ????]???????? ????'IlluminateTestsContainerIContainerContractStub'?=>?[//接口實現(xiàn)綁定 ????????'concrete'?=>?'IlluminateTestsContainerContainerImplementationStub', ????????'shared'?=>?false ????] ]

singleton

public?function?singleton($abstract,?$concrete?=?null) { ????$this->bind($abstract,?$concrete,?true); }

singleton 方法是bind方法的變種,綁定一個只需要解析一次的類或接口到容器,然后接下來對于容器的調(diào)用該服務(wù)將會返回同一個實例

alias

把服務(wù)和服務(wù)別名注冊到容器:

public?function?alias($abstract,?$alias) { ????$this->aliases[$alias]?=?$this->normalize($abstract); }

alias 方法在上面講bind方法里有用到過,它會把把服務(wù)別名和服務(wù)類的對應(yīng)關(guān)系注冊到服務(wù)容器的$aliases屬性里。

例如:

$this->app->alias('IlluminateServiceName',?'service_alias');

綁定完服務(wù)后在使用時就可以通過

$this->app->make('service_alias');

將服務(wù)對象解析出來,這樣make的時候就不用寫那些比較長的類名稱了,對make方法的使用體驗上有很大提升。

服務(wù)解析

make: 從服務(wù)容器中解析出服務(wù)對象,該方法接收你想要解析的類名或接口名作為參數(shù)

/** ?*?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)); ????//?如果服務(wù)是通過instance()方式綁定的,就直接解析返回綁定的service ????if?(isset($this->instances[$abstract]))?{ ????????return?$this->instances[$abstract]; ????} ????//?獲取$abstract接口對應(yīng)的$concrete(接口的實現(xiàn)) ????$concrete?=?$this->getConcrete($abstract); ????if?($this->isBuildable($concrete,?$abstract))?{ ????????$object?=?$this->build($concrete,?$parameters); ????}?else?{ ????????//如果時接口實現(xiàn)這種綁定方式,通過接口拿到實現(xiàn)后需要再make一次才能 ????????//滿足isBuildable的條件?($abstract?===?$concrete) ????????$object?=?$this->make($concrete,?$parameters); ????} ????foreach?($this->getExtenders($abstract)?as?$extender)?{ ????????$object?=?$extender($object,?$this); ????} ????//如果服務(wù)是以singleton方式注冊進來的則,把構(gòu)建好的服務(wù)對象放到$instances里, ????//避免下次使用時重新構(gòu)建 ????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之前沒有注冊類實現(xiàn)到服務(wù)容器里,則服務(wù)容器會認(rèn)為$abstract本身就是接口的類實現(xiàn) ????if?(!?isset($this->bindings[$abstract]))?{ ????????return?$abstract; ????} ????return?$this->bindings[$abstract]['concrete']; } protected?function?isBuildable($concrete,?$abstract) {???????? ????return?$concrete?===?$abstract?||?$concrete?instanceof?Closure; }

通過對make方法的梳理我們發(fā)現(xiàn),build方法的職能是構(gòu)建解析出來的服務(wù)的對象的,下面看一下構(gòu)建對象的具體流程。(構(gòu)建過程中用到了php類的反射來實現(xiàn)服務(wù)的依賴注入)

public?function?build($concrete,?array?$parameters?=?[]) { ????//?如果是閉包直接執(zhí)行閉包并返回(對應(yīng)閉包綁定) ????if?($concrete?instanceof?Closure)?{ ????????return?$concrete($this,?$parameters); ????} ???? ????//?使用反射ReflectionClass來對實現(xiàn)類進行反向工程 ????$reflector?=?new?ReflectionClass($concrete); ????//?如果不能實例化,這應(yīng)該是接口或抽象類,再或者就是構(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ù)是空,說明沒有任何依賴,直接new返回 ????if?(is_null($constructor))?{ ????????array_pop($this->buildStack); ????????return?new?$concrete; ????} ???? ????//?獲取構(gòu)造函數(shù)的依賴(形參),返回一組ReflectionParameter對象組成的數(shù)組表示每一個參數(shù) ????$dependencies?=?$constructor->getParameters(); ????$parameters?=?$this->keyParametersByArgument( ????????$dependencies,?$parameters ????); ????//?構(gòu)建構(gòu)造函數(shù)需要的依賴 ????$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參數(shù))已提供 ????????//?$parameter->name返回參數(shù)名 ????????if?(array_key_exists($parameter->name,?$primitives))?{ ????????????$dependencies[]?=?$primitives[$parameter->name]; ????????}? ????????elseif?(is_null($dependency))?{ ?????????????//?參數(shù)的ReflectionClass為null,說明是基本類型,如'int','string' ????????????$dependencies[]?=?$this->resolveNonClass($parameter); ????????}?else?{ ?????????????//?參數(shù)是一個類的對象,?則用resolveClass去把對象解析出來 ????????????$dependencies[]?=?$this->resolveClass($parameter); ????????} ????} ????return?$dependencies; } //解析出依賴類的對象 protected?function?resolveClass(ReflectionParameter?$parameter) { ????try?{ ????????//?$parameter->getClass()->name返回的是類名(參數(shù)在typehint里聲明的類型) ????????//?然后遞歸繼續(xù)make(在make時發(fā)現(xiàn)依賴類還有其他依賴,那么會繼續(xù)make依賴的依賴 ????????//?直到所有依賴都被解決了build才結(jié)束) ????????return?$this->make($parameter->getClass()->name); ????}?catch?(BindingResolutionException?$e)?{ ????????if?($parameter->isOptional())?{ ????????????return?$parameter->getDefaultValue(); ????????} ????????throw?$e; ????} }

服務(wù)容器就是laravel的核心, 它通過依賴注入很好的替我們解決對象之間的相互依賴關(guān)系,而又通過控制反轉(zhuǎn)讓外部來來定義具體的行為(Route, Eloquent這些都是外部模塊,它們自己定義了行為規(guī)范,這些類從注冊到實例化給你使用才是服務(wù)容器負(fù)責(zé)的)。

一個類要被容器所能夠提取,必須要先注冊至這個容器。既然 laravel 稱這個容器叫做服務(wù)容器,那么我們需要某個服務(wù),就得先注冊、綁定這個服務(wù)到容器,那么提供服務(wù)并綁定服務(wù)至容器的東西,就是服務(wù)提供器(ServiceProvider)。服務(wù)提供者主要分為兩個部分,register(注冊) 和 boot(引導(dǎo)、初始化)

【相關(guān)推薦:laravel視頻教程

以上就是

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊13 分享