摘要:服務容器的綁定綁定歡迎關注我的博客綁定是服務容器最常用的綁定方式,在上一篇文章中我們討論過,的綁定有三種綁定自身綁定閉包綁定接口今天,我們這篇文章主要從源碼上講解服務容器是如何進行綁定的。將閉包函數和單例變量存入數組中,以備解析時使用。
服務容器的綁定 bind 綁定
歡迎關注我的博客:www.leoyang90.cn
bind 綁定是服務容器最常用的綁定方式,在 上一篇文章中我們討論過,bind 的綁定有三種:
綁定自身
綁定閉包
綁定接口
今天,我們這篇文章主要從源碼上講解 Ioc 服務容器是如何進行綁定的。
/** * Register a binding with the container. * * @param string|array $abstract * @param Closure|string|null $concrete * @param bool $shared * @return void */ public function bind($abstract, $concrete = null, $shared = false) { // If no concrete type was given, we will simply set the concrete type to the // abstract type. After that, the concrete type to be registered as shared // without being forced to state their classes in both of the parameters. $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } // If the factory is not a Closure, it means it is just a class name which is // bound into this container to the abstract type and we will just wrap it // up inside its own Closure to give us more convenience when extending. if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact("concrete", "shared"); // If the abstract type was already resolved in this container we"ll fire the // rebound listener so that any objects which have already gotten resolved // can have their copy of the object updated via the listener callbacks. if ($this->resolved($abstract)) { $this->rebound($abstract); } }
從源碼中我們可以看出,服務器的綁定有如下幾個步驟:
去除原有注冊去除原有注冊。去除當前綁定接口的原有實現單例對象,和原有的別名,為實現綁定新的實現做準備。
加裝閉包。如果實現類不是閉包(綁定自身或者綁定接口),那么就創建閉包,以實現 lazy 加載。
注冊。將閉包函數和單例變量存入 bindings 數組中,以備解析時使用。
回調。如果綁定的接口已經被解析過了,將會調用回調函數,對已經解析過的對象進行調整。
dropStaleInstances 用于去除當前接口原有的注冊和別名,這里負責清除綁定的 aliases 和單例對象的 instances,bindings 后面再做修改:
protected function dropStaleInstances($abstract) { unset($this->instances[$abstract], $this->aliases[$abstract]); }加裝閉包
getClosure 的作用是為注冊的非閉包實現外加閉包,這樣做有兩個作用:
延時加載
服務容器在 getClosure 中為每個綁定的類都包一層閉包,這樣服務容器就只有進行解析的時候閉包才會真正進行運行,實現了 lazy 加載的功能。
遞歸綁定
對于服務容器來說,綁定是可以遞歸的,例如:
$app->bind(A::class,B::class); $app->bind(B::class,C::class); $app->bind(C::class,function(){ return new C; })
對于 A 類,我們直接解析 A 可以得到 B 類,但是如果僅僅到此為止,服務容器直接去用反射去創建 B 類的話,那么就很有可能創建失敗,因為 B 類很有可能也是接口,B 接口綁定了其他實現類,要知道接口是無法實例化的。
因此服務容器需要遞歸地對 A 進行解析,這個就是 getClosure 的作用,它把所有可能會遞歸的綁定在閉包中都用 make 函數,這樣解析 make(A::class) 的時候得到閉包 make(B::class),make(B::class) 的時候會得到閉包 make(C::class),make(C::class) 終于可以得到真正的實現了。
對于自我綁定的情況,因為不存在遞歸情況,所以在閉包中會使用 build 函數直接創建對象。(如果仍然使用 make,那就無限循環了)
protected function getClosure($abstract, $concrete) { return function ($container, $parameters = []) use ($abstract, $concrete) { if ($abstract == $concrete) { return $container->build($concrete); } return $container->makeWith($concrete, $parameters); }; }注冊
注冊就是向 binding 數組中添加注冊的接口與它的實現,其中 compact() 函數創建包含變量名和它們的值的數組,創建后的結果為:
$bindings[$abstract] = [ "concrete" => $concrete, "shared" => $shared ]回調
注冊之后,還要查看當前注冊的接口是否已經被實例化,如果已經被服務容器實例化過,那么就要調用回調函數。(若存在回調函數)
resolved() 函數用于判斷當前接口是否曾被解析過,在判斷之前,先獲取了接口的最終服務名:
public function resolved($abstract) { if ($this->isAlias($abstract)) { $abstract = $this->getAlias($abstract); } return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]); } public function isAlias($name) { return isset($this->aliases[$name]); }
getAlias() 函數利用遞歸的方法獲取別名的最終服務名稱:
public function getAlias($abstract) { if (! isset($this->aliases[$abstract])) { return $abstract; } if ($this->aliases[$abstract] === $abstract) { throw new LogicException("[{$abstract}] is aliased to itself."); } return $this->getAlias($this->aliases[$abstract]); }
如果當前接口已經被解析過了,那么就要運行回調函數:
protected function rebound($abstract) { $instance = $this->make($abstract); foreach ($this->getReboundCallbacks($abstract) as $callback) { call_user_func($callback, $this, $instance); } } protected function getReboundCallbacks($abstract) { if (isset($this->reboundCallbacks[$abstract])) { return $this->reboundCallbacks[$abstract]; } return []; }
這里面的 reboundCallbacks 從哪里來呢?這就是 Laravel核心——Ioc服務容器 文章中提到的 rebinding
public function rebinding($abstract, Closure $callback) { $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; if ($this->bound($abstract)) { return $this->make($abstract); } }
值得注意的是: rebinding 函數不僅綁定了回調函數,同時順帶還對接口abstract進行了解析,因為只有解析過,下次注冊才會調用回調函數。
singleton 綁定singleton 綁定僅僅是 bind 綁定的一個 shared 為真的形式:
public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); }instance 綁定
不對接口進行解析,直接給接口一個實例作為單例對象。從下面可以看出,主要的工作就是去除接口在abstractAliases 數組和 aliases 數組中的痕跡,防止 make 函數根據別名繼續解析下去出現錯誤。如果當前接口曾經注冊過,那么就調用回調函數。
public function instance($abstract, $instance) { $this->removeAbstractAlias($abstract); $isBound = $this->bound($abstract); unset($this->aliases[$abstract]); $this->instances[$abstract] = $instance; if ($isBound) { $this->rebound($abstract); } } protected function removeAbstractAlias($searched) { if (! isset($this->aliases[$searched])) { return; } foreach ($this->abstractAliases as $abstract => $aliases) { foreach ($aliases as $index => $alias) { if ($alias == $searched) { unset($this->abstractAliases[$abstract][$index]); } } } } public function bound($abstract) { return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]) || $this->isAlias($abstract); }Context 綁定
Context 綁定一般用于依賴注入,當我們利用依賴注入來自動實例化對象時,服務容器其實是利用反射機制來為構造函數實例化它的參數,這個過程中,被實例化的對象就是下面的 concrete,構造函數的參數接口是 abstract,參數接口實際的實現是 implementation。
例如:
$this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk("local"); });
這里實例化對象 concrete 就是 PhotoController,構造函數的參數接口 abstract 就是 Filesystem。參數接口實際實現 implementation 是 Storage::disk("local")。
這樣,每次進行解析構造函數的參數接口的時候,都會去判斷當前的 contextual 數組里面 concrete[concrete] [abstract](也就是 concrete[PhotoController::class] [Filesystem::class])對應的上下文綁定,如果有就直接從數組中取出來,如果沒有就按照正常方式解析。
值得注意的是,concrete 和 abstract 都是利用 getAlias 函數,保證最后拿到的不是別名。
public function when($concrete) { return new ContextualBindingBuilder($this, $this->getAlias($concrete)); } public function __construct(Container $container, $concrete) { $this->concrete = $concrete; $this->container = $container; } public function needs($abstract) { $this->needs = $abstract; return $this; } public function give($implementation) { $this->container->addContextualBinding( $this->concrete, $this->needs, $implementation ); } public function addContextualBinding($concrete, $abstract, $implementation) { $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation; }tag 綁定
標簽綁定比較簡單,綁定過程就是將標簽和接口之間建立一個對應數組,在解析的過程中,按照標簽把所有接口都解析一遍即可。
public function tag($abstracts, $tags) { $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); foreach ($tags as $tag) { if (! isset($this->tags[$tag])) { $this->tags[$tag] = []; } foreach ((array) $abstracts as $abstract) { $this->tags[$tag][] = $abstract; } } }數組綁定
利用數組進行綁定的時候 ($app()[A::class] = B::class),服務容器會調用 offsetSet 函數:
public function offsetSet($key, $value) { $this->bind($key, $value instanceof Closure ? $value : function () use ($value) { return $value; }); }extend擴展
extend 擴展分為兩種,一種是針對instance注冊的對象,這種情況將立即起作用,并更新之前實例化的對象;另一種情況是非 instance 注冊的對象,那么閉包函數將會被放入 extenders 數組中,將在下一次實例化對象的時候才起作用:
public function extend($abstract, Closure $closure) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) { $this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract); } else { $this->extenders[$abstract][] = $closure; if ($this->resolved()) { $this->rebound($abstract); } } }服務器事件
服務器的事件注冊依靠 resolving 函數和 afterResolving 函數,這兩個函數維護著 globalResolvingCallbacks、resolvingCallbacks、globalAfterResolvingCallbacks、afterResolvingCallbacks 數組,這些數組中存放著事件的回調閉包函數,每當對對象進行解析時就會遍歷這些數組,觸發事件:
public function resolving($abstract, Closure $callback = null) { if (is_string($abstract)) { $abstract = $this->getAlias($abstract); } if (is_null($callback) && $abstract instanceof Closure) { $this->globalResolvingCallbacks[] = $abstract; } else { $this->resolvingCallbacks[$abstract][] = $callback; } } public function afterResolving($abstract, Closure $callback = null) { if (is_string($abstract)) { $abstract = $this->getAlias($abstract); } if ($abstract instanceof Closure && is_null($callback)) { $this->globalAfterResolvingCallbacks[] = $abstract; } else { $this->afterResolvingCallbacks[$abstract][] = $callback; } }
Written with StackEdit.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/22925.html
摘要:劃下重點,服務容器是用于管理類的依賴和執行依賴注入的工具。類的實例化及其依賴的注入,完全由服務容器自動的去完成。 本文首發于 深入剖析 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...
摘要:而函數作用是加載延遲服務,與容器解析關系不大,我們放在以后再說。在構造之前,服務容器會先把放入中,繼而再去解析。利用服務容器解析依賴的參數。 make解析 首先歡迎關注我的博客: www.leoyang90.cn 服務容器對對象的自動解析是服務容器的核心功能,make 函數、build 函數是實例化對象重要的核心,先大致看一下代碼: public function make($abst...
閱讀 3783·2021-09-23 11:32
閱讀 2451·2021-09-06 15:01
閱讀 1616·2021-08-18 10:24
閱讀 3450·2019-12-27 11:44
閱讀 3605·2019-08-30 15:52
閱讀 2512·2019-08-30 11:11
閱讀 674·2019-08-29 17:27
閱讀 600·2019-08-29 16:22