Laravel IoC, ServiceProvider, Facades 原理分析

IoC 是 Inversion Of Control 的缩写,Illuminate\Container\Container 是实现 IoC 的核心类,IoC 允许你将系统中常用类(class) 的创建(new)和实例管理工作交给一个容器类(container),例如 Laravel 中的 Illuminate\Foundation\Application 就是继承自 Illuminate\Container\Container,IoC 的特点是:

  • 使用者无需知道类的头文件,甚至不必知道类的完整名称,直接 $container[‘db’] 即可获得该类实例;
  • 消除诸多散布在项目中的 require include 语句;
  • 类实例统一管理,允许全局共享同一类实例,或者不共享每次都创建新实例;

注:假设 $container 是容器类实例,db 表示数据库类的简称

IoC 原理分析

以下假设 container 对象实例为 $app

  1. Illuminate\Container\Container 是 IoC 的核心类

    1. 它通过两个核心方法 bind() 和 make() 来实现 IoC 功能;

    2. bind() 方法用于告诉 container 如何创建要被管理起来的类;

      1. bind($abstract, $concrete = null, $shared = false)

        1. $abstract 是被管理类的简称,例如 ‘db’

        2. $concrete 有三种可能

          1. 匿名函数:该函数创建并返回类实例

          2. 字符串:…

          3. NULL:…

    2. bind 有个 shared 参数,shared = TRUE 表明该类的实例只创建一次,缓存在 container 的成员变量 instances[] 中,该实例被全局共享;

    3. singleton() 方法就是调用 shared = TRUE 的 bind()

3. make() 方法用于让使用者创建类实例;

4. 语法糖,它实现 PHP ArrayAccess 接口,以便使用者能像数组一样从 container 访问一个类的实例,

    1. 可以用 $app['db'] 来访问数据库Connection 类的实例,

    2. 并且此实例对全局还是共享的,创建一次就会自动缓存在 container 的成员变量 instances[] 中;

    3. $app['db'] 实际上调用的 make() 方法

    4. $app['db'] = ...; 实际上调用的 bind() 方法

ServiceProvider – 配置好的 bind

综上所述,Container::bind() 让某个类能通过 IoC 创建,而不必关心该类的依赖文件甚至不用知道类名bind() 应该先于 make() 方法告诉 container 如何创建要被管理的类,Laravel中那些能够被直接使用的类(例如 $app[‘db’])又是何时 bind() 呢?他们来自于 Illuminate\Support\ServiceProvider 的贡献:

  1. 所有希望享受 IoC 好处的类可以直接用 Container::bind() 方法来向 Container 注册自己的名字和创建方法。但要管理的类多了,调用 bind 的语句也会变多,每个语句的所在的位置也可能会散落在代码各处,为了统一管理之,ServiceProvider 出现了(注意 ServiceProvider 不是 IoC 的必需品,通过 Container::bind() 和 Container::make() 就已经实现 IoC**),只是为了管理众多 Container::bind(),让代码更易于管理,请看 app/config/app.php 中的 ‘providers’ 部分,所有启动时自动 bind 的 ServiceProvider。
  1. 为了使用 ServiceProvider 来管理 Container::bind(),所有希望享受 IoC 好处的类都需要实现一个自己的 ServiceProvider 类,

    1. 它必须继承自 Illuminate\Support\ServiceProvider,例如 $app[‘db’] 中的 db 实现了 Illuminate\Database\DatabaseServiceProvider

    2. ServiceProvider 只用实现一个名为 register 的方法,该方法中执行 Container::bind()

    3. 在 app/config/app.php 的 ‘providers’ 部分配置你的 ServiceProvider,这样 Laravel 每次启动时都会将该类自动加入 IoC 的管理

Facade 语法糖,DB::table() 中 DB:: … 前缀是如何实现的

这也不是必需品,但会让生活更美好

为了不必每次都敲 $app[‘db’],也为了尽量隐藏 $app 和类似于 ‘db’ 这样的简称,Illuminate\Support\Facades\Facade 出现了,每个希望得到 Facade 好处的类都要:

  1. 先注册 IoC,Container 必须是 Application,也就是说需要在使用 Facade 之前对该类执行 $app->bind(),当然,推荐亲实现一个 ServiceProvider 并配置 app/config/app.php
  1. 实现自己的 Facade(必须继承自 Illuminate\Support\Facades\Facade),仅实现要给方法 getFacadeAccessor() 即可,此方法返回该类的简称即可,例如 Illuminate\Support\Facades\DB 的实现代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php namespace Illuminate\Support\Facades;

    class DB extends Facade {

    /**
    * Get the registered name of the component.
    *
    * @return string
    */
    protected static function getFacadeAccessor() { return 'db'; }

    }
  2. 原理

    1. Illuminate\Support\Facades\Facade 实现了 PHP __callStatic,

    2. 当用户调用形如 DB::method() 时,会去调用 Facade 的 __callStatic,

    3. callStatic 中,先根据 DB 的 getFacadeAccessor 得到简称 ‘db’,进而根据 $app[‘db’] 得到 DB 的实例,然后调用此实例的 method,因此看上去调用的是 DB (Facade) 的 method(),实际上被 callStatic 转换成调用 DB -> ‘db’ -> $app[‘db’](Database Connection 实例)的 method

  3. 至此就可以用 DB:: … 来访问先前在 IoC 中注册的类实例