主题
Facade
简介
在整个 Laravel 文档中,你会看到通过 "facade" 与 Laravel 功能交互的代码示例。Facade 为应用程序 service container 中可用的类提供了"静态"接口。Laravel 附带了许多 facade,几乎可以访问 Laravel 的所有功能。
Laravel facade 充当 service container 中底层类的"静态代理",提供简洁、富有表现力的语法,同时比传统静态方法保持更好的可测试性和灵活性。如果你还不完全理解 facade 的工作原理也没关系——只需顺其自然,继续学习 Laravel 即可。
所有 Laravel 的 facade 都定义在 Illuminate\Support\Facades 命名空间中。因此,我们可以这样轻松访问 facade:
php
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});在整个 Laravel 文档中,许多示例都会使用 facade 来演示框架的各种功能。
辅助函数
作为 facade 的补充,Laravel 提供了各种全局"辅助函数",使与常见 Laravel 功能的交互更加简单。你可能会用到的一些常见辅助函数包括 view、response、url、config 等。Laravel 提供的每个辅助函数都在其对应功能的文档中有说明;不过,完整列表可在专门的辅助函数文档中找到。
例如,我们可以简单地使用 response 函数来生成 JSON 响应,而不是使用 Illuminate\Support\Facades\Response facade。因为辅助函数是全局可用的,你不需要导入任何类即可使用它们:
php
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});何时使用 Facade
Facade 有很多好处。它们提供了简洁、易记的语法,让你无需记住必须手动注入或配置的冗长类名即可使用 Laravel 的功能。此外,由于它们对 PHP 动态方法的独特使用,它们很容易测试。
然而,使用 facade 时需要注意一些事项。facade 的主要危险是类的"范围蔓延"。由于 facade 使用非常方便且不需要注入,很容易让你的类不断增长并在单个类中使用许多 facade。使用依赖注入时,大型构造函数给你的视觉反馈会提醒你类正在变得过大,从而减轻这种风险。因此,在使用 facade 时,请特别注意类的大小,使其职责范围保持狭窄。如果你的类变得太大,考虑将其拆分为多个较小的类。
Facade 与依赖注入
依赖注入的主要好处之一是能够替换注入类的实现。这在测试期间很有用,因为你可以注入 mock 或 stub,并断言在 stub 上调用了各种方法。
通常,不可能 mock 或 stub 真正的静态类方法。然而,由于 facade 使用动态方法将方法调用代理到从 service container 解析的对象,我们实际上可以像测试注入的类实例一样测试 facade。例如,给定以下路由:
php
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});使用 Laravel 的 facade 测试方法,我们可以编写以下测试来验证 Cache::get 方法是否使用我们期望的参数被调用:
php
use Illuminate\Support\Facades\Cache;
test('basic example', function () {
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
});php
use Illuminate\Support\Facades\Cache;
/**
* A basic functional test example.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}Facade 与辅助函数
除了 facade 之外,Laravel 还包含各种"辅助"函数,可以执行常见任务,如生成视图、触发事件、分发任务或发送 HTTP 响应。许多这些辅助函数与相应的 facade 执行相同的功能。例如,以下 facade 调用和辅助函数调用是等效的:
php
return Illuminate\Support\Facades\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;
/**
* A basic functional test example.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}Facade 的工作原理
在 Laravel 应用程序中,facade 是一个提供从容器访问对象的类。使这一切工作的机制在 Facade 类中。Laravel 的 facade 以及你创建的任何自定义 facade 都将扩展基类 Illuminate\Support\Facades\Facade。
Facade 基类利用 __callStatic() 魔术方法将 facade 的调用延迟到从容器解析的对象。在下面的例子中,对 Laravel 缓存系统进行了调用。乍一看这段代码,人们可能会认为在 Cache 类上调用了静态 get 方法:
php
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function showProfile(string $id): View
{
$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
{
/**
* Get the registered name of the component.
*/
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}相反,Cache facade 扩展了基类 Facade 并定义了 getFacadeAccessor() 方法。此方法的作用是返回 service container 绑定的名称。当用户引用 Cache facade 上的任何静态方法时,Laravel 从 service container 解析 cache 绑定,并对该对象运行请求的方法(在本例中是 get)。
实时 Facade
使用实时 facade,你可以将应用程序中的任何类当作 facade 来使用。为了说明如何使用,让我们先看一些不使用实时 facade 的代码。例如,假设我们的 Podcast 模型有一个 publish 方法。但是,为了发布播客,我们需要注入一个 Publisher 实例:
php
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*/
public function publish(Publisher $publisher): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}将发布者实现注入到方法中允许我们轻松地隔离测试该方法,因为我们可以 mock 注入的发布者。但是,它要求我们每次调用 publish 方法时都传递一个发布者实例。使用实时 facade,我们可以保持相同的可测试性,同时不需要显式传递 Publisher 实例。要生成实时 facade,请在导入类的命名空间前加上 Facades 前缀:
php
<?php
namespace App\Models;
use App\Contracts\Publisher; // [tl! remove]
use Facades\App\Contracts\Publisher; // [tl! add]
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*/
public function publish(Publisher $publisher): void // [tl! remove]
public function publish(): void // [tl! add]
{
$this->update(['publishing' => now()]);
$publisher->publish($this); // [tl! remove]
Publisher::publish($this); // [tl! add]
}
}使用实时 facade 时,发布者实现将使用 Facades 前缀之后出现的接口或类名部分从 service container 中解析。在测试时,我们可以使用 Laravel 内置的 facade 测试辅助工具来 mock 此方法调用:
php
<?php
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
pest()->use(RefreshDatabase::class);
test('podcast can be published', function () {
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
});php
<?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;
/**
* A test example.
*/
public function test_podcast_can_be_published(): void
{
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}Facade 类参考
下面你可以找到每个 facade 及其底层类。这是一个快速深入了解给定 facade 根的 API 文档的有用工具。Service container 绑定键也在适用的情况下包含在内。