1. bind 簡單綁定 $this->app->bind('HelpSpot\API', function ($app) { return new HelpSpot\API($app->make('HttpClient')); }); 2. singleton 綁定一個單例 $this->app->singleton('HelpSpot\API', function ($app) { return new HelpSpot\API($app->make('HttpClient')); }); 3. instance 綁定實例 $api = new HelpSpot\API(new HttpClient); $this->app->instance('HelpSpot\Api', $api);
1. singleton 和 bind 的解析 public function singleton($abstract, $concrete = null) { // 實際是經過bind來實現的,區別在於最後的一個默認參數 $this->bind($abstract, $concrete, true); } public function bind($abstract, $concrete = null, $shared = false) { // 移除之前的實例和別名 $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } // 統一成匿名函數的形式,進行統一的調用。**注意:這是laravel裏面的按需加載的一種實現方式** if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); // 若已經實例化過了,則從新進行構建 if ($this->resolved($abstract)) { $this->rebound($abstract); } } protected function dropStaleInstances($abstract) { unset($this->instances[$abstract], $this->aliases[$abstract]); } protected function getClosure($abstract, $concrete) { // 參數僅用來區分調用容器裏面的方法,build和make後面進行講解 return function ($container, $parameters = []) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; return $container->$method($concrete, $parameters); }; } public function resolved($abstract) { if ($this->isAlias($abstract)) { $abstract = $this->getAlias($abstract); } return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]); }
小結:singleton和bind綁定以後的結果是填充一個容器屬性$this->bindings,爲後期的服務解析提供數據,數組以下laravel
$this->bindings[$abstract] = [ 'concrete' => $concrete, // 匿名函數,用來構建實例 'shared' => $shared, // 此參數則是用來實現單例的關鍵,也是singleton和bind的差異所在。後期服務解析時會經過此參數和上下文參數來肯定是否放入$this->instances數組裏,這個就是單例的本質。 ]
2. instance 的解析 public function instance($abstract, $instance) { $this->removeAbstractAlias($abstract); unset($this->aliases[$abstract]); // instance方法的本質,將實例$instance注入到容器的instances屬性裏 $this->instances[$abstract] = $instance; if ($this->bound($abstract)) { $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); }
小結:instance綁定以後的結果是填充一個容器屬性的數組$this->instances,爲後期的服務解析提供數據,數組以下api
$this->instances[$abstract] = Object(xxx) // instances對應的是具體的實現
3. 總結 本質上,服務容器的綁定就是將相應的代碼(實例、類名、匿名函數等)注入到服務容器相應的屬性裏。 這樣,就能夠經過容器的服務解析(make)來進行相應的操做。固然,通常狀況都是經過服務容器來自 動解決類之間的依賴關係的(類的反射)。
make | makeWith 方法(makeWith能夠指定參數)數組
$api = $this->app->make('HelpSpot\API');
resolve 全局函數(當不能使用$this->app實例時,本質上是仍是調用容器的make或makeWith)app
$api = resolve('HelpSpot\API');
自動注入(最重要)ide
能夠在類的構造函數或方法中對依賴使用「類型提示」,依賴的類將會被容器自動進行解析,包括在控制 器,事件監聽器,隊列任務,中間件等地方。事實上,這也是大部分類被容器解析的方式。
1. 代碼解析 public function make($abstract) { return $this->resolve($abstract); } public function makeWith($abstract, array $parameters) { return $this->resolve($abstract, $parameters); } protected function resolve($abstract, $parameters = []) { // 參數數組入棧 $this->with[] = $parameters; // 是否有參數或爲上下文形式綁定(when綁定),優先從全局別名 $this->aliases 裏面取出最終值,沒有則返回原值 $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract = $this->getAlias($abstract)) ); // 若存在且沒有上下文關係的實例,則直接返回。 if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $concrete = $this->getConcrete($abstract); // 此方法是經過反射循環處理依賴關係的核心 if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } // $this->app->extend($abstract, Closure $closure)方法能夠指定$abstract在實例化後須要執行的額外操做,將在此時進行調用 foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // singleton只實例化一次的本質 if ($this->isShared($abstract) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } // 對對象執行額外的函數,相似全局函數等後續操做,被定義在$this->globalResolvingCallbacks、$this->resolvingCallbacks、$this->globalAfterResolvingCallbacks、$this->afterResolvingCallbacks數組中,能夠經過$this->app->resolving()或$this->app->afterResolving()方法進行綁定 $this->fireResolvingCallbacks($abstract, $object); // 處理完以後標記爲已解決 $this->resolved[$abstract] = true; array_pop($this->with); return $object; }
注意:上面操做的$abstract鍵不少都是經過$this->getAlias($abstract)處理過的。$this->aliases數組相似函數
$this->aliases['Illuminate\Foundation\Application'] = 'app'; $this->aliases['Illuminate\Contracts\Container\Container'] = 'app'; $this->aliases['Illuminate\Contracts\Foundation\Application'] = 'app';
因此經過$this->getAlias($abstract)獲取到的是相似'app'的別名;
從resolve方法中也能夠看出,make 時先嚐試將 abstract 轉換爲 alias,再從 instances 取,最後才取 bindings。ui
public function getAlias($abstract) { // 不存在則原值返回 if (! isset($this->aliases[$abstract])) { return $abstract; } if ($this->aliases[$abstract] === $abstract) { throw new LogicException("[{$abstract}] is aliased to itself."); } // 不然取別名的最終值,可能會出現鏈式別名。a=>b=>c=>d,a的別名是b,b的別名是c,c的別名是d,最終取d return $this->getAlias($this->aliases[$abstract]); } protected function getContextualConcrete($abstract) { // 若存在上下文的concrete,則直接返回 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) { // 此數組的構建方式是由$this->app->when(x)->needs(y)->give(z)方式來構建的,能夠理解爲:當x須要y時給z if (isset($this->contextual[end($this->buildStack)][$abstract])) { return $this->contextual[end($this->buildStack)][$abstract]; } } protected function getConcrete($abstract) { // 優先取對應的上下文的concrete if (! is_null($concrete = $this->getContextualConcrete($abstract))) { return $concrete; } // 再從以前的$this->bindings裏面取 if (isset($this->bindings[$abstract])) { return $this->bindings[$abstract]['concrete']; } return $abstract; } protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; } // 經過反射處理依賴關係並構建相應的實例的方法 public function build($concrete) { // 若是是匿名函數則直接調用並返回 if ($concrete instanceof Closure) { return $concrete($this, end($this->with)); } // 不然獲取concrete的反射類 $reflector = new ReflectionClass($concrete); // 若不能實例化,拋異常 if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } // 將全部依賴的要處理的$concrete入棧,待一個一個處理完以後出棧,處理完全部的concrete以後應該是個空數組 $this->buildStack[] = $concrete; // 獲取構造器 $constructor = $reflector->getConstructor(); // 沒有構造器(即沒有依賴),直接建立實例並返回 if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } // 獲取構造器的依賴關係,即參數類型 $dependencies = $constructor->getParameters(); // 處理依賴關係,返回的是一個依次含有全部依賴的實例 $instances = $this->resolveDependencies( $dependencies ); // 處理完就出棧 array_pop($this->buildStack); // 返回相應的實例 return $reflector->newInstanceArgs($instances); } 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) //resolvePrimitive基本類型處理 : $this->resolveClass($dependency); //resolveClass類類型處理 } 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 resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); // 遞歸解析各種之間的依賴關係 } catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }
2. 總結 服務解析,實際上就是委託服務容器經過反射來處理類之間的依賴關係,從而獲得相應的實例。但前提是容器裏 服務的註冊,因此,須要再服務解析以前,將全部須要的服務注入到服務容器裏。而laravel裏面的服務注入方 式是經過服務提供者來進行注入的,而後在將服務提供者註冊到容器裏,相應的服務便會注入到容器裏。
每當服務容器解析一個對象時就會觸發一個事件。你能夠使用 resolving 方法監聽這個事件this
$this->app->resolving(function ($object, $app) { // 解析任何類型的對象時都會調用該方法... }); $this->app->resolving(HelpSpot\API::class, function ($api, $app) { // 解析「HelpSpot\API」類型的對象時調用... }); 對應於$this->app->make()調用的resolve()方法:$this->fireResolvingCallbacks($abstract, $object); 注意:若是是單例,則只在解析時會觸發一次
綁定初始數據code
有時,你的類不只須要注入類,還須要注入一些原始數據,如一個整數。此時,你能夠容易地經過情景綁定注入須要的任何值:中間件
$this->app->when('AppHttpControllersUserController')
->needs('$variableName')
->give($value);
綁定接口至實現
服務容器有一個強大的功能,就是將一個指定接口的實現綁定到接口上。例如,若是咱們有一個 EventPusher 接口和一個它的實現類 RedisEventPusher 。編寫完接口的 RedisEventPusher 實現類後,咱們就能夠在服務容器中像下面例子同樣註冊它:
$this->app->bind(
'App\Contracts\EventPusher', 'App\Services\RedisEventPusher'
);
情境綁定
有時候,你可能有兩個類使用到相同的接口,但你但願每一個類都能注入不一樣的實現。例如,兩個控制器可能須要依賴不一樣的 IlluminateContractsFilesystemFilesystem 契約 的實現類。 Laravel 爲此定義了一種簡單、平滑的接口:
use IlluminateSupportFacadesStorage;
use AppHttpControllersPhotoController;
use AppHttpControllersVideoController;
use IlluminateContractsFilesystemFilesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class) ->give(function () { return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class) ->give(function () { return Storage::disk('s3');
});
標記
有時候,你可能須要解析某個「分類」下的全部綁定。例如,你正在構建一個報表的聚合器,它須要接受不一樣 Report 接口的實例。分別註冊了 Report 實例後,你能夠使用 tag 方法爲他們賦予一個標籤:
$this->app->bind('SpeedReport', function () { // }); $this->app->bind('MemoryReport', function () { // }); $this->app->tag(['SpeedReport', 'MemoryReport'], 'reports'); 一旦服務被標記後,你能夠經過 tagged 方法輕鬆地將它們所有解析: $this->app->bind('ReportAggregator', function ($app) { return new ReportAggregator($app->tagged('reports')); });