Laravel 源码分析---Container
标签: laravel 源码分析
Container 简介
Container 是 laravel 框架的核心之一,laravel 框架中类的实例化、存储和管理都是由 Container 来负责的。laravel 里面的 Container 本质上是一个 IOC (Inversion of Control/控制反转) 容器,是用来实现依赖注入(DI/Dependency Injection)的。也有人把这种设计成为服务定位模式。简单的来说就是在 容器中绑定并保存各个类的抽象以及实例化的方法,在需要这个类的实例时,通过抽象访问类的实例化的方法,由容器自动实例化类,并返回。用到的技术主要是 PHP 中的反射类。
下面是两篇关于 IOC 容器和服务定位模式的文档,感兴趣可以参考一下:
Container 核心变量与设计
在介绍分析 Container 的源码之前,我们先介绍一下 Container 类里面几个主要的变量和设计,理解这些,对于我们理解 Container 会有比较大的帮助。
$abstract
Container 容器的主要作用是自动化类的实例化,所以我们首先要给要实例化的类起一个名字。一般我们会用类的带有命名空间的类名作为要实例化的类的名字。但是 laravel 的 Container 提供了更加灵活的描述:$abstract 。$abstract 是要实例化类的抽象,他可以是类的全局名称,也可以是接口的全局名称,还可以是你给类起的一个名字,总之非常灵活。$concrete
描述一个类如何实例化的信息,它可以是一个返回一个类实例的匿名函数,也可以是一个可实例化类的全局名称,Container 会利用反射类自动创建其构造函数所需参数,并实例化这个类。binding
将一个 $abstract 和 $concrete 映射到一起就构成了一个 binding 。laravel 中 $abstract 到 $concrete 之间的绑定非常灵活,比如可以将一个接口绑定到一个实现了接口的类的实例,不过一般相互绑定的 $abstract 和 $concrete 也都是抽象和实例描述这样的关系。alias
Container 允许用户为 $abstract 起多个别名,甚至允许 a 是 b 别名,b 是 c 的别名这样的操作。在 laravel 常见的别名有:对于某个拥有父类或者实现某个接口的类的 $abstract , 将其父类和实现的接口都起成其别名。extender
Container 支持为一个 binding 添加 extender(扩展器/装饰器)。extender 本质上是一个闭包函数,其接收一个 $abstract 的实例作为参数,对此实例进行包装扩展后返回。 用户可以在 Container 上注册 $abstract 的 extender。这样在实例化的时候, Container 会使用这些 extender 对 $abstract 的实例进行装饰扩展。
Container 通过 extender 的设计,可以实现相当丰富的功能,比如实现装饰器模式,对 $abstract 的实例添加装饰;实现代理模式,为 $abstract 的实例构造代理;实现适配器模式,基于 $abstract 的实例构造适配器等。contextual binding
Container 支持创建基于上下文的 binding。先来解释一下什么叫做上下文,因为 Container 在构造 $abstract 的实例的时候,是利用 $abstract 对应的 $concrete 的反射类,通过解析反射类来构造 $concrete 的,如果 $concrete 的构造函数不存在参数,则可以直接构造出 $concrete; 如果 $concrete 的构造函数存在参数,且这些参数也为某些实例对象的时候,Container 会递归构造出这些实例参数,然后构造出 $concrete。在 Container 构造 $concrete 构造函数的参数对象的时候,就处于 $concrete 的上下文中。Container 通过使用 $buildStack 这个私有属性记录当前正在构造 $concrete 的堆栈来实现这个功能。
下面我们在解释什么叫做基于下上文的 binding。我们先来看一下官方文档给出的说明:
有时侯我们可能有两个类使用同一个接口,但我们希望在每个类中注入不同实现,例如,两个控制器依赖 Illuminate\Contracts\Filesystem\Filesystem 契约的不同实现。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 来实例化的,这样在上面的例子中,在不同控制器的构造函数中声明同一接口的参数,会得到不同的实例对象。
Container 源码分析
下面我们开始分析 Container 的源码。Container 类位于 laravel 框架 Illuminate\Container 命名空间下。大家也可以自己打开源码跟着分析。
Container 类的接口
下面我们来开始开始看一下 laravel 是如何实现 Container 的。首先我们来看一下 Container 类的定义
class Container implements ArrayAccess, ContainerContract
{
}
我们看到 Container 主要实现了两个接口,一个是 ArrayAccess 接口,这是一个 PHP 的内置接口,这个接口的定义如下:
interface ArrayAccess {
public function offsetExists($offset);
public function offsetGet($offset);
public function offsetSet($offset, $value);
public function offsetUnset($offset);
}
实现这个接口类的对象,我们就可以像访问数组一样访问对象了,这里我们不在详述。
Container 类实现的第二个接口是 ContainerContract ,这个接口主要定义了 Container 作为 IOC 所需要实现的方法,下面我们来看一下这个接口的定义。
interface Container
{
/**
* Determine if the given abstract type has been bound.
* 判断给定 $abstract 是否已经被绑定
*/
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.
* 创建给定 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.
* 根据容器中的绑定,给出 $abstract 的实例。
*/
public function make($abstract, array $parameters = []);
/**
* Call the given Closure / class@method and inject its dependencies.
* 调用给定匿名函数或者 class@method 描述的类的方法,并且自动注入依赖参数
*/
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 实例化的回调函数
*/
public function resolving($abstract, Closure $callback = null);
/**
* Register a new after resolving callback.
* 注册一个 $abstract 实例化之后的回调函数
*/
public function afterResolving($abstract, Closure $callback = null);
}
以上是 Container 接口中声明的方法,而我们接下来 Container 源码的分析也主要针对 Container 接口中的方法。
Container 类的主要属性和方法
在了解了 Container 的几个主要变量和概念的设计后,我们来看一下 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 (绑定关系),key 为 $abstract ,value 为程序处理过的 $concrete,为一个关联数组,模型如下:
* [
* 'concrete' => Closure,
* 'shared' => bool
* ]
*/
protected $bindings = [];
/**
* The container's shared instances.
* 可共享的 $abstract 的实例, 键为可共享的 $abstract , 值为 $abstract 对应的实例
*/
protected $instances = [];
/**
* The registered type aliases.
* $abstract 的别名, key 是别名, value 是别名对应的$abstract
* @var array
*/
protected $aliases = [];
/**
* The extension closures for services.
* 扩展的 $abstract ,为一个二维数组,key 为 $abstract, vaule 为 Closure 组成的数组
*/
protected $extenders = [];
/**
* All of the registered tags.
* 注册的 $abstract tags
* @var array
*/
protected $tags = [];
/**
* The stack of concretions currently being built.
* 当前正在创建的 concretions 的堆栈
* @var array
*/
protected $buildStack = [];
/**
* The contextual binding map.
*
* @var array
*/
public $contextual = [];
/**
* All of the resolving callbacks by class type.
* resolving 回调函数
*/
protected $resolvingCallbacks = [];
/**
* All of the after resolving callbacks by class type.
* Resolving 之后的回调函数
*/
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.
* 调用给定匿名函数或者 class@method 描述的类的方法,并且自动注入依赖参数
*/
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 作为别名对应的真正的 $abstract。
*/
public function getAlias($abstract);
}
上面的这些方法是 Container 实现其功能的主要方法。下面我会按照一定的顺序分析这些方法源代码。
Container 方法之 alias
因为 alias 是 Container 的一个基础功能,所以我们先来介绍一下 Container alias 的相关方法
/**
* 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 属于一个别名,则返回其此别名对应的值
* @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.");
}
// 地柜调用,返回别名 $abstract 最终对应的值
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 是一个数组, 按照 key 为 $abstract, value 为 $alias 提取出来并设置到容器
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 对应的 的实例,并且如果$abstract是一个别名的话,也删除这个别名的对应关系
$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,函数 compact 以变量名作为数组的键, 值作为数组的值构造数组
$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 是否是已经已经实例化, 如果是重新实例化, 并调用$abstract rebound 时的回调函数
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
/**
* Get the Closure to be used when building a type.
* 创建关于 $abstract 和 $concrete 的闭包,此闭包函数封装了创建 $concrete 对应对象的方法
* @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 : 这个函数调用的第一个参数是 $concrete ,而不是 $abstract,需要注意
return $container->$method($concrete, $parameters);
};
}
/**
* Drop all of the stale instances and aliases.
* 删除 $abstract 对应的 的实例,并且如果 $abstract 是一个别名的话,也删除这个别名的对应关系
* @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, 并调用$abstract rebound 时的回调函数
* @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,我们来看一下这个功能相关的源代码。
/**
* 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, 现在我们来介绍如何创建一个 $abstract 对应的实例。
/**
* Resolve the given type from the container.
* 创建一个 $abstract 对应的实例并返回
* @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 数组里面存储有 $abstract 里面对应的对象,则直接返回
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 数组中
if ($this->isShared($abstract)) {
$this->instances[$abstract] = $object;
}
//触发 resolving 的回调方法
$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.
* 构建 $concrete 对应的对象
* @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 是一个闭包,则直接调用这个闭包方法创建对象并返回
if ($concrete instanceof Closure) {
return $concrete($this, $parameters);
}
//创建 $concrete 的反射类,并解析反射类创建对象
$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, 并根据 tag 进行实例化,下面我们来看一下部分代码的实现
/**
* 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;
}
说明
现在我们看完了 Container 的主要源代码,现在我们主要来看一下 bind 和 make 这两个方法。
我们知道 bind 方法的 $concrete 参数可以为空,当 $concrete 为空时,框架会将 $concrete 设置成 $abstruct, 并根据 $abstruct 和 $concrete 的值构造闭包作为最终的 $concrete。
make 方法支持对没有绑定过的 $abstruct 进行构建,在 getConcrete 方法里面,如果 $abstruct 不存在绑定的 $concrete 的话,会直接返回 $abstruct。这样在 make 函数里面会调用 build 方法进行构建。
下面我们来分析一下对于绑定了字符串类型的 $concrete 且 $abstruct != $concrete
时, make 的执行流程:
bind($abstruct, $concrete)
添加 $abstruct 到 $concrete 的 binding。由于 $concrete 为字符串且$abstruct != $concrete
, 则 Container 构造关于 $abstruct 和 $concrete 的闭包,设为函数 f1,并且 $method 为 make,参数为字符串 $concrete(getClosure 方法的代码执行)。make($abstruct)
创建 $abstruct 对应的对象,获取 $abstruct 对应的 $concrete 为闭包函数 f1,则 f1 是可构建的(isBuildable 返回true),则对 f1 调用 build 方法。build($concrete)
构建 $concrete, 由于 $concrete 是闭包函数 f1,则执行这个闭包函数,针对 f1 里面的字符串 $concrete 调用 make 方法。make($concrete)
构建关于字符串 $concrete 的对象, 由于字符串 $concrete 没有在容器里面绑定过(getConcrete 方法里面isset($this->bindings[$abstract])
为 false),getConcrete 函数返回字符串 $concrete 本身,且 $concrete 是可构建的(isBuildable 返回 true),则对字符串 $concrete 调用 build 方法。build($concrete)
由于 $concrete 是字符串,则针对这个字符串构造反射类,并分析反射类构造对象。
总结
至此,我们已经分析了 Container 的主要功能和源代码。Container 是 laravel 框架的核心,理解这块的代码将对你正确灵活使用 laravel 框架具有很大的帮助。
laravel 其他功能的源码分析将陆续奉上,敬请大家期待。写文不易,如果你喜欢我的文章,还请赞赏支持一下。