Laravel 的 Facades 介绍

简介

Facades(读音:/fəˈsäd/ )为应用程序的 服务容器 中可用的类提供了一个「静态」接口。Laravel 自带了很多 facades ,几乎可以用来访问到 Laravel 中所有的服务。Laravel facades 实际上是服务容器中那些底层类的「静态代理」,相比于传统的静态方法, facades 在提供了简洁且丰富的语法同时,还带来了更好的可测试性和扩展性。

所有的 Laravel facades 都需要定义在命名空间 Illuminate\Support\Facades 下。所以,我们可以容易地向下面这样调用 facade :

use Illuminate\Support\Facades\Cache;

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

在 Laravel 的文档中,很多示例代码都是使用 facades 来演示框架的各种特性的。

何时使用 Facades

Facades 有很多好处,它为我们使用 Laravel 的各种功能提供了简单,易记的语法,让你不需要记住长长的类名来实现依赖注入和手动配置。还有,因为它们对于PHP动态方法的独特用法,测试起来非常容易。

然而,在使用 facades 时,有些地方还需要特别注意。使用 facades 最主要的风险就是会引起类作用范围的膨胀。因为 facades 使用起来非常简单而且不需要注入,我们会不经意的在单个类中大量使用。它不会像使用依赖注入那样,使用的类越多,构造方法会越长,在视觉上就会引起注意,提醒你这个类有点庞大了。所以在使用 facades 的时候,要特别注意控制好类的大小,让类的作用范围保持短小。

{tip} 在开发与 Laravel 交互的第三方扩展包时,最好是在包中通过注入 Laravel contracts ,而不是在包中通过 facades 来使用 Laravel 的类。因为扩展包不是在 Laravel 内部使用的,无法使用 Laravel's facade 的测试辅助函数。

Facades Vs. 依赖注入

依赖注入的一个主要的好处是可以切换注入类的具体实现。这在测试的时候很有用,因为你可以注入一个 mock 或者 stub ,并且对在 stub 中被调用的各种方法进行断言。

通常,静态方法是不可以被 mock 或者 stub 。但是,因为 facades 调用的是对象的动态方法,我们可以像测试注入类的实例一样测试 facades ,例如,像下面的路由:

use Illuminate\Support\Facades\Cache;

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

我们可以用下面的测试代码去验证 Cache::get 方法是否被调用,当传入预期的参数时。

use Illuminate\Support\Facades\Cache;

/**
 * 一个基础功能的测试用例。
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $this->visit('/cache')
         ->see('value');
}

Facades Vs. 辅助函数

除了 facades , Laravel 包含一些「辅助函数」来实现一些常用的功能,比如生成视图,触发事件,调度任务或者发送 HTTP 响应。许多辅助函数的功能和对应的 facades 一样。例如,下面这个 facade 和辅助函数的作用是一样的:

return View::make('profile');

return view('profile');

这里的 facades 和辅助函数是没有任何区别的。当你使用辅助函数时,你依然可以向使用对应的 facade 一样测试他们。例如,下面的路由:

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

在底层,辅助函数 cache 实际是调用 Cache facade 中的 get 方法。因此,尽管我们是在使用辅助函数,我们依然可以用下面的测试代码来验证是否方法被正确调用,在传入预期的参数时:

use Illuminate\Support\Facades\Cache;

/**
 * 一个基础功能的测试用例。
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $this->visit('/cache')
         ->see('value');
}

Facades 工作原理

在 Laravel 应用中,一个 facade 就是一个提供访问容器中对象的类。其中核心的部件就是 Facade 类。不管是 Laravel 自带的 Facades ,还是用户自定义的 Facades ,都继承自 Illuminate\Support\Facades\Facade 类。

Facade 基类使用 __callStatic() 魔术方法在你的 facades 中延迟调用容器中对应对象的方法,在下面的例子中,调用了 Laravel 的缓存系统。在代码里,我们可能认为是 Cache 类中的静态方法 get 被调用了:

<?php

namespace App\Http\Controllers;

   use Illuminate\Support\Facades\Cache;    use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 显示给定用户的大体信息。
     *
     * @param  int  $id
     * @return Response
     */
    public function showProfile($id)
    {
        $user = Cache::get('user:'.$id);

        return view('profile', ['user' => $user]);
    }
}

注意在代码的最上面,我们导入的是 Cache facade 。这个 facade 其实是我们获取底层 Illuminate\Contracts\Cache\Factory 接口实现的一个代理。我们通过这个 facade 调用的任何方法,都会被传递到 Laravel 缓存服务的底层实例中。

如果我们看一下 Illuminate\Support\Facades\Cache 这个类,你会发现类中根本没有 get 这个静态方法:

class Cache extends Facade
{
    /**
     * 获取组件在容器中注册的名称。
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'cache'; }
}

其实, Cache facade 是继承了 Facade 基类,并且定义了 getFacadeAccessor() 方法。这个方法的作用是返回服务容器中对应名字的绑定内容。当用户调用 Cache facade 中的任何静态方法时, Laravel 会解析到服务容器中绑定的键值为 cache 实例对象,并调用这个对象对应的方法(在这个例子中就是 get 方法)。

Facade 类参考

在下面你可以找到每个 facade 类及其对应的底层类。这是一个查找给定 facade 类 API 文档的有用工具。 也列出了绑定在 服务容器 中 facade 类对应的可用键值。

Facade Class Service Container Binding
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache Illuminate\Cache\Repository cache
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection  
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate  
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\Writer log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager  
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue
Queue (Base Class) 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 (Instance) Illuminate\Session\Store  
Storage Illuminate\Contracts\Filesystem\Factory filesystem
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator  
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View