您好,登錄后才能下訂單哦!
這篇文章主要介紹“laravel服務容器的概念是什么”,在日常操作中,相信很多人在laravel服務容器的概念是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”laravel服務容器的概念是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在laravel中,服務容器是一個用于管理類依賴以及實現依賴注入的強有力工具。當應用程序需要使用某一個服務時,服務容器會將服務解析出來,并同時自動解決服務之間的依賴,然后交給應用程序使用。
本教程操作環境:windows7系統、Laravel6版、Dell G3電腦。
什么是服務容器
Laravel 服務容器是一個用于管理類依賴以及實現依賴注入的強有力工具。依賴注入這個名詞表面看起來花哨,實質上是指:通過構造函數,或者某些情況下通過「setter」方法將類依賴「注入」到類中。
Laravel中的功能模塊比如 Route、Eloquent ORM、Request、Response等等等等,實際上都是與核心無關的類模塊提供的,這些類從注冊到實例化,最終被我們所使用,其實都是 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服務容器的概念是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。