Facades

目录老牛浏览 7讨论 0发表于

简介

Facade 为应用的 服务容器 中可用的类提供了一个「静态」接口。Laravel 自带了很多 Facade,可以使用 Laravel 几乎所有的功能。Laravel Facade 充当了服务容器中底层类的「静态代理」,提供了简洁而直观的语法,同时比传统的静态方法更有可测试性和灵活性。

所有的 Laravel Facade 都在 Illuminate\Support\Facades 命名空间中定义。因此,我们可以像这样轻松使用 Facade:

php
use Illuminate\Support\Facades\Cache;

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

在整个 Laravel 的文档中,很多示例代码都会使用 Facade 来演示框架的各种功能。

什么时候使用 Facade

Facade 有很多好处,它为使用 Laravel 的功能提供了简单、易记的语法,而无须在手动注入或配置时记住长长的类名。此外,由于它们使用了 PHP 的动态方法,使得测试起来也很容易。

然而,在使用 Facade 时也有一些要注意的。使用 Facade 最主要的风险是会引起类作用范围的膨胀。由于 Facade 便于使用而且不需要注入,很容易在单个类中使用许多 Facade,从而导致类变得越来越大。而使用依赖注入时,使用的类越多构造函数就越长,在视觉上就会引起注意,提醒您要注意类有点庞大了。因此在使用 Facade 的时候,要特别注意控制好类的大小,让类的作用范围保持短小。

在开发与 Laravel 交互的第三方扩展包时,最好选择注入 Laravel Contract,而不是使用 Facade。由于扩展包是在 Laravel 本身之外构建的,因此您无法使用 Laravel Facade 测试辅助函数。

Facade Vs. 依赖注入

依赖注入的一个主要好处是能够切换注入类的实现。这在测试时很有用,因为您可以注入一个「模拟实现」或者「桩实现」,并断言在「桩」上调用的各种方法。

通常,真正的静态方法是不可能被「模拟实现」或者「桩实现」的。但是,由于 Facade 使用动态方法来代理服务容器所解析对象的方法调用,我们可以像测试注入类的实例一样来测试 Facade。例如,假设有如下路由:

php
use Illuminate\Support\Facades\Cache;

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

我们可以编写以下测试来验证是否使用预期的参数调用了 Cache::get 方法:

php
use Illuminate\Support\Facades\Cache;

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

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

Facade Vs. 辅助函数

除了 Facade,Laravel 还包含各种「辅助函数」来实现一些常用的功能,比如生成视图、触发事件、分发任务或者发送 HTTP 响应。许多辅助函数都有和对应的 Facade 相同的功能。例如,调用下面的 Facade 和调用辅助函数效果是一样的:

php
return View::make('profile');

return view('profile');

Facade 和辅助函数没有任何实质上的区别。使用辅助函数时,您仍然可以像使用对应的 Facade 一样对它们进行测试。例如,假设有如下路由:

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

在底层代码中,辅助函数 cache 调用了 Cache Facade 中的 get 方法。因此,即使使用的是辅助函数,我们依然可以编写以下测试来验证是否使用我们预期的参数调用了该方法:

php
use Illuminate\Support\Facades\Cache;

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

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

Facade 是如何工作的

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

Facade 基类利用 __callStatic() 魔术方法在从容器中解析出对象后再执行 Facade 调用。咋一看下面的代码,可能认为调用了 Cache 类的静态方法 get

php
namespace App\Http\Controllers;

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

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 静态方法:

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

而是,Cache Facade 继承了 Facade 基类,并定义了 getFacadeAccessor() 方法。该方法的作用是返回服务容器绑定的名称。当用户调用 Cache Facade 中的任何静态方法时,Laravel 会从 服务容器 中解析出 cache 绑定,并对该对象运行所请求的方法(在本例中,是 get 方法)。

实时 Facade

使用实时 Facade,您可以把应用中的任何类视为 Facade。为了说明如何使用它,我们来看另外一个例子。例如,假设 Podcast 模型有一个 publish 方法。为了发布播客,我们需要注入一个 Publisher 实例:

php
namespace App;

use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 发布播客
     *
     * @param  Publisher  $publisher
     * @return void
     */
    public function publish(Publisher $publisher)
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
    }
}

将发布者的实现注入到上述方法中可以让我们轻松地单独测试该方法,因为我们可以模拟要注入的发布者。但是,它要求我们每次调用 publish 方法时都要传递一个发布者实例。使用实时 Facade,我们可以有同样的可测试性,而不需要显式地传递 Publisher 实例。要生成实时 Facade,要在导入的命名空间类前加上 Facades

php
namespace App;

use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 发布播客
     *
     * @return void
     */
    public function publish()
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this);
    }
}

使用实时 Facade 时,将使用 Facades 前缀后面的接口或者类名部分从服务容器中解析发布者的实现。测试时,我们可以使用 Laravel 内置的 Facade 辅助函数来模拟该方法的调用:

php
namespace Tests\Feature;

use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;

class PodcastTest extends TestCase
{
    use RefreshDatabase;

    /**
     * 一个测试示例
     *
     * @return void
     */
    public function test_podcast_can_be_published()
    {
        $podcast = factory(Podcast::class)->create();

        Publisher::shouldReceive('publish')->once()->with($podcast);

        $podcast->publish();
    }
}

Facade 类参考

在下面您可以找到每个 Facade 类以及对应的底层类。这是一个快速查找给定 Facade 类的 API 文档的工具。也包括对应的 服务容器 绑定键。

Facade

服务容器绑定

App

Illuminate\Foundation\Application

app

Artisan

Illuminate\Contracts\Console\Kernel

artisan

Auth

Illuminate\Auth\AuthManager

auth

Auth (Instance)

Illuminate\Contracts\Auth\Guard

auth.driver

Blade

Illuminate\View\Compilers\BladeCompiler

blade.compiler

Broadcast

Illuminate\Contracts\Broadcasting\Factory

Broadcast (Instance)

Illuminate\Contracts\Broadcasting\Broadcaster

Bus

Illuminate\Contracts\Bus\Dispatcher

Cache

Illuminate\Cache\CacheManager

cache

Cache (Instance)

Illuminate\Cache\Repository

cache.store

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

db.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\Logger

log

Mail

Illuminate\Mail\Mailer

mailer

Notification

Illuminate\Notifications\ChannelManager

Password

Illuminate\Auth\Passwords\PasswordBrokerManager

auth.password

Password (Instance)

Illuminate\Auth\Passwords\PasswordBroker

auth.password.broker

Queue

Illuminate\Queue\QueueManager

queue

Queue (Instance)

Illuminate\Contracts\Queue\Queue

queue.connection

Queue (Base Class)

Illuminate\Queue\Queue

Redirect

Illuminate\Routing\Redirector

redirect

Redis

Illuminate\Redis\RedisManager

redis

Redis (Instance)

Illuminate\Redis\Connections\Connection

redis.connection

Request

Illuminate\Http\Request

request

Response

Illuminate\Contracts\Routing\ResponseFactory

Response (Instance)

Illuminate\Http\Response

Route

Illuminate\Routing\Router

router

Schema

Illuminate\Database\Schema\Builder

Session

Illuminate\Session\SessionManager

session

Session (Instance)

Illuminate\Session\Store

session.store

Storage

Illuminate\Filesystem\FilesystemManager

filesystem

Storage (Instance)

Illuminate\Contracts\Filesystem\Filesystem

filesystem.disk

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

点赞
收藏
上一篇:服务提供者
下一篇:Contracts
暂无讨论,快来发起讨论吧~
私信
老牛@ilaoniu
牛哥,俗称哞哞。单纯的九零后理工小青年。喜欢折腾,爱玩,爱安卓,爱音乐,爱游戏,爱电影,爱旅游...
最后活跃于