简介
在 Laravel 文档中,你会看到通过“门面(facades)”与 Laravel 功能交互的代码示例。门面为应用的服务容器中可用的类提供了“静态”接口。Laravel 自带许多门面,可用于访问 Laravel 几乎所有的功能。
Laravel 门面充当服务容器中底层类的“静态代理”,既具备简洁、富于表现力的语法优势,又比传统静态方法拥有更好的可测试性与灵活性。即便你不完全理解门面的工作原理也完全没关系——按部就班,继续学习 Laravel 即可。
所有 Laravel 门面都定义在 Illuminate\Support\Facades 命名空间下。因此,我们可以像下面这样轻松访问某个门面:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});在 Laravel 文档中,很多示例都会使用门面来演示框架的各种功能。
辅助函数
为了与门面相互补充,Laravel 提供了多种全局“辅助函数”,让与 Laravel 常用功能的交互更加便捷。你可能用到的一些常见辅助函数有 view、response、url、config 等等。Laravel 提供的每个辅助函数都在其对应的功能章节中有文档说明;不过,完整的函数清单可在专门的辅助函数文档中找到。
例如,与其使用 Illuminate\Support\Facades\Response 门面来生成 JSON 响应,我们可以直接使用 response 函数。由于辅助函数是全局可用的,因此你无需导入任何类即可使用它们:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});何时使用门面
门面有很多好处。它们提供了简洁、便于记忆的语法,让你无需记住那些必须手动注入或配置的长类名,就能使用 Laravel 的功能。此外,由于它们对 PHP 动态方法的独特运用,使得它们易于测试。
不过,使用门面时也需要注意一些问题。门面的主要风险是类的“范围蔓延”。由于门面非常易用且无需注入,很容易让你的类不断膨胀,在单个类中用到很多门面。而使用依赖注入时,庞大的构造函数会从视觉上提醒你:你的类已经变得过大,从而缓解这一问题。因此,在使用门面时,请特别留意类的规模,确保其职责范围保持狭窄。如果你的类变得过大,请考虑将其拆分为多个更小的类。
门面与依赖注入
依赖注入的主要优势之一,是能够替换所注入类的实现。这在测试时非常有用,因为你可以注入模拟对象或存根,并断言在存根上调用过各种方法。
通常情况下,无法对真正的静态类方法进行模拟或存根处理。然而,由于门面使用动态方法将方法调用代理到从服务容器解析出的对象,我们实际上可以像测试注入的类实例那样测试门面。例如,给定如下路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});借助 Laravel 的门面测试方法,我们可以编写如下测试,以验证 Cache::get 方法被以我们预期的参数调用过:
use Illuminate\Support\Facades\Cache;
test('basic example', function () {
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
});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');
}门面与辅助函数
除了门面,Laravel 还内置了多种“辅助”函数,可以执行诸如生成视图、触发事件、分发任务或发送 HTTP 响应等常见任务。其中很多辅助函数与对应的门面功能相同。例如,下面的门面调用和辅助函数调用是等价的:
return Illuminate\Support\Facades\View::make('profile');
return view('profile');门面与辅助函数在实际使用上没有任何区别。在使用辅助函数时,你仍然可以像测试对应门面那样对它们进行测试。例如,给定如下路由:
Route::get('/cache', function () {
return cache('key');
});cache 辅助函数会调用 Cache 门面底层类上的 get 方法。因此,即便我们使用的是辅助函数,也可以编写如下测试,以验证该方法被以我们预期的参数调用过:
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');
}门面的工作原理
在 Laravel 应用中,门面是一个能够让你从容器中访问对象的类。实现这一切的核心机制位于 Facade 类中。Laravel 自带的门面以及你创建的任何自定义门面,都会继承基础的 Illuminate\Support\Facades\Facade 类。
基础类 Facade 利用 __callStatic() 魔术方法,将针对门面的调用延迟转发到从容器中解析出的对象。在下面的例子中,调用了 Laravel 的缓存系统。粗看这段代码,你可能以为正在调用 Cache 类的静态 get 方法:
<?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 门面。该门面充当访问 Illuminate\Contracts\Cache\Factory 接口底层实现的代理。我们通过门面发出的所有调用,都会被传递给 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 类,并定义了 getFacadeAccessor() 方法。该方法的作用是返回某个服务容器绑定的名称。当用户在 Cache 门面上引用任意静态方法时,Laravel 会从服务容器中解析出 cache 绑定,并在该对象上执行所请求的方法(在此例中为 get)。
实时门面
借助实时门面,你可以把应用中的任何类都当作门面来使用。为了说明其用法,我们先来看一段不使用实时门面的代码。例如,假设我们的 Podcast 模型有一个 publish 方法。不过,要发布播客,我们需要注入一个 Publisher 实例:
<?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);
}
}在方法中注入发布器的实现,让我们可以对该方法进行独立的测试,因为我们能对注入的发布器进行模拟。但这也要求我们在每次调用 publish 方法时,都必须显式传入一个发布器实例。使用实时门面,我们既能保持相同的可测试性,又无需显式传入 Publisher 实例。要生成实时门面,只需在导入类的命名空间前加上 Facades 前缀即可:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布播客。
*/
public function publish(Publisher $publisher): void
public function publish(): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
Publisher::publish($this);
}
}当使用实时门面时,发布器的实现将通过出现在 Facades 前缀之后的那部分接口或类名,从服务容器中解析出来。测试时,我们可以使用 Laravel 内置的门面测试辅助工具来模拟此方法调用:
<?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
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();
}
}门式类参考
下面可以找到每个门式类及其底层类。这是一个实用的工具,可帮助您快速查阅特定门式根类的 API 文档。同时,在适用情况下,也包含了服务容器绑定键。
| 外观 | 类 | 服务容器绑定 |
|---|---|---|
| App | Illuminate\Foundation\Application | app |
| Artisan | Illuminate\Contracts\Console\Kernel | |
| Auth (Instance) | Illuminate\Contracts\Auth\Guard | auth.driver |
| Auth | Illuminate\Auth\AuthManager | auth |
| Blade | Illuminate\View\Compilers\BladeCompiler | blade.compiler |
| Broadcast (Instance) | Illuminate\Contracts\Broadcasting\Broadcaster | |
| Broadcast | Illuminate\Contracts\Broadcasting\Factory | |
| Bus | Illuminate\Contracts\Bus\Dispatcher | |
| Cache (Instance) | Illuminate\Cache\Repository | cache.store |
| Cache | Illuminate\Cache\CacheManager | cache |
| Cloud | Illuminate\Foundation\Cloud\CloudManager | |
| Config | Illuminate\Config\Repository | config |
| Context | Illuminate\Log\Context\Repository | |
| Cookie | Illuminate\Cookie\CookieJar | cookie |
| Crypt | Illuminate\Encryption\Encrypter | encrypter |
| Date | Illuminate\Support\DateFactory | date |
| DB (Instance) | Illuminate\Database\Connection | db.connection |
| DB | Illuminate\Database\DatabaseManager | db |
| Event | Illuminate\Events\Dispatcher | events |
| Exceptions (Instance) | Illuminate\Contracts\Debug\ExceptionHandler | |
| Exceptions | Illuminate\Foundation\Exceptions\Handler | |
| File | Illuminate\Filesystem\Filesystem | files |
| Gate | Illuminate\Contracts\Auth\Access\Gate | |
| Hash | Illuminate\Contracts\Hashing\Hasher | hash |
| Http | Illuminate\Http\Client\Factory | |
| Lang | Illuminate\Translation\Translator | translator |
| Log | Illuminate\Log\LogManager | log |
| Mail (Instance) | Illuminate\Mail\Mailer | mailer |
| Illuminate\Mail\MailManager | mail.manager | |
| Notification | Illuminate\Notifications\ChannelManager | |
| Password (Instance) | Illuminate\Auth\Passwords\PasswordBroker | auth.password.broker |
| Password | Illuminate\Auth\Passwords\PasswordBrokerManager | auth.password |
| Pipeline (Instance) | Illuminate\Pipeline\Pipeline | |
| Process | Illuminate\Process\Factory | |
| Queue (Base Class) | Illuminate\Queue\Queue | |
| Queue (Instance) | Illuminate\Contracts\Queue\Queue | queue.connection |
| Queue | Illuminate\Queue\QueueManager | queue |
| RateLimiter | Illuminate\Cache\RateLimiter | |
| Redirect | Illuminate\Routing\Redirector | redirect |
| Redis (Instance) | Illuminate\Redis\Connections\Connection | redis.connection |
| Redis | Illuminate\Redis\RedisManager | redis |
| Request | Illuminate\Http\Request | request |
| Response (Instance) | Illuminate\Http\Response | |
| Response | Illuminate\Contracts\Routing\ResponseFactory | |
| Route | Illuminate\Routing\Router | router |
| Schedule | Illuminate\Console\Scheduling\Schedule | |
| Schema | Illuminate\Database\Schema\Builder | |
| Session (Instance) | Illuminate\Session\Store | session.store |
| Session | Illuminate\Session\SessionManager | session |
| Storage (Instance) | Illuminate\Contracts\Filesystem\Filesystem | filesystem.disk |
| Storage | Illuminate\Filesystem\FilesystemManager | filesystem |
| URL | Illuminate\Routing\UrlGenerator | url |
| Validator (Instance) | Illuminate\Validation\Validator | |
| Validator | Illuminate\Validation\Factory | validator |
| View (Instance) | Illuminate\View\View | |
| View | Illuminate\View\Factory | view |
| Vite | Illuminate\Foundation\Vite |