在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影片教學】
以上是laravel服務容器是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!