外观模式
简介
在 Laravel 文档中,您将看到通过“外观模式”与 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 动态方法的独特使用,它们易于测试。
但是,使用外观模式时必须小心。外观模式的主要危险是类“范围蔓延”。由于外观模式非常易于使用,并且不需要注入,因此很容易让您的类不断增长,并在单个类中使用许多外观模式。使用依赖注入,可以通过大型构造函数提供的视觉反馈来减轻这种可能性,该构造函数会告诉您您的类正在变得太大。因此,在使用外观模式时,请特别注意类的大小,以使其职责范围保持狭窄。如果您的类变得太大,请考虑将其拆分为多个较小的类。
外观模式 vs. 依赖注入
依赖注入的主要好处之一是能够交换注入类的实现。这在测试期间很有用,因为您可以注入模拟或存根,并断言在存根上调用了各种方法。
通常,不可能模拟或存根真正的静态类方法。但是,由于外观模式使用动态方法将方法调用代理到从服务容器解析的对象,因此我们实际上可以像测试注入的类实例一样测试外观模式。例如,给定以下路由
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');}
外观模式 vs. 助手函数
除了外观模式外,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 App\Http\Controllers\Controller;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{ /** * Publish the podcast. */ 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; uses(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; /** * A test example. */ public function test_podcast_can_be_published(): void { $podcast = Podcast::factory()->create(); Publisher::shouldReceive('publish')->once()->with($podcast); $podcast->publish(); }}
外观类参考
下面您将找到每个外观模式及其底层类。这是一个有用的工具,可以快速深入研究给定外观模式根的 API 文档。在适用的情况下,还包括服务容器绑定键。