一、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'); });
1、辅助函数
为了补充 Facades 能力,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 提供了简洁、易于记忆的语法,无需记住要手动注入或配置的长类名。此外,由于它是 PHP 动态调用的,很容易测试。
然而,在使用 Facades 时必须小心。它的主要危险是类的「作用范围扩张」。Facades 的易于使用且不需要注入,这就导致在类的持续开发过程中,很容易不知不觉用到许多的 Facades。使用依赖注入时,这种潜在问题通过构造函数变得更为明显,类慢慢变得越来越大。所以,在使用 Facades 时,需要特别关注类的大小,以便它保持责任范围单一,如果类变得太大,请考虑将它拆分成多个较小的类。
1、Facades vs. 依赖注入
依赖注入的主要好处之一是能够交换注入类的实现。这在测试时非常有用,可以注入 mock 或 stub,并断言在 stub 上调用了各种方法。
通常,无法 mock 或 stub 一个真正的静态类方法。但是,由于 Laravel 的 Facades 使用动态方法来代理对从服务容器中解析出来的对象的方法调用,实际上可以像测试注入的类实例一样测试 Facades。例如,给定以下路由:
use Illuminate\Support\Facades\Cache; Route::get('/cache', function () { return Cache::get('key'); });
使用 Laravel 的 Facade 测试方法,可以编写以下测试来验证 Cache::get 方法是否如期望的参数被调用:
// Pest use Illuminate\Support\Facades\Cache; test('basic example', function () { Cache::shouldReceive('get') ->with('key') ->andReturn('value'); $response = $this->get('/cache'); $response->assertSee('value'); });
// PHPUnit 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'); }
2、Facades vs. 辅助函数
除了 Facades,Laravel 还包含许多 「辅助」函数,这些函数可以执行常见的任务,如生成视图、触发事件、调度任务或发送 HTTP 响应。许多这些辅助函数与对应的 Facades 执行相同的功能。例如,以下 Facade 调用和辅助函数调用是等效的:
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 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 接口的底层实现。使用 Facade 进行的任何调用都将传递给底层实例的相应方法。由于 Facade 使用了 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); } }
将发布者实现注入方法中可以让我们轻松地隔离测试该方法,因为我们可以模拟被注入的发布者。然而,它要求我们每次调用 publish 方法时总是传递一个发布者实例。使用实时 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 { /** * 发布播客。 */ 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 测试辅助函数来模拟这个方法调用:
<?php // Pest 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 // PHPUnit 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 文档,还包含了 服务容器绑定 的键名称 (key)。
Facade | 类 | 服务容器绑定名称 |
App | Illuminate\Foundation\Application | app |
Artisan | Illuminate\Contracts\Console\Kernel | artisan |
Auth | Illuminate\Auth\AuthManager | auth |
Auth (Instance) | Illuminate\Contracts\Auth\Guard | auth.driver |
Blade | Illuminate\View\Compilers\BladeCompiler | blade.compiler |
Broadcast | Illuminate\Contracts\Broadcasting\Factory | |
Broadcast (Instance) | Illuminate\Contracts\Broadcasting\Broadcaster | |
Bus | Illuminate\Contracts\Bus\Dispatcher | |
Cache | Illuminate\Cache\CacheManager | cache |
Cache (Instance) | Illuminate\Cache\Repository | cache.store |
Config | Illuminate\Config\Repository | config |
Cookie | Illuminate\Cookie\CookieJar | cookie |
Crypt | Illuminate\Encryption\Encrypter | encrypter |
Date | Illuminate\Support\DateFactory | date |
DB | Illuminate\Database\DatabaseManager | db |
DB (Instance) | Illuminate\Database\Connection | db.connection |
Event | Illuminate\Events\Dispatcher | events |
Exceptions | Illuminate\Foundation\Exceptions\Handler | |
Exceptions (Instance) | Illuminate\Contracts\Debug\ExceptionHandler | |
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 |
Illuminate\Mail\Mailer | mailer | |
Notification | Illuminate\Notifications\ChannelManager | |
Password | Illuminate\Auth\Passwords\PasswordBrokerManager | auth.password |
Password (Instance) | Illuminate\Auth\Passwords\PasswordBroker | auth.password.broker |
Pipeline (Instance) | Illuminate\Pipeline\Pipeline | |
Process | Illuminate\Process\Factory | |
Queue | Illuminate\Queue\QueueManager | queue |
Queue (Instance) | Illuminate\Contracts\Queue\Queue | queue.connection |
Queue (Base Class) | Illuminate\Queue\Queue | |
RateLimiter | Illuminate\Cache\RateLimiter | |
Redirect | Illuminate\Routing\Redirector | redirect |
Redis | Illuminate\Redis\RedisManager | redis |
Redis (Instance) | Illuminate\Redis\Connections\Connection | redis.connection |
Request | Illuminate\Http\Request | request |
Response | Illuminate\Contracts\Routing\ResponseFactory | |
Response (Instance) | Illuminate\Http\Response | |
Route | Illuminate\Routing\Router | router |
Schedule | Illuminate\Console\Scheduling\Schedule | |
Schema | Illuminate\Database\Schema\Builder | |
Session | Illuminate\Session\SessionManager | session |
Session (Instance) | Illuminate\Session\Store | session.store |
Storage | Illuminate\Filesystem\FilesystemManager | filesystem |
Storage (Instance) | Illuminate\Contracts\Filesystem\Filesystem | filesystem.disk |
URL | Illuminate\Routing\UrlGenerator | url |
Validator | Illuminate\Validation\Factory | validator |
Validator (Instance) | Illuminate\Validation\Validator | |
View | Illuminate\View\Factory | view |
View (Instance) | Illuminate\View\View | |
Vite | Illuminate\Foundation\Vite |
————————————————
原文作者:Laravel China 社区文档:《Laravel 11 中文文档(11.x)》
转自链接:https://learnku.com/docs/laravel/11.x/facadesmd/16656