xiaohuilam / laravel

Laravel 深入详解 —— 源代码解析,新手进阶指南
433 stars 80 forks source link

12. Facade 机制 #12

Open xiaohuilam opened 5 years ago

xiaohuilam commented 5 years ago

总结: Facade 就是一个标记便捷的调用容器盛放对象的方法的设计。

一般的,Facade 类都继承于 Illuminate\Support\Facades\Facade

比如 Illuminate\Support\Facades\Gate 这个门面:

<?php
namespace Illuminate\Support\Facades;

use Illuminate\Contracts\Auth\Access\Gate as GateContract;

class Gate extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return GateContract::class;
    }
}

代码中我们只需要留意 getFacadeAccessor 方法是在 Illuminate\Support\Facades\Gate 中定义的就好了。 我们来分析 Illuminate\Support\Facades\Facade

魔术方法 part 1

首先我们来看末尾的 __callStatic 方法: https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L206-L224

还记得 Facade 类我们是怎么调用的吗?

静态调用!比如 Gate::allows()

因为在 Facade 类中定义了 __callStatic 魔术方法,所以只要静态调用了里面没有提供的方法,都会走到魔术方法里面。 这个魔术方法内部有一个取出实例的逻辑:

$instance = static::getFacadeRoot();

代码位于 https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L123-L131

前面,我们留意了 static::getFacadeAccessor() 是位于 Illuminate\Support\Facades\Gate 中的,而 static::getFacadeAccessor() 运行后作为参数给了 static::resolveFacadeInstance(),后者实现为 https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L145-L162

static::$app 就是容器对象 https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L11-L16

这句 https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L161 尝试从容器对象 数组取 操作,就是调用了 make https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Container/Container.php#L1208-L1211

就是从容器取出对象。

总结 getFacadeRoot 的逻辑大概为

  1. 判断 static::getFacadeAccessor() 返回是否对象,如果对象则直接使用。
  2. Facade::$resolvedInstance 数组中尝试根据 $name 取出对象,避免多次调用时,每次都取。
  3. Illuminate\Foundation\Application 容器取出对象。

魔术方法 part 2

拿到 $instance 后,先验证 $instance 是否为空。如果是就报错 A facade root has not been set.https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L219-L221

所以如果遇到 A facade root has not been set. 这个错误,一般都是 a. 相应的服务提供者未注册; b. 对象绑定到容器的别名跟 static::getFacadeAccessor() 返回不一致。

最后,直接把方法和参数穿透到背后的 $instance 去执行。 https://github.com/xiaohuilam/laravel/blob/ca57c288f7825c42550333b613440cb4e824b3ad/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php#L223

Laravel 有很多 facade 门面,背后穿透的不只一个类(譬如 DBViewSession),这是怎么回事呢? 请看 15. Laravel 神奇的 Manager 类