Facades
Facades
简介
在整个 Laravel 文档中,您将看到通过 Facades 与 Laravel 特性交互的代码示例。Facades 为应用程序的 服务容器 中可用的类提供了 『静态代理』。在 Laravel 这艘船上有许多 Facades,提供了几乎所有 Laravel 的特征。
Laravel Facades 充当服务容器中底层类的 『静态代理』,提供简洁、富有表现力的好处,同时保持比传统静态方法更多的可测试性和灵活性。如果你不完全理解引擎盖下的 Facades 是如何工作的,那也没问题,跟着流程走,继续学习 Laravel。
Laravel的所有 Facades 都在 Illuminate\Support\facades
命名空间中定义。因此,我们可以很容易地访问这样一个 Facades:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
在整个 Laravel 文档中,许多示例将使用 Facades 来演示框架的各种特性。
助手函数
为了补充 Facades,Laravel 提供了各种全局 「助手函数」,使它更容易与常见的 Laravel 功能进行交互。可以与之交互的一些常用助手函数有 view
、response
、url
、config
等。Laravel提供的每个助手函数都有相应的特性;但是,在专用的 助手函数文档 中有一个完整的列表。
例如,我们可以简单地使用 Response
函数,而不是使用 Illighte\Support\Facades\Response
Facade 来生成 JSON 响应。因为 helper 函数是全局可用的,所以不需要导入任何类就可以使用它们:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});
什么时候使用 Facades
Facade 有很多好处。它们提供了一种简洁、令人难忘的语法,允许您使用 Laravel 的特性,而无需记住必须手动注入或配置的长类名。此外,由于它们对 PHP 的动态方法的独特用法,它们很容易测试:
但是,在使用 Facade 时必须小心。Facade 的主要危险是「范围溢出」。由于 Facade 非常容易使用并且不需要注入,所以让类继续增长并在单个类中使用多个 Facade 是很容易的。通过使用依赖注入,一个大型构造函数给您的视觉反馈减轻了这种可能性,即类增长过大。所以,在使用 Facade 的时候,要特别注意你的类规模,这样它的职责范围就不会太窄。如果你的类太大了,可以考虑把它分成多个较小的类。
Facades Vs. 依赖注入
依赖注入的主要好处之一是能够交换注入类的实现。这在测试期间非常有用,因为您可以注入一个 mock 或 stub 并断言在 stub 上调用了各种方法。
通常,真正的静态方法是不可能 mock 或 stub 的。 但是由于 Facades 使用动态方法对服务容器中解析出来的对象方法的调用进行了代理, 我们也可以像测试注入类实例一样测试 Facades。比如,像下面的路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
使用Laravel的 Facade 测试方法, 我们可以编写以下测试用例来验证是否 Cache::get
使用我们期望的参数调用了该方法:
use Illuminate\Support\Facades\Cache;
/**
* 一个进步功能测试用例。
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Facades 相较于辅助函数
除了 Facades,Laravel 还包含各种 『辅助函数』 来实现这些常用功能,比如生成视图、触发事件、任务调度或者发送 HTTP 响应。许多辅助函数都有与之对应的 Facade。例如,下面这个 Facades 和辅助函数的作用是一样的:
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;
/**
* 一个基础功能的测试用例。
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Facades 工作原理
在 Laravel 应用中, Facade 就是一个可以从容器访问对象的类。 其中核心的部件就是 Facade
类。 不管是 Laravel 自带的 Facade, 还是自定义的 Facade, 都继承自 Illuminate\Support\Facades\Facade
类。
Facade
基类使用了 __callStatic()
魔术方法,直到对象从容器中被解析出来后,才会进行调用。在下面的例子中,调用了 Laravel 的缓存系统。通过浏览这段代码,可以假定在 Cache
类中调用了静态方法 get
:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* 显示给定用户的信息。
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
注意在上面这段代码中,我们「导入」了 Cache
Facade。 这个 Facade 作为访问 Illuminate\Contracts\Cache\Factory
接口底层实现的代理。我们使用 Facade 进行的任何调用都将传递给 Laravel 缓存服务的底层实例。
如果我们看一下 Illuminate\Support\Facades\Cache
这个类,你会发现类中根本没有 get
这个静态方法:
class Cache extends Facade
{
/**
* 获取组件的注册名称。
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
相反, Cache
Facade 继承了 Facade
类,并且定义了 getFacadeAccessor()
方法。 这个方法的作用是返回服务容器绑定的名称。 当用户调用 Cache
Facade 中的任何静态方法时, Laravel 会从 服务容器 中解析 cache
绑定以及该对象运行所请求的方法(在这个例子中就是 get
方法)。
实时 Facades
使用实时 Facade, 你可以将应用程序中的任何类视为 Facade。为了说明这是如何使用的, 让我们首先看一下一些不使用实时 Facade 的代码。例如,假设我们的 Podcast
模型有一个 publish
方法。 但是,为了发布 Podcast,我们需要注入一个 Publisher
实例:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 Podcast.
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
将 publisher 的实现注入到该方法中,我们可以轻松地测试这种方法,因为我们可以模拟注入的 publisher 。但是,它要求我们每次调用 publish
方法时始终传递一个 publisher 实例。 使用实时的 Facades, 我们可以保持同样的可测试性,而不需要显式地通过 Publisher
实例。要生成实时 Facade,请在导入类的名称空间中加上 Facades
:
<?php
namespace App\Models;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 Podcast.
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
当使用实时 Facade 时, publisher 实现将通过使用 Facades
前缀后出现的接口或类名的部分来解决服务容器的问题。在测试时,我们可以使用 Laravel 的内置 Facade 测试辅助函数来模拟这种方法调用:
<?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;
/**
* 一个测试示例。
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
Facade 类参考
在下面你可以找到每个 Facade 类及其对应的底层类。这是一个查找给定 Facade 类 API 文档的工具。 服务容器绑定 的关键信息也包含在内。