摘要:而函數作用是加載延遲服務,與容器解析關系不大,我們放在以后再說。在構造之前,服務容器會先把放入中,繼而再去解析。利用服務容器解析依賴的參數。
make解析
首先歡迎關注我的博客: www.leoyang90.cn
服務容器對對象的自動解析是服務容器的核心功能,make 函數、build 函數是實例化對象重要的核心,先大致看一下代碼:
public function make($abstract) { $abstract = $this->getAlias($abstract); if (isset($this->deferredServices[$abstract])) { $this->loadDeferredProvider($abstract); } return parent::make($abstract); }
public function make($abstract) { return $this->resolve($abstract); } public function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) ); // If an instance of the type is currently being managed as a singleton we"ll // just return an existing instance instead of instantiating new instances // so the developer can keep using the same objects instance every time. if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $concrete = $this->getConcrete($abstract); // We"re ready to instantiate an instance of the concrete type registered for // the binding. This will instantiate the types, as well as resolve any of // its "nested" dependencies recursively until all have gotten resolved. if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } // If we defined any extenders for this type, we"ll need to spin through them // and apply them to the object being built. This allows for the extension // of services, such as changing configuration or decorating the object. foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // If the requested type is registered as a singleton we"ll want to cache off // the instances in "memory" so we can return it later without creating an // entirely new instance of an object on each subsequent request for it. if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); $this->resolved[$abstract] = true; return $object; }
在講解解析流程之前,我們先說說使用make函數進行解析的分類:
我們詳細的講一下上圖。這里我把使用make函數進行解析的情況分為大致兩種:
解析對象沒有綁定過任何類,例如:
$app->make("AppHttpControllersHomeController");
解析對象綁定過實現類
對于綁定過實現類的對象可以分為兩種:
綁定的是類名,例如:
$app->when("AppHttpControllersHomeController") ->needs("AppHttpRequestsInRequest") ->give("AppHttpRequestsTestsRequest");
綁定的是閉包
對于綁定的是閉包的又可以分為:
用戶綁定閉包,例如:
$app->singleton("auth",function($app){ return new AuthManager($app) });//對象類直接實現方法 $app->singleton(EloquentFactory::class, function ($app) { return EloquentFactory::construct( $app->make(FakerGenerator::class), database_path("factories") );//對象類依賴注入 });
服務容器外包一層閉包函數(make/build),例如:
$app->singleton( IlluminateContractsHttpKernel::class, AppHttpKernel::class );//包裝make $app->singleton( AppConSoleKernel::class, );//包裝build
我們在這里先大致講一下服務容器解析的流程,值得注意的是其中 build 函數有可能會遞歸調用 make:
獲取服務名稱。
加載延遲服務。判斷當前的接口是否是延遲服務提供者,若是延遲服務提供者,那么還要對服務提供者進行注冊與啟動操作。
解析單例。如果接口服務是已經被解析過的單例對象,而且并非上下文綁定,那么直接取出對象。
獲取注冊的實現。實現方式可能是上下文綁定的,也可能是 binding 數組中的閉包,也有可能就是接口本身。
build 解析。首先判斷是否需要遞歸。是,則遞歸 make;否,則調用 build 函數;直到調用 build 為止
執行擴展。若當前解析對象存在擴展,運行擴展函數。
創造單例對象。若 shared 為真,且不存在上下文綁定,則放入單例數組中
回調
標志解析
下面我們開始詳細分解代碼邏輯。由于 getAlias 函數已經在 上一篇 講過,這里不會再說。而loadDeferredProvider 函數作用是加載延遲服務,與容器解析關系不大,我們放在以后再說。
獲取注冊的實現獲取解析類的真正實現,函數優先去獲取上下文綁定的實現,否則獲取 binding 數組中的實現,獲取不到就是直接返回自己作為實現:
protected function getConcrete($abstract) { if (! is_null($concrete = $this->getContextualConcrete($abstract))) { return $concrete; } if (isset($this->bindings[$abstract])) { return $this->bindings[$abstract]["concrete"]; } return $abstract; }
一般來說,上下文綁定的服務是通過依賴注入來實現的:
$this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk("local"); }); class PhotoController{ protected $file; public function __construct(Filesystem $file){ $this->file = $file; } }
服務容器會在解析 PhotoController 的時候,通過放射獲取參數類型 Filesystem,并且會把 Filesystem 自動解析為 Storage::disk("local")。如何實現的呢?首先,從 上一篇 文章我們知道,當進行上下文綁定的時候,實際上是維護 contextual 數組,通過上下文綁定,這個數組中存在:
contextual[PhotoController][Filesystem] = function () { return Storage::disk("local"); }
若是服務容器試圖構造 PhotoController 類,那么由于其構造函數依賴于 Filesystem,所以容器必須先生成 Filesystem 類,然后再注入到 PhotoController 中。
在構造 Filesystem 之前,服務容器會先把 PhotoController 放入 buildStack 中,繼而再去解析 Filesystem。
解析 Filesystem 時,運行 getContextualConcrete 函數:
protected function getContextualConcrete($abstract) { if (! is_null($binding = $this->findInContextualBindings($abstract))) { return $binding; } if (empty($this->abstractAliases[$abstract])) { return; } foreach ($this->abstractAliases[$abstract] as $alias) { if (! is_null($binding = $this->findInContextualBindings($alias))) { return $binding; } } } protected function findInContextualBindings($abstract) { if (isset($this->contextual[end($this->buildStack)][$abstract])) { return $this->contextual[end($this->buildStack)][$abstract]; } }
從上面可以看出,getContextualConcrete 函數把當前解析的類(Filesystem)作為 abstract,buildStack 最后一個類(PhotoController)作為 concrete,尋找 this->contextual[concrete] [abstract] (contextual[PhotoController] [Filesystem])中的值,在這個例子里面這個數組值就是那個匿名函數。
build 解析對于服務容器來說,綁定是可以遞歸的,例如:
$app->bind("a","b"); $app->bind("b","c"); $app->bind("c",function(){ return new C; })
遇到這樣的情況,bind 綁定中 getClosure 函數開始發揮作用,該函數會給類包一層閉包,閉包內調用 make 函數,服務容器會不斷遞歸調用 make 函數,直到最后一層,也就是綁定 c 的匿名函數。但是另一方面,有一些綁定方式并沒有調用 bind 函數,例如上下文綁定 context:
$this->app->when(E::class) ->needs(F::class) ->give(A::class);
當make(E::class)的時候,getConcrete 返回 A 類,而不是調用 make 函數的閉包,所以并不會啟動遞歸流程得到 C 的匿名函數,所以造成 A 類完全無法解析,isBuildable 函數就是解決這種問題的,當發現需要解析構造的對象很有可能是遞歸的,那么就遞歸調用 make 函數,否則才會調用build。
... if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } ... protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; }執行擴展
獲取擴展閉包,并運行擴展函數:
protected function getExtenders($abstract) { $abstract = $this->getAlias($abstract); if (isset($this->extenders[$abstract])) { return $this->extenders[$abstract]; } return []; }回調
先后啟動全局的解析事件回調函數,再啟動針對類型的事件回調函數:
protected function fireResolvingCallbacks($abstract, $object) { $this->fireCallbackArray($object, $this->globalResolvingCallbacks); $this->fireCallbackArray( $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) ); $this->fireAfterResolvingCallbacks($abstract, $object); } protected function getCallbacksForType($abstract, $object, array $callbacksPerType) { $results = []; foreach ($callbacksPerType as $type => $callbacks) { if ($type === $abstract || $object instanceof $type) { $results = array_merge($results, $callbacks); } } return $results; } protected function fireAfterResolvingCallbacks($abstract, $object) { $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks); $this->fireCallbackArray( $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks) );build 解析
make 函數承擔了解析的大致框架,build 主要的職責就是利用反射將類構造出來,先看看主要代碼:
public function build($concrete) { // If the concrete type is actually a Closure, we will just execute it and // hand back the results of the functions, which allows functions to be // used as resolvers for more fine-tuned resolution of these objects. if ($concrete instanceof Closure) { return $concrete($this, $this->getLastParameterOverride()); } $reflector = new ReflectionClass($concrete); // If the type is not instantiable, the developer is attempting to resolve // an abstract type such as an Interface of Abstract Class and there is // no binding registered for the abstractions so we need to bail out. if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } $this->buildStack[] = $concrete; $constructor = $reflector->getConstructor(); // If there are no constructors, that means there are no dependencies then // we can just resolve the instances of the objects right away, without // resolving any other types or dependencies out of these containers. if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } $dependencies = $constructor->getParameters(); // Once we have all the constructor"s parameters we can create each of the // dependency instances and then use the reflection instances to make a // new instance of this class, injecting the created dependencies in. $instances = $this->resolveDependencies( $dependencies ); array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }
我們下面詳細的說一下各個部分:
閉包函數執行if ($concrete instanceof Closure) { return $concrete($this, $this->getLastParameterOverride()); }
這段代碼很簡單,但是作用很大。前面說過閉包函數有很多種類:
用戶綁定時提供的直接提供實現類的方式:
$app->singleton("auth",function($app){ return new AuthManager($app) });//對象類直接實現方法
這種情況 concrete(this) 直接就可以解析構造出具體實現類,服務容器解析完畢。
用戶綁定時提供的帶有依賴注入的實現:
$app->singleton(EloquentFactory::class, function ($app) { return EloquentFactory::construct( $app->make(FakerGenerator::class), database_path("factories") );//對象類依賴注入
這種情況下,concrete(this) 會轉而去解析 FakerGenerator::class,遞歸調用 make 函數。
bind函數使用 getClosure 包裝而來:
function($container, $parameters = []){ method = make/build; return $container->$method($concrete, $parameters); }
這種情況,concrete(this) 將會繼續遞歸調用 make 或者 build。
反射當 build 的參數是類名而不是閉包的時候,就要利用反射構建類對象,如果構建的類對象不需要依賴任何其他參數,那么:
$reflector = new ReflectionClass($concrete); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { return new $concrete; }
如果需要依賴注入,那么就要用反射機制來獲取 __construct 函數所需要注入的依賴,如果在make的時候帶入參數值,那么直接利用傳入的參數值;如果依賴是類對像,那么遞歸調用 make 函數;如果依賴是變量值,那么就從上下文中或者參數默認值中去獲取:
... $dependencies = $constructor->getParameters(); $instances = $this->resolveDependencies($dependencies); ... protected function resolveDependencies(array $dependencies) { $results = []; foreach ($dependencies as $dependency) { if ($this->hasParameterOverride($dependency)) { $results[] = $this->getParameterOverride($dependency); continue; } $results[] = is_null($class = $dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } return $results; }
解析變量值參數,如果變量值在上下文綁定中設置過,則去取上下文綁定的值,否則通過反射去取參數默認值,如果沒有默認值,那么就要終止報錯:
protected function resolvePrimitive(ReflectionParameter $parameter) { if (! is_null($concrete = $this->getContextualConcrete("$".$parameter->name))) { return $concrete instanceof Closure ? $concrete($this) : $concrete; } if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } $this->unresolvablePrimitive($parameter); } protected function hasParameterOverride($dependency) { return array_key_exists( $dependency->name, $this->getLastParameterOverride() ); } protected function getParameterOverride($dependency) { return $this->getLastParameterOverride()[$dependency->name]; } protected function getLastParameterOverride() { return count($this->with) ? end($this->with) : []; }
解析類參數,利用服務容器進行依賴注入:
protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }buildstack 解析棧
值的注意的是服務容器里面有個 buildStack,每次利用反射對參數進行依賴注入的時候,都要向這個數組中壓入當前的解析對象,前面說過這部分是為了上下文綁定而設計的:
... $this->buildStack[] = $concrete;//壓入數組棧中 ... $instances = $this->resolveDependencies($dependencies);//解析依賴注入的參數 array_pop($this->buildStack);//彈出數組棧 ...解析標簽
使用標簽綁定的類,將會使用 tagged 來解析:
public function tagged($tag) { $results = []; if (isset($this->tags[$tag])) { foreach ($this->tags[$tag] as $abstract) { $results[] = $this->make($abstract); } } return $results; }call方法注入
服務容器中,我們直接使用或者間接的使用 make 來構造服務對象,但是在實際的應用場景中,會有這樣的需求:我們擁有一個對象或者閉包函數,想要調用它的一個函數,但是它函數里面卻有其他類的參數,這個就需要進行 call 方法注入
public function call($callback, array $parameters = [], $defaultMethod = null) { return BoundMethod::call($this, $callback, $parameters, $defaultMethod); }
在 上一篇 文章中,我們說過,call 函數中的 callback 參數有以下幾種形式:
閉包 Closure
class@method
類靜態函數,class::method
類靜態函數: [ className/classObj, method ];類非靜態函數: [ classObj, method ]
若 defaultMethod 不為空,className
首先,我們先看看 call 方法注入的流程圖:
從流程圖中我們可以看出來,雖然調用 call 的形式有 5 種,但是實際最終的形式是三種,第二種和第五種被轉化為了第四種。
接下來,我們詳細的解析源碼:
先看一下 call 方法的主體:
public static function call($container, $callback, array $parameters = [], $defaultMethod = null) { if (static::isCallableWithAtSign($callback) || $defaultMethod) { return static::callClass($container, $callback, $parameters, $defaultMethod); } return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) { return call_user_func_array( $callback, static::getMethodDependencies($container, $callback, $parameters) ); }); }
可以看出來,call 方法注入主要有 4 個大的步驟:
對于 className@method 和 className-defaultMethod,實例化 className 為類對象,轉化為 [ classObj, method ]。
判斷 [ classObj / classname, method ] 是否存在被綁定的方法,如果有則調用。
利用服務容器解析依賴的參數。
調用 call_user_func_array。
實例化類對象在這里 className@method 和 className-defaultMethod 兩種情況被轉化為 [ classObj, method ], className會被實例化為類對象,并重新調用 call:
protected static function isCallableWithAtSign($callback) { return is_string($callback) && strpos($callback, "@") !== false; } protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null) { $segments = explode("@", $target); $method = count($segments) == 2 ? $segments[1] : $defaultMethod; if (is_null($method)) { throw new InvalidArgumentException("Method not provided."); } return static::call( $container, [$container->make($segments[0]), $method], $parameters ); }執行綁定方法
針對 [ className/classObj, method ], 調用被綁定的方法:
protected static function callBoundMethod($container, $callback, $default) { if (! is_array($callback)) { return value($default); } $method = static::normalizeMethod($callback); if ($container->hasMethodBinding($method)) { return $container->callMethodBinding($method, $callback[0]); } return value($default); } protected static function normalizeMethod($callback) { $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]); return "{$class}@{$callback[1]}"; } public function hasMethodBinding($method) { return isset($this->methodBindings[$method]); } public function callMethodBinding($method, $instance) { return call_user_func($this->methodBindings[$method], $instance, $this); }
那么這個被綁定的方法 methodBindings 從哪里來呢,就是 上一篇 文章提的 bindMethod:
public function bindMethod($method, $callback) { $this->methodBindings[$method] = $callback; }
從上面可以看出來,methodBindings 中 callback 參數一定是 classname@method 形式的。
實例化依賴這一步就要通過反射來獲取函數方法需要注入的參數類型,然后利用服務容器對參數類型進行解析構建:
protected static function getMethodDependencies($container, $callback, array $parameters = []) { $dependencies = []; foreach (static::getCallReflector($callback)->getParameters() as $parameter) { static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies); } return array_merge($dependencies, $parameters); }
getCallReflector 函數利用反射來獲取參數類型,值得注意的是class::method是需要拆分處理的:
protected static function getCallReflector($callback) { if (is_string($callback) && strpos($callback, "::") !== false) { $callback = explode("::", $callback); } return is_array($callback) ? new ReflectionMethod($callback[0], $callback[1]) : new ReflectionFunction($callback); }
利用傳入的參數,利用服務容器構建解析參數類型,或者獲取參數默認值:
protected static function addDependencyForCallParameter($container, $parameter, array &$parameters, &$dependencies) { if (array_key_exists($parameter->name, $parameters)) { $dependencies[] = $parameters[$parameter->name]; unset($parameters[$parameter->name]); } elseif ($parameter->getClass()) { $dependencies[] = $container->make($parameter->getClass()->name); } elseif ($parameter->isDefaultValueAvailable()) { $dependencies[] = $parameter->getDefaultValue(); } }call_user_func_array
關于這個函數可以參考 Laravel學習筆記之Callback Type
call_user_func_array( $callback, static::getMethodDependencies($container, $callback, $parameters) );
Written with StackEdit.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/22965.html
摘要:服務容器的綁定綁定歡迎關注我的博客綁定是服務容器最常用的綁定方式,在上一篇文章中我們討論過,的綁定有三種綁定自身綁定閉包綁定接口今天,我們這篇文章主要從源碼上講解服務容器是如何進行綁定的。將閉包函數和單例變量存入數組中,以備解析時使用。 服務容器的綁定 bind 綁定 歡迎關注我的博客:www.leoyang90.cn bind 綁定是服務容器最常用的綁定方式,在 上一篇文章中我們討論...
摘要:劃下重點,服務容器是用于管理類的依賴和執行依賴注入的工具。類的實例化及其依賴的注入,完全由服務容器自動的去完成。 本文首發于 深入剖析 Laravel 服務容器,轉載請注明出處。喜歡的朋友不要吝嗇你們的贊同,謝謝。 之前在 深度挖掘 Laravel 生命周期 一文中,我們有去探究 Laravel 究竟是如何接收 HTTP 請求,又是如何生成響應并最終呈現給用戶的工作原理。 本章將帶領大...
摘要:的核心概念包括服務容器服務提供者門面契約。所有服務提供者都需要繼承類。可以為服務提供者的方法設置類型提示。方法將在所有其他服務提供者均已注冊之后調用。同樣會整理成思維導圖的形式以方便記憶與回顧。 showImg(https://segmentfault.com/img/remote/1460000010771201); Laravel 的核心概念包括:服務容器、服務提供者、門面(Fac...
摘要:哲學的一個重要組成部分就是容器,也可以稱為服務容器。那我們要怎么做呢請看下面的例子數據庫連接通過上面的代碼,如果我們想把改成,根本不需要去修改類構造函數里的依賴。現在我要講下容器里到底發生了什么。 showImg(https://segmentfault.com/img/remote/1460000018868909); IOC 容器是一個實現依賴注入的便利機制 - Taylor?Ot...
摘要:可以為服務提供者的方法設置類型提示。方法將在所有其他服務提供者均已注冊之后調用。所有服務提供者都在配置文件中注冊。可以選擇推遲服務提供者的注冊,直到真正需要注冊綁定時,這樣可以提供應用程序的性能。 本文最早發布于 Rootrl的Blog 導言 Laravel是一款先進的現代化框架,里面有一些概念非常重要。在上手Laravel之前,我認為先弄懂這些概念是很有必要的。你甚至需要重溫下PHP...
閱讀 2295·2021-11-24 10:18
閱讀 2728·2021-11-19 09:59
閱讀 1716·2019-08-30 15:53
閱讀 1193·2019-08-30 15:53
閱讀 1076·2019-08-30 14:19
閱讀 2486·2019-08-30 13:14
閱讀 3020·2019-08-30 13:00
閱讀 1954·2019-08-30 11:11