Laravel 的 Facade 實現原理

大張發表於2017-07-28

Laravel的Facade是什麼?

Facade其實是一個容器中類的靜態代理,他可以讓你以靜態的方式來呼叫存放在容器中任何物件的任何方法。舉個例子:

use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

實際上,Cache類並沒有一個get靜態方法,但是卻可以被呼叫,這就是Facade的靜態代理功能。

如何使用Facade?

假如我們自定義了一個類並且放入容器中後,該如何以Facade的方式來呼叫呢?很簡單:

use Illuminate\Support\Facades\Facade;

class Cache extends Facade
{
    /**
     * 獲取元件註冊名稱
     *
     * @return string
     */
    protected static function getFacadeAccessor() { 
        return 'cache'; 
    }
}

只要定義一個類讓他繼承自Illuminate\Support\Facades\Facade,並且實現一個抽象方法getFacadeAccessor即可。這個方法只要返回一個字串,就是返回服務容器繫結類的別名。其實,通過原始碼可以知道,物件不一定要放到容器中,可以直接在這裡返回也是可以的,下面會說道:

use Illuminate\Support\Facades\Facade;
use Cache;

class Cache extends Facade
{
    /**
     * 獲取元件註冊名稱
     *
     * @return string
     */
    protected static function getFacadeAccessor() { 
        return new Cache; 
    }
}

如何實現的呢?

我們來看看Illuminate\Support\Facades\Facade的原始碼:

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

    /**
     * The resolved object instances.
     *
     * @var array
     */
    protected static $resolvedInstance;

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

    /**
     * Get the registered name of the component.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    protected static function getFacadeAccessor()
    {
        throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
    }

/**
     * 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];
    }

    /**
     * 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的程式碼不止以上這些,我抽出了核心的部分。直接來看看__callStatic這個方法:

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);     //呼叫方法
    }

這是PHP中的一個魔術方法,當以靜態的方式呼叫一個不存在的方法時,該方法會被呼叫。程式碼很簡單,就是解析例項呼叫方法。不過這裡要注意一個就是這裡使用的是static關鍵字而不是self關鍵字,這涉及到一個後期的靜態繫結,可以看看文件:http://php.net/manual/zh/language.oop5.lat...

再來看看getFacadeRoot的程式碼,這個方法就是從容器中解析出物件:

public static function getFacadeRoot(){
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}
protected static function getFacadeAccessor(){
     throw new RuntimeException('Facade does not implement getFacadeAccessor method.');
}
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];
}

其中的getFacadeAccessor這個方法必須被重寫,否者就會丟擲異常。然後在resolveFacadeInstance這個方法中會先判斷是否是一個物件,如果是的話就直接返回。所以上文說的getFacadeAccessor這個方法直接返回一個物件也是可以的,奧祕就在這。
然後會去判斷需要解析的物件是否已經解析過了,如果解析過了就直接返回,否則會從容器中去解析再返回,這樣不僅僅實現了單例,而且還可以提升效能。
得到物件後,就是直接通過物件來呼叫方法了:

$instance->$method(...$args);     //呼叫方法

總結

其實LaravelFacade原理不難,但是在研究的過程是可以發現很多文件沒有提供的內容的,也能進一步提高我們閱讀原始碼的能力。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章