Laravel源码--Facade门面

文章简单介绍了 Facade 门面,总结了其工作原理,并介绍了 Facade 的使用方法。

介绍

Facade 为应用服务容器中绑定的类提供了一个“静态”接口。Laravel 内置了很多 Facade,几乎可以用来访问 Laravel 中所有的服务。Laravel 的 Facade 作为服务容器中底层类的“静态代理”,相比于传统静态方法,Facade 提供了简洁且丰富的语法同时,还带来了更好的可测试性和扩展性。Laravel 的所有 Facade 都定义在 “Illuminate\Support\Facades”命名空间下,我们也可以自己定义新的 Facade。

Facade 加载原理

为了更好的理解 Facade 的工作原理,我们从以下几个方面进行介绍:

  • Facade 配置文件
  • 加载 RegisterFacades 类
  • 注册 Facade 服务
  • 解析 Facade 服务

Facade配置文件

Facade 的配置文件保存在 config/app.php 中,主要用来保存所有的 Facade 别名,也即是把 Facade 和别名的对应关系存放在 config/app.php文件中的 aliases 数组里。aliases 数组的形式如下:


    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Auth' => Illuminate\Support\Facades\Auth::class,
        'Blade' => Illuminate\Support\Facades\Blade::class,
        'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
        'Bus' => Illuminate\Support\Facades\Bus::class,
        'Cache' => Illuminate\Support\Facades\Cache::class,
        'Config' => Illuminate\Support\Facades\Config::class,
        'Cookie' => Illuminate\Support\Facades\Cookie::class,
        'Crypt' => Illuminate\Support\Facades\Crypt::class,
        'DB' => Illuminate\Support\Facades\DB::class,
        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
        'Event' => Illuminate\Support\Facades\Event::class,
        'File' => Illuminate\Support\Facades\File::class,
        'Gate' => Illuminate\Support\Facades\Gate::class,
        'Hash' => Illuminate\Support\Facades\Hash::class,
        'Lang' => Illuminate\Support\Facades\Lang::class,
        'Log' => Illuminate\Support\Facades\Log::class,
        'Mail' => Illuminate\Support\Facades\Mail::class,
        'Notification' => Illuminate\Support\Facades\Notification::class,
        'Password' => Illuminate\Support\Facades\Password::class,
        'Queue' => Illuminate\Support\Facades\Queue::class,
        'Redirect' => Illuminate\Support\Facades\Redirect::class,
        'Redis' => Illuminate\Support\Facades\Redis::class,
        'Request' => Illuminate\Support\Facades\Request::class,
        'Response' => Illuminate\Support\Facades\Response::class,
        'Route' => Illuminate\Support\Facades\Route::class,
        'Schema' => Illuminate\Support\Facades\Schema::class,
        'Session' => Illuminate\Support\Facades\Session::class,
        'Storage' => Illuminate\Support\Facades\Storage::class,
        'URL' => Illuminate\Support\Facades\URL::class,
        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,

    ],

aliases 数组遵循(别名 => Facade 类)的数据格式,当接受一个 HTTP 请求时注册 aliases 数组,但是并没有实际的进行别名绑定,只是通过 spl_autoload_register() 函数进行了注册。只有在遇到尚未发现的别名时,才去真正绑定别名,因此,别名绑定是“懒惰”加载,并不影响程序的性能。

加载 RegisterFacades 类

Facade 服务的注册工作由“Illuminate\Foundation\Bootstrap\RegisterFacades”类完成,RegisterFacades 类是在启动应用程序的过程中加载的。因此,我们首先来看index.php文件

make(Illuminate\Contracts\Http\Kernel::class);


/**
 * laravel中所有功能服务的注册加载
 * 通过调用kernel的handle方法,返回一个response
 * Illuminate\Http\Request::capture():通过全局$_SERVER数组构造一个Http请求的语句
 */
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

//把response内容发到浏览器
$response->send();

//执行请求生命周期中的后续操作
$kernel->terminate($request, $response);

RegisterFacades 类的加载是在 $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ) 语句进行的。该语句主要用于 Laravel各个功能服务的注册启动。

$request = Illuminate\Http\Request::capture() 

该语句是 Laravel 通过全局 $_SERVER 数组构造一个 HTTP 请求的语句,接下来会调用 HTTP 的内核函数 handle(),RegisterFacades 类的加载即是在内核函数 handle() 中进行。

//Illuminate\Foundation\Http\Kernel.php
    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function handle($request)
    {
        try {
            //允许在表单中使用delete、put等类型的请求
            $request->enableHttpMethodParameterOverride();

            //将请求发给中间件、路由处理
            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));

            $response = $this->renderException($request, $e);
        }

        $this->app['events']->dispatch(
            new Events\RequestHandled($request, $response)
        );

        return $response;
    }

接下来,我们查看 sendRequestThroughRouter() 函数。

//Illuminate\Foundation\Http\Kernel.php
     /**
     * Send the given request through the middleware / router.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendRequestThroughRouter($request)
    {
        //设置request请求的对象实例
        $this->app->instance('request', $request);

        //清楚'request'对应的服务对象实例
        Facade::clearResolvedInstance('request');

        /**
         * 依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数,做了几件准备事情:
         * 1. 配置加载 LoadConfiguration
         * 2.日志配置 ConfigureLogging
         * 3.异常处理 HandleException
         * 4.注册Facades RegisterFacades
         * 5.注册Providers RegisterProviders
         * 6.启动Providers BootProviders
         */
        $this->bootstrap();

        return (new Pipeline($this->app))       //请求的分发
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

函数首先在 Laravel 容器中设置了 request 请求的对象实例,并且清楚了 Facade 中 request 请求对应的服务对象实例。接下来我们来看 bootstrap() 函数

//Illuminate\Foundation\Http\Kernel.php
     /**
     * Bootstrap the application for HTTP requests.
     * 启动引导(reauests驱动)
     *
     * @return void
     */
    public function bootstrap()
    {
        //判断引导是否已经被启动
        if (! $this->app->hasBeenBootstrapped()) {
            /**
             * 使用Application类的bootstrapWith()函数启动引导
             * 参数:bootstrapers数组。
             */
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

     /**
     * The bootstrap classes for the application.
     * 引导类,起引导作用的类
     *
     * @var array
     */
    protected $bootstrappers = [
        //载入服务器环境变量(.env文件)
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        //载入配置信息(config目录)
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        //配置异常处理
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        //注册Facades
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        //注册Providers
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        //启动Providers
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];

bootstrap() 函数首先判断引导类是否已经启动,如果没有启动,则启动引导类。引导类数组包含 RegisterFacades 类。启动引导类是调用“Illuminate\Foundation\Application”类中的 bootstrapWith() 函数。

//Illuminate\Foundation\Application
     /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            /**
             * 告知将要启动该bootstrapper
             * $this['events']:对应绑定的类为 Dispatcher类(Illuminate\Events\Dispatcher)
             * [$this]:数组,只有一个Application类元素
             */
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            //解析每个 $bootstrapper,并调用他们自身的 bootstrap() 函数
            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

bootstrapWith() 函数中的 $this->make($bootstrapper)->bootstrap($this) 语句即是解析出 $bootstrappers 数组中的每个类,并调用相应的 bootstrap() 函数。因此,RegisterFacades 类也即是在此时调用。

注册 Facade 服务

下面我们来看 RegisterFacades 类。

//Illuminate\Foundation\Bootstrap\RegisterFacades.php
make('config')->get('app.aliases', []),
            $app->make(PackageManifest::class)->aliases()
        ))->register();
    }
}

可以看出,RegisterFacades 类只有一个 bootstrap() 函数,该函数主要完成以下功能:

  • 清楚所有 Facade 对象服务实例
  • 设置门面对象的 Application 实例
  • 通过 AliasLoader 类为所有的 Facade 注册别名

接下来我们查看 AliasLoader 类如何注册别名。

//llluminate\Foundation\AliasLoader.php
     /**
     * Get or create the singleton alias loader instance.
     * 获取或创建 AliasLoader 单例实例。
     *
     * @param  array  $aliases
     * @return \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;
    }

     /**
     * Register the loader on the auto-loader stack.
     * 将加载器注册到自动加载中
     *
     * @return void
     */
    public function register()
    {
        if (! $this->registered) {
            $this->prependToLoaderStack();

            $this->registered = true;
        }
    }

     /**
     * Prepend the load method to the auto-loader stack.
     * 设置自动加载方法
     *
     * @return void
     */
    protected function prependToLoaderStack()
    {
        // 把AliasLoader::load()放入自动加载函数队列中,并置于队列头部
        spl_autoload_register([$this, 'load'], true, true);
    }

AliasLoader 类首先调用 getInstance() 函数获得 AliasLoader 的单例实例,然后调用 register() 函数将 AliasLoader 类中的 load() 函数注册到 SPL __autoload 函数队列的头部。

//llluminate\Foundation\AliasLoader.php
     /**
     * Load a class alias if it is registered.
     *
     * @param  string  $alias
     * @return bool|null
     */
    public function load($alias)
    {
        if (static::$facadeNamespace && strpos($alias, static::$facadeNamespace) === 0) {
            $this->loadFacade($alias);

            return true;
        }

        if (isset($this->aliases[$alias])) {
            //注册别名
            return class_alias($this->aliases[$alias], $alias);
        }
    }

load() 函数的上半部分是 laravel5.4 版本新出的功能,叫做实时门面服务。下半部分是 class_alias() 函数利用别名映射数组将别名映射到真正的门面类中去。例如,我们使用别名类 Cache 时,程序会通过 AliasLoader 类的 load() 函数为“Illuminate\Support\Facades\Cache::class”类创建一个别名 Cache,所以我们在程序里使用别名 Cache 就是使用“Illuminate\Support\Facades\Cache”类。

解析 Facade 服务

以 Cache 为例,我们讲一下 Facade 的使用。我们首先来看一下 Cache Facade 类。

该类内部只有一个 getFacadeAccessor() 函数,该方法的功能是获取已经注册组件的名称。其实每个门面类只是重写了基类(Facade)的 getFacadeAccessor() 方法。

//Illuminate\Support\Facades\Facade.php
     /**
     * 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);
    }

当运行 Cache::get() 函数时,门面类 Cache 类里并没有 get() 函数,此时 PHP 会自动调用魔术函数 __callStatic()。魔术函数 __callStatic() 首先获得 Facade 的服务对象实例,然后调用对象的 get() 函数。下面我们来看如何获得 Facade 的对象实例。

//Illuminate\Support\Facades\Facade.php
     /**
     * 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.
     * 创建并返回 $name 对应的对象实例
     *
     * @param  string|object  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        //如果是对象,则直接返回
        if (is_object($name)) {
            return $name;
        }

        //$resolvedInstance数组中 $name 对应的对象实例是否存在,如果存在,直接返回
        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }

        /**
         * 具体的创建工作由Application类对象进行,所以$name需要在Application中进行过绑定
         */
        return static::$resolvedInstance[$name] = static::$app[$name];
    }

基类中的 getFacadeRoot() 函数首先调用了 getFacadeAccessor() 函数,也即是 Cache 类重写的 getFacadeAccessor() 函数,如果调用的是基类的 getFacadeAccessor() 函数则报错。然后调用 resolveFacadeInstance() 函数获得“cache”对应的服务实例。resolveFacadeInstance() 函数主要是从 Laravel 服务容器 static::$app[$name] 中解析出相应的服务。“cache”服务是应用程序初始化时,在类“Illuminate\Foundation\Bootstrap\RegisterProviders”中被注册到容器中的。
因为在程序启动时,将 Facade 的别名通过 RegisterFacades 类进行了加载注册,所以可以直接使用别名 Cache 代替“Illuminate\Support\Facades\Cache”。
其实,Cache::get('key') 的写法等价于以下写法。

$value = $app->make('cache')->get('key');

建立 Facade

建立自己的 Facade,需要做以下几方面的工作:

  • 创建自定义类(Facade 的实现类)
  • 创建 Facade 类
  • Facade 类别名设置
  • 创建 ServiceProvider
  • 加载 ServiceProvider

创建自定义类(Facade 的实现类)

我们现在 app\Facades 目录下创建 PaymentGateway\Payment 类。

创建 Facade 类

创建自定义的 Facade 类,该类继承基类 Facade,并重写基类的 getFacadeAccessor() 函数。

Facade 类别名设置

我们可以为自定义的 Facade 类起个别名,只需要将别名和类的对应关系添加到 config/app.php 配置文件中的 aliases 数组即可。

//config/app.php

  'aliases' => [
        ...
        'Payment' => App\Facades\PaymentGateway\Facade\Payment::class,
        ...
    ],

创建 ServiceProvider

在 app\Providers 文件夹下创建 PaymentServiceProvider,提供“payment”服务。

app->singleton('payment', function ($app) {
            return new Payment();
        });

    }

    /**
     * 设置了延迟启动,需要重写 providers 函数
     * 该方法返回provider中注册的服务容器绑定别名
     */
    public function provides()
    {
        return ["payment"];
    }
}

其中,$defer 设置为 true,是为了提供延迟加载功能,provides() 函数也是为了辅助延迟加载功能而重写的。register() 函数中的服务名“payment”应该与 Facade 类中 getFacadeAccessor() 函数返回值保持一致。

加载 ServiceProvider

为了能加载我们创建的 PaymentServiceProvider,需要在 config/app.php 配置文件中 providers 数组内添加 PaymentServiceProvider。

//config/app.php

    'providers' => [
        ...
        App\Providers\PaymentServiceProvider::class,
        ...
 ],

最后,使用如下代码即可以调用 Facade 类。

$value = Payment::get();

总结

之所以能够简单的使用 Cache::get() 操作,是因为程序启动时自动进行了别名注册,使用 Cache 等价于“Illuminate\Support\Facades\Cache”。另外,程序在基类 Facade 中自动调用魔术函数 __callStatic() 进行了动态绑定。

门面类列表

下面列出了每个 Facade 、对应的底层类以及服务容器绑定键。

门面 Facade 类 Class 服务容器绑定
App Illuminate\Foundation\Application app
Artisan Illuminate\Console\Application artisan
Auth Illuminate\Auth\AuthManager auth
Auth (实例) Illuminate\Auth\Guard
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\CacheManager cache
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (实例) Illuminate\Database\Connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Hash Illuminate\Contracts\Hashing\Hasher hash
Input Illuminate\Http\Request request
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\Writer log
Mail Illuminate\Mail\Mailer mailer
Password Illuminate\Auth\Passwords\PasswordBroker auth.password
Queue Illuminate\Queue\QueueManager queue
Queue (实例) Illuminate\Queue\QueueInterface
Queue (基础类) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\Database redis
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Blueprint
Session Illuminate\Session\SessionManager session
Session (实例) Illuminate\Session\Store
Storage Illuminate\Contracts\Filesystem\Factory filesystem
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (实例) Illuminate\Validation\Validator
View Illuminate\View\Factory view
View (实例) Illuminate\View\View

你可能感兴趣的:(Laravel源码--Facade门面)