Laravel 源码分析---Container

Container 简介

Container 是 laravel 框架的核心之一,laravel 框架中类的实例化、存储和管理都是由 Container 来负责的。laravel 里面的 Container 本质上是一个 IOC (Inversion of Control/控制反转) 容器,是用来实现依赖注入(DI/Dependency Injection)的。也有人把这种设计成为服务定位模式。简单的来说就是在 容器中绑定并保存各个类的抽象以及实例化的方法,在需要这个类的实例时,通过抽象访问类的实例化的方法,由容器自动实例化类,并返回。用到的技术主要是 PHP 中的反射类。

下面是两篇关于 IOC 容器和服务定位模式的文档,感兴趣可以参考一下:
1. laravel 学习笔记 —— 神奇的服务容器
2. PHP 设计模式系列 —— 服务定位器模式(Service Locator)

Container 核心变量与设计

在介绍分析 Container 的源码之前,我们先介绍一下 Container 类里面几个主要的变量和设计,理解这些,对于我们理解 Container 会有比较大的帮助。

  1. $abstract
    Container 容器的主要作用是自动化类的实例化,所以我们首先要给要实例化的类起一个名字。一般我们会用类的带有命名空间的类名作为要实例化的类的名字。但是 laravel 的 Container 提供了更加灵活的描述:$abstract 。$abstract 是要实例化类的抽象,他可以是类的全局名称,也可以是接口的全局名称,还可以是你给类起的一个名字,总之非常灵活。

  2. $concrete
    描述一个类如何实例化的信息,它可以是一个返回一个类实例的匿名函数,也可以是一个可实例化类的全局名称,Container 会利用反射类自动创建其构造函数所需参数,并实例化这个类。

  3. binding
    将一个 $abstract 和 $concrete 映射到一起就构成了一个 binding 。laravel 中 $abstract 到 $concrete 之间的绑定非常灵活,比如可以将一个接口绑定到一个实现了接口的类的实例,不过一般相互绑定的 $abstract 和 $concrete 也都是抽象和实例描述这样的关系。

  4. alias
    Container 允许用户为 $abstract 起多个别名,甚至允许 a 是 b 别名,b 是 c 的别名这样的操作。在 laravel 常见的别名有:对于某个拥有父类或者实现某个接口的类的 $abstract , 将其父类和实现的接口都起成其别名。

  5. extender
    Container 支持为一个 binding 添加 extender(扩展器/装饰器)。extender 本质上是一个闭包函数,其接收一个 $abstract 的实例作为参数,对此实例进行包装扩展后返回。 用户可以在 Container 上注册 $abstract 的 extender。这样在实例化的时候, Container 会使用这些 extender 对 $abstract 的实例进行装饰扩展。
    Container 通过 extender 的设计,可以实现相当丰富的功能,比如实现装饰器模式,对 $abstract 的实例添加装饰;实现代理模式,为 $abstract 的实例构造代理;实现适配器模式,基于 $abstract 的实例构造适配器等。

  6. 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 的实例
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值