为什么要使用Facade

Facade 提供一个更简洁的方式去访问【已经在容器注册的对象】的方法。

那”更简洁”的方式是什么呢,是一种静态访问的方式。

# 使用make 去访问注册日志对象的info方法
$container->make('log')->info('message')

# 使用arrayaccess的方式
$container["log"]->info('message')

# 使用Facade访问Logger对象的info方法, 不需要使用容器去获取一个对象。
Log::info('message');

记住了,这就是Facade的本质, 使用【静态方式】访问一个对象的方法。

当然还有一个更大的好处,我会在文章的最后指出来。

怎么实现静态访问一个对象的任意方法

抛开Laravel Facade.如果我们自己设计一个”Facade”我们应该怎么做呢.

设想一下,逻辑可能是这样:

通过静态访问的方式,访问一个对象的方法method,但是这个方法不存在,于是可能访问到某一个魔术方法,在这个魔术方式呢,实例化了某一个真正的类,通过这个类去访问method。

这样就是实现了最基本的Facade方式。

事实上laravel 也是这样做的。这个魔术方法就是 __callStatic().

核心代码如下:

use IlluminateSupportFacadesFacade;

# laravel中所有的Facade都继承了Facade对象
class LogFacade extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'log';
    }
}

# 在Facade抽象类有有一个魔术方法__callStatic
abstract class Facade
{
    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);
    }
}

于是我们就可以这样访问对象:


LogFacade::info('message')

但是注意啦,LogFacade::info('message') 和上文提到了Log::info('message') 不一样呀,

怎么使用实现【访问一个”Log”对象,实际上是访问的”LogFacade”对象】呢?

答案是 class_alias():


# 使用alias给LogFacade对象取了一个别名Log
class_alias('Log', 'LogFacade');

自此,我们基本了解了Facade的实现机制啦,但还有最后一个疑问:

【访问Log对象,怎么就可以访问到在容器里面注册的log对象的】?

如何从Log::info 到 $container[‘log’]->info

再回到__staticMethod方法中,有一个方法getFacadeRoot, 这个方法就是用来【获取一个真正的对象


        public static function getFacadeRoot()
        {
            # getFacadeAccessor 就是获取对象在container注册的下标
            return static::resolveFacadeInstance(static::getFacadeAccessor());
        }

    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        # 核心在这里:$app对象就是container哟,这里就是通过container arrayAccess的方式获取一个注册的对象
        return static::$resolvedInstance[$name] = static::$app[$name];
    }

容器所有问题基本解答完毕,static::$app这个属性是什么使用初始化的呢,实在laravel启动的时候调用了setFacadeApplication


Facade::setFacadeApplication($this);

public static function setFacadeApplication($app)
{
    static::$app = $app;
}

那现在剩下最后一个问题 【什么容器还有什么好处】

答案是【便于单测】

容器如何便于单测

比如有下面一段代码

class Event
{
    public function fire($eventName)
    {
        # 这里使用了日志对象记录fire的事件,但是测试fire方法的时候,因为某些原因不能使用log对象,必须mock使用的log对象
        Log::info("event");
    }
}

Facace提供了getMockableClass 可以mock一个对象

public function testFire()
{
    # 创建一个mock对象
    Event::shouldReceive('info')->once()->with('event');

}

到目前为止了,Facade已经全部讲完了,了解Facade的原理,除了Laravel自定义的Facade以外。

也可以给自己定义的类去实现Facade的访问方式

你应该知道怎么做了吧