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