Facades
门面(Facades)
简介
在整个 Laravel 文档中,你会看到一些通过“facades(门面)”与 Laravel 功能交互的代码示例。Facades 为应用程序 服务容器 中可用的类提供了一个“静态(static)”接口。Laravel 自带了许多 facades,它们提供了对 Laravel 几乎所有功能的访问。
Laravel facades 充当服务容器中底层类的“静态代理(static proxies)”,在保持比传统静态方法更好的可测试性和灵活性的同时,还提供了简洁且富有表现力的语法。即使你还没有完全理解 facades 的工作原理也没关系——顺其自然,继续学习 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 来演示框架的各种功能。
辅助函数(Helper Functions)
为了补充 facades,Laravel 提供了各种全局“辅助函数(helper functions)”,使与常见 Laravel 功能交互变得更加简单。你可能会接触到的一些常见辅助函数包括 view、response、url、config 等。Laravel 提供的每个辅助函数都会在其对应功能的文档中进行说明;不过,你也可以在专门的 辅助函数文档 中查看完整列表。
例如,与其使用 Illuminate\Support\Facades\Response facade 来生成 JSON 响应,我们可以直接使用 response 函数。由于辅助函数是全局可用的,因此你无需导入任何类即可使用它们:
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,并断言(assert)是否调用了 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;
test('basic example', function () {
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
});
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 与辅助函数
除了 facades 之外,Laravel 还包含各种“辅助函数(helper)”,可用于执行常见任务,例如生成视图、触发事件、分发任务或发送 HTTP 响应。这些辅助函数中的许多都执行与相应 facade 相同的功能。例如,下面的 facade 调用与 helper 调用是等价的:
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;
/**
* 一个基础功能测试示例。
*/
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 缓存系统。仅通过查看这段代码,人们可能会认为是在 Cache 类上调用静态 get 方法:
<?php
namespace App\Http\Controllers;
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]);
}
}
请注意,在文件顶部我们“导入(import)”了 Cache facade。该 facade 作为代理,用于访问 Illuminate\Contracts\Cache\Factory 接口的底层实现。我们通过 facade 发出的任何调用,都会被传递到底层的 Laravel 缓存服务实例。
如果我们查看 Illuminate\Support\Facades\Cache 类,你会发现其中并没有静态 get 方法:
class Cache extends Facade
{
/**
* 获取组件已注册的名称。
*/
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}
相反,Cache facade 继承了基础 Facade 类,并定义了 getFacadeAccessor() 方法。这个方法的作用是返回服务容器绑定的名称。当用户在 Cache facade 上调用任何静态方法时,Laravel 会从 服务容器 中解析 cache 绑定,并在该对象上运行所请求的方法(在本例中为 get)。
实时 Facades
使用实时 facades,你可以将应用程序中的任何类当作 facade 来使用。为了说明它的用法,我们先来看一段未使用实时 facades 的代码。例如,假设我们的 Podcast 模型有一个 publish 方法。不过,为了发布 podcast,我们需要注入一个 Publisher 实例:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 podcast。
*/
public function publish(Publisher $publisher): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
将 publisher 的实现注入到方法中,可以让我们轻松地对该方法进行独立测试,因为我们可以 mock 被注入的 publisher。不过,这也要求我们每次调用 publish 方法时都必须传递一个 publisher 实例。
使用实时 facades,我们既可以保持相同的可测试性,又无需显式地传递 Publisher 实例。要生成一个实时 facade,只需在导入类的命名空间前添加 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
{
/**
* 发布 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 时,publisher 的实现将通过 Facades 前缀之后的接口或类名部分,从服务容器中解析出来。在测试时,我们可以使用 Laravel 内置的 facade 测试辅助工具来 mock 这个方法调用:
<?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();
}
}
Facade 类参考
下面你将看到每个 facade 及其底层类。这是一个有用的工具,可以帮助你快速查阅给定 facade 根类的 API 文档。在适用的情况下,也包含了 服务容器绑定 键。
| Facade | Class | Service Container Binding | | --- | --- | --- | | App | [Illuminate\Foundation\Application](https://api.laravel.com/docs/laravel/12.x/Illuminate/Foundation/Application.html) | `app` | | Artisan | [Illuminate\Contracts\Console\Kernel](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Console/Kernel.html) | `artisan` | | Auth (Instance) | [Illuminate\Contracts\Auth\Guard](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Auth/Guard.html) | `auth.driver` | | Auth | [Illuminate\Auth\AuthManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Auth/AuthManager.html) | `auth` | | Blade | [Illuminate\View\Compilers\BladeCompiler](https://api.laravel.com/docs/laravel/12.x/Illuminate/View/Compilers/BladeCompiler.html) | `blade.compiler` | | Broadcast (Instance) | [Illuminate\Contracts\Broadcasting\Broadcaster](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Broadcasting/Broadcaster.html) | | | Broadcast | [Illuminate\Contracts\Broadcasting\Factory](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Broadcasting/Factory.html) | | | Bus | [Illuminate\Contracts\Bus\Dispatcher](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Bus/Dispatcher.html) | | | Cache (Instance) | [Illuminate\Cache\Repository](https://api.laravel.com/docs/laravel/12.x/Illuminate/Cache/Repository.html) | `cache.store` | | Cache | [Illuminate\Cache\CacheManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Cache/CacheManager.html) | `cache` | | Config | [Illuminate\Config\Repository](https://api.laravel.com/docs/laravel/12.x/Illuminate/Config/Repository.html) | `config` | | Context | [Illuminate\Log\Context\Repository](https://api.laravel.com/docs/laravel/12.x/Illuminate/Log/Context/Repository.html) | | | Cookie | [Illuminate\Cookie\CookieJar](https://api.laravel.com/docs/laravel/12.x/Illuminate/Cookie/CookieJar.html) | `cookie` | | Crypt | [Illuminate\Encryption\Encrypter](https://api.laravel.com/docs/laravel/12.x/Illuminate/Encryption/Encrypter.html) | `encrypter` | | Date | [Illuminate\Support\DateFactory](https://api.laravel.com/docs/laravel/12.x/Illuminate/Support/DateFactory.html) | `date` | | DB (Instance) | [Illuminate\Database\Connection](https://api.laravel.com/docs/laravel/12.x/Illuminate/Database/Connection.html) | `db.connection` | | DB | [Illuminate\Database\DatabaseManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Database/DatabaseManager.html) | `db` | | Event | [Illuminate\Events\Dispatcher](https://api.laravel.com/docs/laravel/12.x/Illuminate/Events/Dispatcher.html) | `events` | | Exceptions (Instance) | [Illuminate\Contracts\Debug\ExceptionHandler](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Debug/ExceptionHandler.html) | | | Exceptions | [Illuminate\Foundation\Exceptions\Handler](https://api.laravel.com/docs/laravel/12.x/Illuminate/Foundation/Exceptions/Handler.html) | | | File | [Illuminate\Filesystem\Filesystem](https://api.laravel.com/docs/laravel/12.x/Illuminate/Filesystem/Filesystem.html) | `files` | | Gate | [Illuminate\Contracts\Auth\Access\Gate](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Auth/Access/Gate.html) | | | Hash | [Illuminate\Contracts\Hashing\Hasher](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Hashing/Hasher.html) | `hash` | | Http | [Illuminate\Http\Client\Factory](https://api.laravel.com/docs/laravel/12.x/Illuminate/Http/Client/Factory.html) | | | Lang | [Illuminate\Translation\Translator](https://api.laravel.com/docs/laravel/12.x/Illuminate/Translation/Translator.html) | `translator` | | Log | [Illuminate\Log\LogManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Log/LogManager.html) | `log` | | Mail | [Illuminate\Mail\Mailer](https://api.laravel.com/docs/laravel/12.x/Illuminate/Mail/Mailer.html) | `mailer` | | Notification | [Illuminate\Notifications\ChannelManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Notifications/ChannelManager.html) | | | Password (Instance) | [Illuminate\Auth\Passwords\PasswordBroker](https://api.laravel.com/docs/laravel/12.x/Illuminate/Auth/Passwords/PasswordBroker.html) | `auth.password.broker` | | Password | [Illuminate\Auth\Passwords\PasswordBrokerManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Auth/Passwords/PasswordBrokerManager.html) | `auth.password` | | Pipeline (Instance) | [Illuminate\Pipeline\Pipeline](https://api.laravel.com/docs/laravel/12.x/Illuminate/Pipeline/Pipeline.html) | | | Process | [Illuminate\Process\Factory](https://api.laravel.com/docs/laravel/12.x/Illuminate/Process/Factory.html) | | | Queue (Base Class) | [Illuminate\Queue\Queue](https://api.laravel.com/docs/laravel/12.x/Illuminate/Queue/Queue.html) | | | Queue (Instance) | [Illuminate\Contracts\Queue\Queue](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Queue/Queue.html) | `queue.connection` | | Queue | [Illuminate\Queue\QueueManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Queue/QueueManager.html) | `queue` | | RateLimiter | [Illuminate\Cache\RateLimiter](https://api.laravel.com/docs/laravel/12.x/Illuminate/Cache/RateLimiter.html) | | | Redirect | [Illuminate\Routing\Redirector](https://api.laravel.com/docs/laravel/12.x/Illuminate/Routing/Redirector.html) | `redirect` | | Redis (Instance) | [Illuminate\Redis\Connections\Connection](https://api.laravel.com/docs/laravel/12.x/Illuminate/Redis/Connections/Connection.html) | `redis.connection` | | Redis | [Illuminate\Redis\RedisManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Redis/RedisManager.html) | `redis` | | Request | [Illuminate\Http\Request](https://api.laravel.com/docs/laravel/12.x/Illuminate/Http/Request.html) | `request` | | Response (Instance) | [Illuminate\Http\Response](https://api.laravel.com/docs/laravel/12.x/Illuminate/Http/Response.html) | | | Response | [Illuminate\Contracts\Routing\ResponseFactory](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Routing/ResponseFactory.html) | | | Route | [Illuminate\Routing\Router](https://api.laravel.com/docs/laravel/12.x/Illuminate/Routing/Router.html) | `router` | | Schedule | [Illuminate\Console\Scheduling\Schedule](https://api.laravel.com/docs/laravel/12.x/Illuminate/Console/Scheduling/Schedule.html) | | | Schema | [Illuminate\Database\Schema\Builder](https://api.laravel.com/docs/laravel/12.x/Illuminate/Database/Schema/Builder.html) | | | Session (Instance) | [Illuminate\Session\Store](https://api.laravel.com/docs/laravel/12.x/Illuminate/Session/Store.html) | `session.store` | | Session | [Illuminate\Session\SessionManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Session/SessionManager.html) | `session` | | Storage (Instance) | [Illuminate\Contracts\Filesystem\Filesystem](https://api.laravel.com/docs/laravel/12.x/Illuminate/Contracts/Filesystem/Filesystem.html) | `filesystem.disk` | | Storage | [Illuminate\Filesystem\FilesystemManager](https://api.laravel.com/docs/laravel/12.x/Illuminate/Filesystem/FilesystemManager.html) | `filesystem` | | URL | [Illuminate\Routing\UrlGenerator](https://api.laravel.com/docs/laravel/12.x/Illuminate/Routing/UrlGenerator.html) | `url` | | Validator (Instance) | [Illuminate\Validation\Validator](https://api.laravel.com/docs/laravel/12.x/Illuminate/Validation/Validator.html) | | | Validator | [Illuminate\Validation\Factory](https://api.laravel.com/docs/laravel/12.x/Illuminate/Validation/Factory.html) | `validator` | | View (Instance) | [Illuminate\View\View](https://api.laravel.com/docs/laravel/12.x/Illuminate/View/View.html) | | | View | [Illuminate\View\Factory](https://api.laravel.com/docs/laravel/12.x/Illuminate/View/Factory.html) | `view` | | Vite | [Illuminate\Foundation\Vite](https://api.laravel.com/docs/laravel/12.x/Illuminate/Foundation/Vite.html) | |
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
Laravel 13 中文文档
关于 LearnKu