xiaohuilam/laravel

12. Facade 机制

Opened this issue · 0 comments

总结: 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 方法:

/**
* Handle dynamic, static calls to the object.
*
* @param string $method
* @param array $args
* @return mixed
*
* @throws \RuntimeException
*/
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}

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

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

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

$instance = static::getFacadeRoot();

代码位于

/**
* Get the root object behind the facade.
*
* @return mixed
*/
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}

前面,我们留意了 static::getFacadeAccessor() 是位于 Illuminate\Support\Facades\Gate 中的,而 static::getFacadeAccessor() 运行后作为参数给了 static::resolveFacadeInstance(),后者实现为

/**
* Resolve the facade root instance from the container.
*
* @param string|object $name
* @return mixed
*/
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}

static::$app 就是容器对象

/**
* The application instance being facaded.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected static $app;

这句

return static::$resolvedInstance[$name] = static::$app[$name];

尝试从容器对象 数组取 操作,就是调用了 make
public function offsetGet($key)
{
return $this->make($key);
}

就是从容器取出对象。

总结 getFacadeRoot 的逻辑大概为

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

魔术方法 part 2

拿到 $instance 后,先验证 $instance 是否为空。如果是就报错 A facade root has not been set.

if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}

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

最后,直接把方法和参数穿透到背后的 $instance 去执行。

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