外观
简介
在整个 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 动态方法的独特用法,因此易于测试。
但是,在使用外观时必须小心谨慎。外观的主要危险是类“范围蔓延”。由于外观非常易于使用且不需要注入,因此很容易让您的类继续增长并在单个类中使用许多外观。使用依赖注入,通过构造函数给出的视觉反馈(您的类正在变得太大)可以缓解这种可能性。因此,在使用外观时,请特别注意类的规模,以便其责任范围保持狭窄。如果您的类变得太大,请考虑将其拆分为多个较小的类。
外观与依赖注入
依赖注入的主要优势之一是能够交换注入类的实现。这在测试期间很有用,因为您可以注入模拟或存根并断言在存根上调用了各种方法。
通常,不可能模拟或存根真正的静态类方法。但是,由于外观使用动态方法将方法调用代理到从服务容器解析的对象,因此我们实际上可以像测试注入的类实例一样测试外观。例如,给定以下路由
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');}
外观与助手函数
除了外观之外,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 文档。在适用的情况下,还包括 服务容器绑定 密钥。