Laravel 源碼分析---Container
標簽: laravel 源碼分析
Container 簡介
Container 是 laravel 框架的核心之一殴泰,laravel 框架中類的實例化、存儲和管理都是由 Container 來負責(zé)的忘巧。laravel 里面的 Container 本質(zhì)上是一個 IOC (Inversion of Control/控制反轉(zhuǎn)) 容器,是用來實現(xiàn)依賴注入(DI/Dependency Injection)的啤咽。也有人把這種設(shè)計成為服務(wù)定位模式奥洼。簡單的來說就是在 容器中綁定并保存各個類的抽象以及實例化的方法,在需要這個類的實例時搀擂,通過抽象訪問類的實例化的方法,由容器自動實例化類卷玉,并返回哨颂。用到的技術(shù)主要是 PHP 中的反射類。
下面是兩篇關(guān)于 IOC 容器和服務(wù)定位模式的文檔相种,感興趣可以參考一下:
Container 核心變量與設(shè)計
在介紹分析 Container 的源碼之前威恼,我們先介紹一下 Container 類里面幾個主要的變量和設(shè)計,理解這些,對于我們理解 Container 會有比較大的幫助沃测。
$abstract
Container 容器的主要作用是自動化類的實例化缭黔,所以我們首先要給要實例化的類起一個名字食茎。一般我們會用類的帶有命名空間的類名作為要實例化的類的名字蒂破。但是 laravel 的 Container 提供了更加靈活的描述:$abstract 。$abstract 是要實例化類的抽象别渔,他可以是類的全局名稱附迷,也可以是接口的全局名稱,還可以是你給類起的一個名字哎媚,總之非常靈活喇伯。$concrete
描述一個類如何實例化的信息,它可以是一個返回一個類實例的匿名函數(shù)拨与,也可以是一個可實例化類的全局名稱稻据,Container 會利用反射類自動創(chuàng)建其構(gòu)造函數(shù)所需參數(shù),并實例化這個類买喧。binding
將一個 $abstract 和 $concrete 映射到一起就構(gòu)成了一個 binding 捻悯。laravel 中 $abstract 到 $concrete 之間的綁定非常靈活,比如可以將一個接口綁定到一個實現(xiàn)了接口的類的實例淤毛,不過一般相互綁定的 $abstract 和 $concrete 也都是抽象和實例描述這樣的關(guān)系今缚。alias
Container 允許用戶為 $abstract 起多個別名,甚至允許 a 是 b 別名低淡,b 是 c 的別名這樣的操作姓言。在 laravel 常見的別名有:對于某個擁有父類或者實現(xiàn)某個接口的類的 $abstract , 將其父類和實現(xiàn)的接口都起成其別名。extender
Container 支持為一個 binding 添加 extender(擴展器/裝飾器)蔗蹋。extender 本質(zhì)上是一個閉包函數(shù)何荚,其接收一個 $abstract 的實例作為參數(shù),對此實例進行包裝擴展后返回猪杭。 用戶可以在 Container 上注冊 $abstract 的 extender餐塘。這樣在實例化的時候, Container 會使用這些 extender 對 $abstract 的實例進行裝飾擴展胁孙。
Container 通過 extender 的設(shè)計唠倦,可以實現(xiàn)相當(dāng)豐富的功能,比如實現(xiàn)裝飾器模式涮较,對 $abstract 的實例添加裝飾稠鼻;實現(xiàn)代理模式,為 $abstract 的實例構(gòu)造代理狂票;實現(xiàn)適配器模式候齿,基于 $abstract 的實例構(gòu)造適配器等。contextual binding
Container 支持創(chuàng)建基于上下文的 binding。先來解釋一下什么叫做上下文慌盯,因為 Container 在構(gòu)造 $abstract 的實例的時候周霉,是利用 $abstract 對應(yīng)的 $concrete 的反射類,通過解析反射類來構(gòu)造 $concrete 的亚皂,如果 $concrete 的構(gòu)造函數(shù)不存在參數(shù)俱箱,則可以直接構(gòu)造出 $concrete; 如果 $concrete 的構(gòu)造函數(shù)存在參數(shù)灭必,且這些參數(shù)也為某些實例對象的時候狞谱,Container 會遞歸構(gòu)造出這些實例參數(shù),然后構(gòu)造出 $concrete禁漓。在 Container 構(gòu)造 $concrete 構(gòu)造函數(shù)的參數(shù)對象的時候跟衅,就處于 $concrete 的上下文中。Container 通過使用 $buildStack 這個私有屬性記錄當(dāng)前正在構(gòu)造 $concrete 的堆棧來實現(xiàn)這個功能播歼。
下面我們在解釋什么叫做基于下上文的 binding伶跷。我們先來看一下官方文檔給出的說明:
有時侯我們可能有兩個類使用同一個接口,但我們希望在每個類中注入不同實現(xiàn)秘狞,例如叭莫,兩個控制器依賴 Illuminate\Contracts\Filesystem\Filesystem 契約的不同實現(xiàn)。Laravel 為此定義了簡單谒撼、平滑的接口:
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\VideoController;
use App\Http\Controllers\PhotoControllers;
use Illuminate\Contracts\Filesystem\Filesystem;
$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');
});
由于控制器類在 Laravel 框架中確實是有 Container 來實例化的食寡,這樣在上面的例子中,在不同控制器的構(gòu)造函數(shù)中聲明同一接口的參數(shù)廓潜,會得到不同的實例對象抵皱。
Container 源碼分析
下面我們開始分析 Container 的源碼。Container 類位于 laravel 框架 Illuminate\Container 命名空間下辩蛋。大家也可以自己打開源碼跟著分析呻畸。
Container 類的接口
下面我們來開始開始看一下 laravel 是如何實現(xiàn) Container 的。首先我們來看一下 Container 類的定義
class Container implements ArrayAccess, ContainerContract
{
}
我們看到 Container 主要實現(xiàn)了兩個接口悼院,一個是 ArrayAccess 接口伤为,這是一個 PHP 的內(nèi)置接口,這個接口的定義如下:
interface ArrayAccess {
public function offsetExists($offset);
public function offsetGet($offset);
public function offsetSet($offset, $value);
public function offsetUnset($offset);
}
實現(xiàn)這個接口類的對象据途,我們就可以像訪問數(shù)組一樣訪問對象了绞愚,這里我們不在詳述。
Container 類實現(xiàn)的第二個接口是 ContainerContract 颖医,這個接口主要定義了 Container 作為 IOC 所需要實現(xiàn)的方法位衩,下面我們來看一下這個接口的定義。
interface Container
{
/**
* Determine if the given abstract type has been bound.
* 判斷給定 $abstract 是否已經(jīng)被綁定
*/
public function bound($abstract);
/**
* Alias a type to a different name.
* 給一下 $abstract 起一個別名
*/
public function alias($abstract, $alias);
/**
* Assign a set of tags to a given binding.
* 給一組 $abstracts 打上一組tag
*/
public function tag($abstracts, $tags);
/**
* Resolve all of the bindings for a given tag.
* 創(chuàng)建給定 tag 下所有 $abstract 的實例
*/
public function tagged($tag);
/**
* Register a binding with the container.
* 注冊一個 $abstract 到 $concrete 的綁定到容器熔萧。
*/
public function bind($abstract, $concrete = null, $shared = false);
/**
* Register a binding if it hasn't already been registered.
* 如果 $abstract 沒有被注冊的話糖驴,注冊一個 $abstract 到 $concrete 的綁定到容器僚祷。
*/
public function bindIf($abstract, $concrete = null, $shared = false);
/**
* Register a shared binding in the container.
* 注冊一個可共享的綁定到容器
*/
public function singleton($abstract, $concrete = null);
/**
* "Extend" an abstract type in the container.
* 使用 $closure 擴展容器中的 $abstract
*/
public function extend($abstract, Closure $closure);
/**
* Register an existing instance as shared in the container.
* 注冊一個實例到容器中
*/
public function instance($abstract, $instance);
/**
* Define a contextual binding.
* 定義一個上下文的綁定
*/
public function when($concrete);
/**
* Resolve the given type from the container.
* 根據(jù)容器中的綁定,給出 $abstract 的實例贮缕。
*/
public function make($abstract, array $parameters = []);
/**
* Call the given Closure / class@method and inject its dependencies.
* 調(diào)用給定匿名函數(shù)或者 class@method 描述的類的方法辙谜,并且自動注入依賴參數(shù)
*/
public function call($callback, array $parameters = [], $defaultMethod = null);
/**
* Determine if the given abstract type has been resolved.
* 判斷一個 $abstract 是否實例化過
*/
public function resolved($abstract);
/**
* Register a new resolving callback.
* 注冊一個 $abstract 實例化的回調(diào)函數(shù)
*/
public function resolving($abstract, Closure $callback = null);
/**
* Register a new after resolving callback.
* 注冊一個 $abstract 實例化之后的回調(diào)函數(shù)
*/
public function afterResolving($abstract, Closure $callback = null);
}
以上是 Container 接口中聲明的方法,而我們接下來 Container 源碼的分析也主要針對 Container 接口中的方法感昼。
Container 類的主要屬性和方法
在了解了 Container 的幾個主要變量和概念的設(shè)計后装哆,我們來看一下 Container 的主要屬性和方法。
class Contner implements ArrayAccess, ContainerContract
{
/**
* An array of the types that have been resolved.
* 記錄實例化過的 $abstract 抑诸,key 為 $abstract烂琴,velue 為布爾值
*/
protected $resolved = [];
/**
* The container's bindings.
* 容器的 bindings (綁定關(guān)系),key 為 $abstract 蜕乡,value 為程序處理過的 $concrete,為一個關(guān)聯(lián)數(shù)組梗夸,模型如下:
* [
* 'concrete' => Closure,
* 'shared' => bool
* ]
*/
protected $bindings = [];
/**
* The container's shared instances.
* 可共享的 $abstract 的實例, 鍵為可共享的 $abstract , 值為 $abstract 對應(yīng)的實例
*/
protected $instances = [];
/**
* The registered type aliases.
* $abstract 的別名, key 是別名, value 是別名對應(yīng)的$abstract
* @var array
*/
protected $aliases = [];
/**
* The extension closures for services.
* 擴展的 $abstract 层玲,為一個二維數(shù)組,key 為 $abstract, vaule 為 Closure 組成的數(shù)組
*/
protected $extenders = [];
/**
* All of the registered tags.
* 注冊的 $abstract tags
* @var array
*/
protected $tags = [];
/**
* The stack of concretions currently being built.
* 當(dāng)前正在創(chuàng)建的 concretions 的堆棧
* @var array
*/
protected $buildStack = [];
/**
* The contextual binding map.
*
* @var array
*/
public $contextual = [];
/**
* All of the resolving callbacks by class type.
* resolving 回調(diào)函數(shù)
*/
protected $resolvingCallbacks = [];
/**
* All of the after resolving callbacks by class type.
* Resolving 之后的回調(diào)函數(shù)
*/
protected $afterResolvingCallbacks = [];
/**
* Define a contextual binding.
* 定義一個上下文的綁定
*/
public function when($concrete);
/**
* Determine if the given abstract type has been bound.
* 判斷 $abstract 是否綁定過
*/
public function bound($abstract);
/**
* Determine if the given abstract type has been resolved.
* $abstract 是否實例化過
*/
public function resolved($abstract);
/**
* Register a binding with the container.
* 在容器上反症,將 $abstract 綁定到 $concrete
*/
public function bind($abstract, $concrete = null, $shared = false);
/**
* Register a shared binding in the container.
* 注冊一個可共享的綁定到容器
*/
public function singleton($abstract, $concrete = null);
/**
* "Extend" an abstract type in the container.
* 在容器上擴展一個 $abstract
*/
public function extend($abstract, Closure $closure);
/**
* Register an existing instance as shared in the container.
* 注冊一個可共享的實例到容器
* @return void
*/
public function instance($abstract, $instance);
/**
* Assign a set of tags to a given binding.
* 給一組 $abstract 打上一組 tag
*/
public function tag($abstracts, $tags);
/**
* Resolve all of the bindings for a given tag.
* 實例化 $tag 下的 $abstract
*/
public function tagged($tag);
/**
* Alias a type to a different name.
* 給 $abstract 起別名 $alias
*/
public function alias($abstract, $alias);
/**
* Call the given Closure / class@method and inject its dependencies.
* 調(diào)用給定匿名函數(shù)或者 class@method 描述的類的方法辛块,并且自動注入依賴參數(shù)
*/
public function call($callback, array $parameters = [], $defaultMethod = null);
/**
* Resolve the given type from the container.
* 實例化 $abstract 綁定的 $concrete
*/
public function make($abstract, array $parameters = []);
/**
* Get the alias for an abstract if available.
* 返回 $abstract 作為別名對應(yīng)的真正的 $abstract。
*/
public function getAlias($abstract);
}
上面的這些方法是 Container 實現(xiàn)其功能的主要方法铅碍。下面我會按照一定的順序分析這些方法源代碼润绵。
Container 方法之 alias
因為 alias 是 Container 的一個基礎(chǔ)功能,所以我們先來介紹一下 Container alias 的相關(guān)方法
/**
* Alias a type to a different name.
* 給 $abstract 添加別名 $alias
* @param string $abstract 抽象
* @param string $alias 別名
* @return void
*/
public function alias($abstract, $alias)
{
$this->aliases[$alias] = $this->normalize($abstract);
}
/**
* Get the alias for an abstract if available.
* 如果 $abstract 屬于一個別名胞谈,則返回其此別名對應(yīng)的值
* @param string $abstract
* @return string
*
* @throws \LogicException
*/
public function getAlias($abstract)
{
// 如果$abstract 不屬于別名尘盼,直接返回
if (! isset($this->aliases[$abstract])) {
return $abstract;
}
if ($this->aliases[$abstract] === $abstract) {
throw new LogicException("[{$abstract}] is aliased to itself.");
}
// 地柜調(diào)用局待,返回別名 $abstract 最終對應(yīng)的值
return $this->getAlias($this->aliases[$abstract]);
}
/**
* Extract the type and alias from a given definition.
*
* @param array $definition
* @return array
*/
protected function extractAlias(array $definition)
{
return [key($definition), current($definition)];
}
/**
* Normalize the given class name by removing leading slashes.
*
* @param mixed $service
* @return mixed
*/
protected function normalize($service)
{
return is_string($service) ? ltrim($service, '\\') : $service;
}
Container 方法之 bind
下面埂伦,我們來介紹一下 Container 注冊 binding 的一系列方法
/**
* Register a binding with the container.
* 在 Container 注冊一個 binding
* @param string|array $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
$abstract = $this->normalize($abstract);
$concrete = $this->normalize($concrete);
// If the given types are actually an array, we will assume an alias is being
// defined and will grab this "real" abstract class name and register this
// alias with the container so that it can be used as a shortcut for it.
if (is_array($abstract)) {
//如果 array 是一個數(shù)組, 按照 key 為 $abstract, value 為 $alias 提取出來并設(shè)置到容器
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);
}
// 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.
// 刪除 $abstract 對應(yīng)的 的實例名党,并且如果$abstract是一個別名的話债鸡,也刪除這個別名的對應(yīng)關(guān)系
$this->dropStaleInstances($abstract);
// 如果 $concrete 為空, 默認為 $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.
// 如果 $concrete 不是閉包的話, 將其封裝成一個閉包
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
// 注冊 binding饲漾,函數(shù) compact 以變量名作為數(shù)組的鍵, 值作為數(shù)組的值構(gòu)造數(shù)組
$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.
// 判斷 $abstract 是否是已經(jīng)已經(jīng)實例化, 如果是重新實例化, 并調(diào)用$abstract rebound 時的回調(diào)函數(shù)
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
/**
* Get the Closure to be used when building a type.
* 創(chuàng)建關(guān)于 $abstract 和 $concrete 的閉包钾恢,此閉包函數(shù)封裝了創(chuàng)建 $concrete 對應(yīng)對象的方法
* @param string $abstract
* @param string $concrete
* @return \Closure
*/
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
$method = ($abstract == $concrete) ? 'build' : 'make';
//Note : 這個函數(shù)調(diào)用的第一個參數(shù)是 $concrete 废岂,而不是 $abstract,需要注意
return $container->$method($concrete, $parameters);
};
}
/**
* Drop all of the stale instances and aliases.
* 刪除 $abstract 對應(yīng)的 的實例浑娜,并且如果 $abstract 是一個別名的話享扔,也刪除這個別名的對應(yīng)關(guān)系
* @param string $abstract
* @return void
*/
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
/**
* Determine if the given abstract type has been resolved.
* 判斷抽象是否被實例化(make)過
* @param string $abstract
* @return bool
*/
public function resolved($abstract)
{
$abstract = $this->normalize($abstract);
if ($this->isAlias($abstract)) {
$abstract = $this->getAlias($abstract);
}
return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]);
}
/**
* Fire the "rebound" callbacks for the given abstract type.
* make $abstract, 并調(diào)用$abstract rebound 時的回調(diào)函數(shù)
* @param string $abstract
* @return void
*/
protected function rebound($abstract)
{
$instance = $this->make($abstract);
foreach ($this->getReboundCallbacks($abstract) as $callback) {
call_user_func($callback, $this, $instance);
}
}
/**
* Register a shared binding in the container.
* 在 Container 上注冊一個可共享的 binding
* @param string|array $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
/**
* Register an existing instance as shared in the container.
* 注冊一個可共享的實例到 Container
* @param string $abstract
* @param mixed $instance
* @return void
*/
public function instance($abstract, $instance)
{
$abstract = $this->normalize($abstract);
// First, we will extract the alias from the abstract if it is an array so we
// are using the correct name when binding the type. If we get an alias it
// will be registered with the container so we can resolve it out later.
if (is_array($abstract)) {
list($abstract, $alias) = $this->extractAlias($abstract);
$this->alias($abstract, $alias);
}
unset($this->aliases[$abstract]);
// We'll check to determine if this type has been bound before, and if it has
// we will fire the rebound callbacks registered with the container and it
// can be updated with consuming classes that have gotten resolved here.
$bound = $this->bound($abstract);
$this->instances[$abstract] = $instance;
if ($bound) {
$this->rebound($abstract);
}
}
/**
* Register a binding if it hasn't already been registered.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bindIf($abstract, $concrete = null, $shared = false)
{
if (! $this->bound($abstract)) {
$this->bind($abstract, $concrete, $shared);
}
}
/**
* "Extend" an abstract type in the container.
* 在 Container 為 $abstract 注冊 extender
* @param string $abstract
* @param \Closure $closure
* @return void
*
* @throws \InvalidArgumentException
*/
public function extend($abstract, Closure $closure)
{
$abstract = $this->normalize($abstract);
if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $closure($this->instances[$abstract], $this);
$this->rebound($abstract);
} else {
$this->extenders[$abstract][] = $closure;
}
}
Container 方法之 contextual binding
Container 支持 contextual binding底桂,我們來看一下這個功能相關(guān)的源代碼。
/**
* Define a contextual binding.
* 定義一個 contextual binding
* @param string $concrete
* @return \Illuminate\Contracts\Container\ContextualBindingBuilder
*/
public function when($concrete)
{
$concrete = $this->normalize($concrete);
return new ContextualBindingBuilder($this, $concrete);
}
/**
* Add a contextual binding to the container.
* 添加一個 contextual binding 到容器
* @param string $concrete
* @param string $abstract
* @param \Closure|string $implementation
* @return void
*/
public function addContextualBinding($concrete, $abstract, $implementation)
{
$this->contextual[$this->normalize($concrete)][$this->normalize($abstract)] = $this->normalize($implementation);
}
我們看到惧眠,在 when 方法里面返回了一個 ContextualBindingBuilder 類的實例籽懦,下面我們來看一下 ContextualBindingBuilder 類的源碼
namespace Illuminate\Container;
use Illuminate\Contracts\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;
class ContextualBindingBuilder implements ContextualBindingBuilderContract
{
/**
* The underlying container instance.
*
* @var \Illuminate\Container\Container
*/
protected $container;
/**
* The concrete instance.
*
* @var string
*/
protected $concrete;
/**
* The abstract target.
*
* @var string
*/
protected $needs;
/**
* Create a new contextual binding builder.
*
* @param \Illuminate\Container\Container $container
* @param string $concrete
* @return void
*/
public function __construct(Container $container, $concrete)
{
$this->concrete = $concrete;
$this->container = $container;
}
/**
* Define the abstract target that depends on the context.
*
* @param string $abstract
* @return $this
*/
public function needs($abstract)
{
$this->needs = $abstract;
return $this;
}
/**
* Define the implementation for the contextual binding.
*
* @param \Closure|string $implementation
* @return void
*/
public function give($implementation)
{
$this->container->addContextualBinding($this->concrete, $this->needs, $implementation);
}
}
ContextualBindingBuilder 的源碼非常簡單,通過以上分析,我們可以知道 Container 如何添加一個 contextual binding锉试。
Container 方法之 make
我們介紹過了如何注冊一個 binding 到 Container, 現(xiàn)在我們來介紹如何創(chuàng)建一個 $abstract 對應(yīng)的實例猫十。
/**
* Resolve the given type from the container.
* 創(chuàng)建一個 $abstract 對應(yīng)的實例并返回
* @param string $abstract
* @param array $parameters
* @return mixed
*/
public function make($abstract, array $parameters = [])
{
$abstract = $this->getAlias($this->normalize($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.
// 如果 Instance 數(shù)組里面存儲有 $abstract 里面對應(yīng)的對象览濒,則直接返回
if (isset($this->instances[$abstract])) {
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, $parameters);
} else {
$object = $this->make($concrete, $parameters);
}
// 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.
// 如果 $abstract 存在 extender , 則利用這些 extender 對 $abstract 實例進行擴展裝飾
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.
// 如果 $abstract 是可共享的,則將其放入 instances 數(shù)組中
if ($this->isShared($abstract)) {
$this->instances[$abstract] = $object;
}
//觸發(fā) resolving 的回調(diào)方法
$this->fireResolvingCallbacks($abstract, $object);
//標記 $abstract 實例化過
$this->resolved[$abstract] = true;
return $object;
}
/**
* Get the concrete type for a given abstract.
* 返回 $abstract 的 $concrete
* @param string $abstract
* @return mixed $concrete
*/
protected function getConcrete($abstract)
{
if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
return $concrete;
}
// If we don't have a registered resolver or concrete for the type, we'll just
// assume each type is a concrete name and will attempt to resolve it as is
// since the container should be able to resolve concretes automatically.
// 如果不存在拖云,說明 $abstract 沒有注冊過贷笛,直接返回 $abstract 本身
if (! isset($this->bindings[$abstract])) {
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
/**
* Get the contextual concrete binding for the given abstract.
*
* @param string $abstract
* @return string|null
*/
protected function getContextualConcrete($abstract)
{
if (isset($this->contextual[end($this->buildStack)][$abstract])) {
return $this->contextual[end($this->buildStack)][$abstract];
}
}
/**
* Determine if the given concrete is buildable.
*
* @param mixed $concrete
* @param string $abstract
* @return bool
*/
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
/**
* Instantiate a concrete instance of the given type.
* 構(gòu)建 $concrete 對應(yīng)的對象
* @param string $concrete
* @param array $parameters
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function build($concrete, array $parameters = [])
{
// 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.
// 如果 $concrete 是一個閉包,則直接調(diào)用這個閉包方法創(chuàng)建對象并返回
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
//創(chuàng)建 $concrete 的反射類宙项,并解析反射類創(chuàng)建對象
$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()) {
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();
// 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.
$parameters = $this->keyParametersByArgument(
$dependencies, $parameters
);
$instances = $this->getDependencies(
$dependencies, $parameters
);
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);
}
Contaner 方法之 tag
我們知道乏苦,Container 支持對 $abstarct 打上 tag, 并根據(jù) tag 進行實例化,下面我們來看一下部分代碼的實現(xiàn)
/**
* Assign a set of tags to a given binding.
*
* @param array|string $abstracts
* @param array|mixed ...$tags
* @return void
*/
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][] = $this->normalize($abstract);
}
}
}
/**
* Resolve all of the bindings for a given tag.
*
* @param string $tag
* @return array
*/
public function tagged($tag)
{
$results = [];
if (isset($this->tags[$tag])) {
foreach ($this->tags[$tag] as $abstract) {
$results[] = $this->make($abstract);
}
}
return $results;
}
說明
現(xiàn)在我們看完了 Container 的主要源代碼尤筐,現(xiàn)在我們主要來看一下 bind 和 make 這兩個方法汇荐。
我們知道 bind 方法的 $concrete 參數(shù)可以為空,當(dāng) $concrete 為空時盆繁,框架會將 $concrete 設(shè)置成 $abstruct, 并根據(jù) $abstruct 和 $concrete 的值構(gòu)造閉包作為最終的 $concrete掀淘。
make 方法支持對沒有綁定過的 $abstruct 進行構(gòu)建,在 getConcrete 方法里面油昂,如果 $abstruct 不存在綁定的 $concrete 的話革娄,會直接返回 $abstruct。這樣在 make 函數(shù)里面會調(diào)用 build 方法進行構(gòu)建冕碟。
下面我們來分析一下對于綁定了字符串類型的 $concrete 且 $abstruct != $concrete
時拦惋, make 的執(zhí)行流程:
bind($abstruct, $concrete)
添加 $abstruct 到 $concrete 的 binding。由于 $concrete 為字符串且$abstruct != $concrete
, 則 Container 構(gòu)造關(guān)于 $abstruct 和 $concrete 的閉包安寺,設(shè)為函數(shù) f1厕妖,并且 $method 為 make,參數(shù)為字符串 $concrete(getClosure 方法的代碼執(zhí)行)挑庶。make($abstruct)
創(chuàng)建 $abstruct 對應(yīng)的對象言秸,獲取 $abstruct 對應(yīng)的 $concrete 為閉包函數(shù) f1,則 f1 是可構(gòu)建的(isBuildable 返回true)挠羔,則對 f1 調(diào)用 build 方法井仰。build($concrete)
構(gòu)建 $concrete, 由于 $concrete 是閉包函數(shù) f1破加,則執(zhí)行這個閉包函數(shù)俱恶,針對 f1 里面的字符串 $concrete 調(diào)用 make 方法。make($concrete)
構(gòu)建關(guān)于字符串 $concrete 的對象范舀, 由于字符串 $concrete 沒有在容器里面綁定過(getConcrete 方法里面isset($this->bindings[$abstract])
為 false)合是,getConcrete 函數(shù)返回字符串 $concrete 本身,且 $concrete 是可構(gòu)建的(isBuildable 返回 true)锭环,則對字符串 $concrete 調(diào)用 build 方法聪全。build($concrete)
由于 $concrete 是字符串,則針對這個字符串構(gòu)造反射類辅辩,并分析反射類構(gòu)造對象难礼。
總結(jié)
至此娃圆,我們已經(jīng)分析了 Container 的主要功能和源代碼。Container 是 laravel 框架的核心蛾茉,理解這塊的代碼將對你正確靈活使用 laravel 框架具有很大的幫助讼呢。
laravel 其他功能的源碼分析將陸續(xù)奉上,敬請大家期待谦炬。寫文不易悦屏,如果你喜歡我的文章,還請贊賞支持一下键思。