外观模式
简介
在整个 Laravel 文档中,您将看到通过“外观模式”与 Laravel 的功能交互的代码示例。外观模式为应用程序的服务容器中可用的类提供了一个“静态”接口。Laravel 附带了许多外观模式,这些外观模式提供了对几乎所有 Laravel 功能的访问。
Laravel 外观模式充当服务容器中底层类的“静态代理”,在保持比传统静态方法更强的可测试性和灵活性的同时,提供了简洁、富有表现力的语法的好处。如果您不完全理解外观模式的工作原理,那也没关系 - 顺其自然,继续学习 Laravel 即可。
Laravel 的所有外观模式都在 Illuminate\Support\Facades
命名空间中定义。因此,我们可以轻松地访问外观模式,如下所示
1use Illuminate\Support\Facades\Cache;2use Illuminate\Support\Facades\Route;3 4Route::get('/cache', function () {5 return Cache::get('key');6});
在整个 Laravel 文档中,许多示例将使用外观模式来演示框架的各种功能。
辅助函数
为了补充外观模式,Laravel 提供了各种全局“辅助函数”,使与常见 Laravel 功能的交互更加容易。您可能会交互的一些常见辅助函数有 view
、response
、url
、config
等。Laravel 提供的每个辅助函数都在其相应的功能文档中进行了说明;但是,完整的列表可在专门的辅助函数文档中找到。
例如,与其使用 Illuminate\Support\Facades\Response
外观模式来生成 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});
何时使用外观模式
外观模式有很多优点。它们提供了简洁、易记的语法,使您无需记住必须手动注入或配置的长类名即可使用 Laravel 的功能。此外,由于它们对 PHP 动态方法的独特使用,它们易于测试。
但是,使用外观模式时必须小心。外观模式的主要危险是类“作用域蔓延”。由于外观模式非常易于使用且不需要注入,因此很容易让您的类不断增长并在单个类中使用许多外观模式。使用依赖注入,可以通过大型构造函数给您的视觉反馈来缓解这种可能性,即您的类正在变得太大。因此,当使用外观模式时,请特别注意您类的大小,以便其职责范围保持狭窄。如果您的类变得太大,请考虑将其拆分为多个较小的类。
外观模式 vs. 依赖注入
依赖注入的主要好处之一是能够交换注入类的实现。这在测试期间很有用,因为您可以注入模拟或桩,并断言在桩上调用了各种方法。
通常,不可能模拟或桩真正的静态类方法。但是,由于外观模式使用动态方法将方法调用代理到从服务容器解析的对象,因此我们实际上可以像测试注入的类实例一样测试外观模式。例如,给定以下路由
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}
外观模式 vs. 辅助函数
除了外观模式之外,Laravel 还包含各种“辅助”函数,这些函数可以执行常见任务,例如生成视图、触发事件、调度作业或发送 HTTP 响应。许多这些辅助函数执行与相应外观模式相同的功能。例如,此外观模式调用和辅助函数调用是等效的
1return Illuminate\Support\Facades\View::make('profile');2 3return view('profile');
外观模式和辅助函数之间绝对没有实际区别。当使用辅助函数时,您仍然可以像测试相应的外观模式一样测试它们。例如,给定以下路由
1Route::get('/cache', function () {2 return cache('key');3});
cache
辅助函数将调用 Cache
外观模式底层类上的 get
方法。因此,即使我们正在使用辅助函数,我们也可以编写以下测试来验证是否使用我们期望的参数调用了该方法
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}
外观模式的工作原理
在 Laravel 应用程序中,外观模式是一个类,它提供对容器中对象的访问。使这项工作得以实现的机制位于 Facade
类中。Laravel 的外观模式以及您创建的任何自定义外观模式都将扩展基类 Illuminate\Support\Facades\Facade
。
Facade
基类利用 __callStatic()
魔术方法将来自您的外观模式的调用延迟到从容器解析的对象。在下面的示例中,调用是对 Laravel 缓存系统进行的。通过浏览此代码,人们可能会认为静态 get
方法正在 Cache
类上调用
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Http\Controllers\Controller; 6use Illuminate\Support\Facades\Cache; 7use Illuminate\View\View; 8 9class UserController extends Controller10{11 /**12 * Show the profile for the given user.13 */14 public function showProfile(string $id): View15 {16 $user = Cache::get('user:'.$id);17 18 return view('profile', ['user' => $user]);19 }20}
请注意,在文件顶部附近,我们正在“导入”Cache
外观模式。此外观模式充当访问 Illuminate\Contracts\Cache\Factory
接口的底层实现的代理。我们使用外观模式进行的任何调用都将传递给 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
)。
实时外观模式
使用实时外观模式,您可以将应用程序中的任何类都视为外观模式。为了说明如何使用它,让我们首先检查一些不使用实时外观模式的代码。例如,假设我们的 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 的内置外观模式测试辅助函数来模拟此方法调用
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}
外观模式类参考
下面您将找到每个外观模式及其底层类。这是一个有用的工具,可以快速深入研究给定外观模式根的 API 文档。服务容器绑定键也在适用情况下包含在内。