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