跳到主要内容
版本:9.x

Facades

简介​

在整个 Laravel 文档中,您将看到通过 Facades 与 Laravel 特性交互的代码示例。Facades 为应用程序的 服务容器 中可用的类提供了「静态代理」。在 Laravel 这艘船上有许多 Facades,提供了几乎所有 Laravel 的特征。

Laravel Facades 充当服务容器中底层类的「静态代理」,提供简洁、富有表现力的好处,同时保持比传统静态方法更多的可测试性和灵活性。如果你不完全理解引擎盖下的 Facades 是如何工作的,那也没问题,跟着流程走,继续学习 Laravel。

Laravel 的所有 Facades 都在 Illuminate\Support\Facades 命名空间中定义。因此,我们可以很容易地访问这样一个 Facades:

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

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

在整个 Laravel 文档中,许多示例将使用 Facades 来演示框架的各种特性。

辅助函数​

为了补充 Facades,Laravel 提供了各种全局 「助手函数」,使它更容易与常见的 Laravel 功能进行交互。可以与之交互的一些常用助手函数有 view, response, url, config,等。Laravel 提供的每个助手函数都有相应的特性;但是,在专用的 辅助函数文档 中有一个完整的列表。

例如,我们可以简单地使用 response 函数,而不是使用 Illuminate\Support\Facades\Response Facade 来生成 JSON 响应。因为 helper 函数是全局可用的,所以不需要导入任何类就可以使用它们:

use Illuminate\Support\Facades\Response;

Route::get('/users', function () {
return Response::json([
// ...
]);
});

Route::get('/users', function () {
return response()->json([
// ...
]);
});

什么时候使用 Facades​

Facade 有很多好处。它们提供了一种简洁、令人难忘的语法,允许您使用 Laravel 的特性,而无需记住必须手动注入或配置的长类名。此外,由于它们对 PHP 的动态方法的独特用法,它们很容易测试:

但是,在使用 Facade 时必须小心。Facade 的主要危险是「范围溢出」。由于 Facade 非常容易使用并且不需要注入,所以让类继续增长并在单个类中使用多个 Facade 是很容易的。通过使用依赖注入,一个大型构造函数给您的视觉反馈减轻了这种可能性,即类增长过大。所以,在使用 Facade 的时候,要特别注意你的类规模,这样它的职责范围就不会太窄。如果你的类太大了,可以考虑把它分成多个较小的类。

Facades Vs 依赖注入​

依赖注入的主要好处之一是能够交换注入类的实现。这在测试期间非常有用,因为您可以注入一个 mock 或 stub 并断言在 stub 上调用了各种方法。

通常,真正的静态方法是不可能 mock 或 stub 的。但是由于 Facades 使用动态方法对服务容器中解析出来的对象方法的调用进行了代理, 我们也可以像测试注入类实例一样测试 Facades。比如,像下面的路由:

use Illuminate\Support\Facades\Cache;

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

使用 Laravel 的 Facade 测试方法,我们可以编写以下测试用例来验证是否 Cache::get 使用我们期望的参数调用了该方法:

use Illuminate\Support\Facades\Cache;

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

$response = $this->get('/cache');

$response->assertSee('value');
}

Facades Vs 助手函数​

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

return Illuminate\Support\Facades\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');

$response = $this->get('/cache');

$response->assertSee('value');
}

Facades 工作原理​

在 Laravel 应用程序中,Facades 是一个提供从容器访问对象的类。完成这项工作的部分属于Facade 类。Laravel 的 Facade、以及您创建的任何自定义 Facade,都继承自 Illuminate\Support\Facades\Facade 类。

Facade 基类使用 __callStatic() 魔术方法将来自 Facade 的调用推迟到从容器解析出对象后。在下面的示例中,调用了 Laravel 缓存系统。看一眼这段代码,人们可能会假设静态的 get 方法正在 Cache 类上被调用:

<?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:

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

相反,Cache Facade 继承了 Facade 基类并定义了 getFacadeAccessor() 方法。此方法的工作是返回服务容器绑定的名称。当用户引用 Cache Facade 上的任何静态方法时,Laravel 会从 服务容器 中解析 cache 绑定并运行该对象请求的方法(在这个例子中就是 get 方法)

实时 Facades​

使用实时 Facade, 你可以将应用程序中的任何类视为 Facade。为了说明这是如何使用的, 让我们首先看一下一些不使用实时 Facade 的代码。例如,假设我们的 Podcast 模型有一个 publish 方法。 但是,为了发布 Podcast,我们需要注入一个 Publisher 实例:

<?php

namespace App\Models;

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

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

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

将 publisher 的实现注入到该方法中,我们可以轻松地测试这种方法,因为我们可以模拟注入的 publisher 。但是,它要求我们每次调用 publish 方法时始终传递一个 publisher 实例。 使用实时的 Facades, 我们可以保持同样的可测试性,而不需要显式地通过 Publisher 实例。要生成实时 Facade,请在导入类的名称空间中加上 Facades:

<?php

namespace App\Models;

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

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

Publisher::publish($this);
}
}

当使用实时 Facade 时, publisher 实现将通过使用 Facades 前缀后出现的接口或类名的部分来解决服务容器的问题。在测试时,我们可以使用 Laravel 的内置 Facade 测试辅助函数来模拟这种方法调用:

<?php

namespace Tests\Feature;

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

class PodcastTest extends TestCase
{
use RefreshDatabase;

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

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

$podcast->publish();
}
}

Facade 类参考​

在下面你可以找到每个 Facade 类及其对应的底层类。这是一个查找给定 Facade 类 API 文档的工具。服务容器绑定 的关键信息也包含在内。

Facade类服务容器绑定
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
AuthIlluminate\Auth\AuthManagerauth
Auth (Instance)Illuminate\Contracts\Auth\Guardauth.driver
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
BroadcastIlluminate\Contracts\Broadcasting\Factory 
Broadcast (Instance)Illuminate\Contracts\Broadcasting\Broadcaster 
BusIlluminate\Contracts\Bus\Dispatcher 
CacheIlluminate\Cache\CacheManagercache
Cache (Instance)Illuminate\Cache\Repositorycache.store
ConfigIlluminate\Config\Repositoryconfig
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DateIlluminate\Support\DateFactorydate
DBIlluminate\Database\DatabaseManagerdb
DB (Instance)Illuminate\Database\Connectiondb.connection
EventIlluminate\Events\Dispatcherevents
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate 
HashIlluminate\Contracts\Hashing\Hasherhash
HttpIlluminate\Http\Client\Factory 
LangIlluminate\Translation\Translatortranslator
LogIlluminate\Log\LogManagerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager 
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Password (Instance)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
QueueIlluminate\Queue\QueueManagerqueue
Queue (Instance)Illuminate\Contracts\Queue\Queuequeue.connection
Queue (Base Class)Illuminate\Queue\Queue 
RedirectIlluminate\Routing\Redirectorredirect
RedisIlluminate\Redis\RedisManagerredis
Redis (Instance)Illuminate\Redis\Connections\Connectionredis.connection
RequestIlluminate\Http\Requestrequest
ResponseIlluminate\Contracts\Routing\ResponseFactory 
Response (Instance)Illuminate\Http\Response 
RouteIlluminate\Routing\Routerrouter
SchemaIlluminate\Database\Schema\Builder 
SessionIlluminate\Session\SessionManagersession
Session (Instance)Illuminate\Session\Storesession.store
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
Storage (Instance)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
URLIlluminate\Routing\UrlGeneratorurl
ValidatorIlluminate\Validation\Factoryvalidator
Validator (Instance)Illuminate\Validation\Validator 
ViewIlluminate\View\Factoryview
View (Instance)Illuminate\View\View