首頁  >  文章  >  後端開發  >  Laravel核心解讀Facades

Laravel核心解讀Facades

不言
不言原創
2018-07-06 14:45:451480瀏覽

這篇文章主要介紹了Laravel核心解讀Facades,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下

什麼是Facades

#Facades是我們在Laravel應用開發中使用頻率很高的一個元件,叫元件不太合適,其實它們是一組靜態類別介面或者說代理,讓開發者能簡單的存取綁定到服務容器裡的各種服務。 Laravel文件中對Facades的解釋如下:

Facades 為應用程式的 服務容器 中可用的類別提供了一個「靜態」介面。 Laravel 本身附帶許多的 facades,甚至你可能在不知情的狀況下已經在使用他們! Laravel “facades”作為在服務容器內基類的「靜態代理」,擁有簡潔、易表達的語法優點,同時維持著比傳統靜態方法更高的可測試性和靈活性。

我們常用的Route就是一個Facade, 它是\Illuminate\Support\Facades\Route類別的別名,這個Facade類別代理的是註冊到服務容器裡的 router服務,所以透過Route類別我們就能夠方便地使用router服務中提供的各種服務,而其中涉及到的服務解析完全是隱式地由Laravel完成的,這在一定程度上讓應用程式代碼變的簡潔了不少。下面我們會大概看一下Facades從被註冊進Laravel框架到被應用程式使用這中間的流程。 Facades是和ServiceProvider緊密配合的所以如果你了解了中間的這些流程對開發自訂Laravel元件會很有幫助。

註冊Facades

說到Facades註冊又要回到再介紹其它核心組成時提到過很多次的Bootstrap階段了,在讓請求通過中間件和路由之前有一個啟動應用程式的過程:

//Class: \Illuminate\Foundation\Http\Kernel
 
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
}

//引导启动Laravel应用程序
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        /**依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数
         $bootstrappers = [
               'Illuminate\Foundation\Bootstrap\DetectEnvironment',
             'Illuminate\Foundation\Bootstrap\LoadConfiguration',
              'Illuminate\Foundation\Bootstrap\ConfigureLogging',
             'Illuminate\Foundation\Bootstrap\HandleExceptions',
             'Illuminate\Foundation\Bootstrap\RegisterFacades',
             'Illuminate\Foundation\Bootstrap\RegisterProviders',
             'Illuminate\Foundation\Bootstrap\BootProviders',
            ];*/
            $this->app->bootstrapWith($this->bootstrappers());
    }
}

在啟動應用程式的過程中Illuminate\Foundation\Bootstrap\RegisterFacades這個階段會註冊應用程式裡用到的Facades。

class RegisterFacades
{
    /**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        Facade::clearResolvedInstances();

        Facade::setFacadeApplication($app);

        AliasLoader::getInstance(array_merge(
            $app->make('config')->get('app.aliases', []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
}

在這裡會透過AliasLoader類別的實例將為所有Facades註冊別名,Facades和別名的對應關係存放在config/app.php檔案的$aliases陣列中

'aliases' => [

    'App' => Illuminate\Support\Facades\App::class,
    'Artisan' => Illuminate\Support\Facades\Artisan::class,
    'Auth' => Illuminate\Support\Facades\Auth::class,
    ......
    'Route' => Illuminate\Support\Facades\Route::class,
    ......
]

看看AliasLoader裡是如何註冊這些別名的

// class: Illuminate\Foundation\AliasLoader
public static function getInstance(array $aliases = [])
{
    if (is_null(static::$instance)) {
        return static::$instance = new static($aliases);
    }

    $aliases = array_merge(static::$instance->getAliases(), $aliases);

    static::$instance->setAliases($aliases);

    return static::$instance;
}

public function register()
{
    if (! $this->registered) {
        $this->prependToLoaderStack();

        $this->registered = true;
    }
}

protected function prependToLoaderStack()
{
    // 把AliasLoader::load()放入自动加载函数队列中,并置于队列头部
    spl_autoload_register([$this, 'load'], true, true);
}

透過上面的程式碼段可以看到AliasLoader將load方法註冊到了SPL __autoload函數隊列的頭部。看看load方法的原始碼:

public function load($alias)
{
    if (isset($this->aliases[$alias])) {
        return class_alias($this->aliases[$alias], $alias);
    }
}

在load方法裡$aliases設定裡的Facade類別建立了對應的別名,例如當我們使用別名類別Route時PHP會透過AliasLoader的load方法為把Illuminate\Support\Facades\Route::class類別建立一個別名類別Route,所以我們在程式裡使用別Route 其實使用的就是`Illuminate\Support\Facades\Route類別。

解析Facade代理的服務

把Facades註冊到框架後我們在應用程式裡就能使用其中的Facade了,例如註冊路由時我們常用Route::get ('/uri', 'Controller@action);,那麼Route是怎麼代理到路由服務的呢,這就涉及到在Facade裡服務的隱式解析了, 我們看一下Route類別的原始碼:

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

只有簡單的一個方法,並沒有get, post, delete等那些路由方法, 父類裡也沒有,不過我們知道呼叫類別不存在的靜態方法時會觸發PHP的__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);
}

//获取Facade根对象
public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(static::getFacadeAccessor());
}

/**
 * 从服务容器里解析出Facade对应的服务
 */
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];
}

透過在子類別Route Facade裡設定的accessor(字串router) , 從服務容器解析出對應的服務,router服務是在應用程式初始化時的registerBaseServiceProviders階段(具體可以看​​Application的建構方法)被\Illuminate\Routing\RoutingServiceProvider註冊到服務容器裡的:

class RoutingServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->registerRouter();
        ......
    }

    /**
     * Register the router instance.
     *
     * @return void
     */
    protected function registerRouter()
    {
        $this->app->singleton('router', function ($app) {
            return new Router($app['events'], $app);
        });
    }
    ......
}

router服務對應的類別就是\Illuminate\Routing\Router, 所以Route Facade實際上代理的就是這個類,Route::get實際上呼叫的是 \Illuminate\Routing\Router物件的get方法

/**
 * Register a new GET route with the router.
 *
 * @param  string  $uri
 * @param  \Closure|array|string|null  $action
 * @return \Illuminate\Routing\Route
 */
public function get($uri, $action = null)
{
    return $this->addRoute(['GET', 'HEAD'], $uri, $action);
}

補充兩點:

  1. 解析服務時用的static::$app是在最開始的RegisterFacades裡面設定的,它引用的是服務容器。

  2. static::$app['router'];以陣列存取的形式能夠從服務容器解析出router服務是因為服務容器實作了SPL的ArrayAccess介面, 對此沒有概念的可以看下官方文件ArrayAccess

總結

透過梳理Facade的註冊和使用流程我們可以看到Facade和服務提供者(ServiceProvider)是緊密配合的,所以如果以後自己寫Laravel自訂服務時除了透過元件的ServiceProvider將服務註冊進服務容器,還可以在元件中提供一個Facade讓應用程式能夠方便的存取你寫的自訂服務。

以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!

相關推薦:

Laravel中間件(Middleware)的解讀

#Laravel路由(Route)解讀

#

以上是Laravel核心解讀Facades的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn