门面(Facade)
介绍
在 Laravel 的文档中,你会看到很多通过“Facades”与 Laravel 的特性进行交互的代码示例。Facades 为应用程序服务容器中的类提供了“静态”的接口。Laravel 附带了许多 Facades,可提供访问几乎所有 Laravel 特性的功能。
Laravel 的 Facades 作为服务容器中底层类的“静态代理”,提供了简洁、表达性强的语法,同时保持了比传统静态方法更高的可测试性和灵活性。如果你并不完全理解 Facades 如何工作,没关系,继续学习 Laravel 就好了。
Laravel 的所有 Facades 都定义在 Illuminate\Support\Facades
命名空间中。因此,我们可以这样轻松地访问一个 Facade:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});
在 Laravel 的文档中,许多示例都会使用 Facades 来演示框架的各种特性。
辅助函数
为了补充门面类,Laravel 提供了许多全局的“辅助函数”,让与常见的 Laravel 功能交互更加简单。其中一些常用的辅助函数是 view
、response
、url
、config
等。Laravel 提供的每个辅助函数都有对应的功能文档,不过完整的列表可以在专门的辅助函数文档中找到。
例如,我们可以使用 response
函数来生成 JSON 响应,而不是使用 Illuminate\Support\Facades\Response
门面类。由于辅助函数是全局可用的,所以您不需要导入任何类就可以使用它们:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});
什么时候使用 Facades
Facades 是有很多好处的。它们提供了一种简洁、易记的语法,使您可以使用 Laravel 的功能,而无需记住必须手动注入或配置的长类名。此外,由于它们使用 PHP 的动态方法的独特用法,它们很容易测试。
但是,在使用 Facades 时,必须要注意一些问题。Facades 的主要危险是类的“范围蔓延”。由于 Facades 使用起来非常容易,而且不需要注入,因此很容易让您的类继续增长,并在单个类中使用许多 Facades。使用依赖注入,这种潜在问题可以通过构造函数的大小给出视觉反馈,让您知道您的类正在变得过大。因此,在使用 Facades 时,请特别注意类的大小,以便其责任范围保持狭窄。如果您的类变得太大,请考虑将其拆分为多个较小的类。
Facades Vs. 依赖注入
依赖注入最大的优点之一就是能够替换注入的类的实现。在测试时,这是很有用的,因为你可以注入一个模拟或存根并断言存根上调用了各种方法。
通常,我们不可能模拟或存根一个真正的静态类方法。然而,由于Facades使用动态方法将方法调用代理到从服务容器中解析的对象,我们实际上可以像测试注入的类实例一样测试Facades。例如,给定以下路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
使用 Laravel 的 Facades 测试方法,我们可以编写以下测试来验证 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');
}
Facades Vs. 辅助函数
除了使用 Laravel 的 Facades,Laravel 还包括一些 "helper" 函数,可以执行常见的任务,如生成视图、触发事件、调度作业或发送 HTTP 响应等。许多这些帮助函数执行与相应 Facade 相同的功能。例如,这个 Facade 调用和 helper 调用是等效的:
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;
/**
* 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');
}
Facades 是如何工作的
Facades 是 Laravel 应用中提供对容器对象的访问的类。使这一特性生效的类是 Facade
类。Laravel 自带的 Facades,以及开发者自己创建的,都会继承 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
Facade。这个 Facade 用作访问实现了 Illuminate\Contracts\Cache\Factory
接口的类的代理。我们使用 Facade 进行的任何调用都将传递到 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 扩展了基础 Facade 类并定义了 getFacadeAccessor()
方法。此方法的作用是返回服务容器绑定的名称。当用户引用 Cache
Facade 上的任何静态方法时,Laravel 将从服务容器中解析出缓存绑定,并在该对象上运行所请求的方法(在本例中为 get
)。
实时 Facades
使用实时 Facade,您可以将应用程序中的任何类视为外观。为了说明这可以如何使用,让我们先检查一些不使用实时 Fascade 的代码。例如,假设我们的 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
方法时都传递一个发布者实例。使用实时 Fascade,我们可以在不需要显式传递 Publisher
实例的情况下保持相同的可测试性。要生成实时外观,请使用导入类的命名空间前缀 Facades:
<?php
namespace App\Models;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*/
public function publish(): void
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
使用实时外观时,将使用 Facades 前缀之后出现的接口或类名称部分从服务容器中解析出发布者实现。在测试时,我们可以使用 Laravel 内置的外观测试助手来模拟此方法调用:
<?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();
}
}
Facade 类引用
以下是每个 Facade 及其底层类。这是一个有用的工具,可以快速查找给定 Facade 根的 API 文档。如适用,还包括服务容器绑定键。
Facade | Class | 服务容器绑定键 |
---|---|---|
App | Illuminate\Foundation\Application |
app |
Artisan | Illuminate\Contracts\Console\Kernel |
artisan |
Auth | Illuminate\Auth\AuthManager |
auth |
Auth (Instance) | Illuminate\Contracts\Auth\Guard |
auth.driver |
Blade | Illuminate\View\Compilers\BladeCompiler |
blade.compiler |
Broadcast | Illuminate\Contracts\Broadcasting\Factory |
|
Broadcast (Instance) | Illuminate\Contracts\Broadcasting\Broadcaster |
|
Bus | Illuminate\Contracts\Bus\Dispatcher |
|
Cache | Illuminate\Cache\CacheManager |
cache |
Cache (Instance) | Illuminate\Cache\Repository |
cache.store |
Config | Illuminate\Config\Repository |
config |
Cookie | Illuminate\Cookie\CookieJar |
cookie |
Crypt | Illuminate\Encryption\Encrypter |
encrypter |
Date | Illuminate\Support\DateFactory |
date |
DB | Illuminate\Database\DatabaseManager |
db |
DB (Instance) | Illuminate\Database\Connection |
db.connection |
Event | Illuminate\Events\Dispatcher |
events |
File | Illuminate\Filesystem\Filesystem |
files |
Gate | Illuminate\Contracts\Auth\Access\Gate |
|
Hash | Illuminate\Contracts\Hashing\Hasher |
hash |
Http | Illuminate\Http\Client\Factory |
|
Lang | Illuminate\Translation\Translator |
translator |
Log | Illuminate\Log\LogManager |
log |
Illuminate\Mail\Mailer |
mailer |
|
Notification | Illuminate\Notifications\ChannelManager |
|
Password | Illuminate\Auth\Passwords\PasswordBrokerManager |
auth.password |
Password (Instance) | Illuminate\Auth\Passwords\PasswordBroker |
auth.password.broker |
Queue | Illuminate\Queue\QueueManager |
queue |
Queue (Instance) | Illuminate\Contracts\Queue\Queue |
queue.connection |
Queue (Base Class) | Illuminate\Queue\Queue |
|
Redirect | Illuminate\Routing\Redirector |
redirect |
Redis | Illuminate\Redis\RedisManager |
redis |
Redis (Instance) | Illuminate\Redis\Connections\Connection |
redis.connection |
Request | Illuminate\Http\Request |
request |
Response | Illuminate\Contracts\Routing\ResponseFactory |
|
Response (Instance) | Illuminate\Http\Response |
|
Route | Illuminate\Routing\Router |
router |
Schema | Illuminate\Database\Schema\Builder |
|
Session | Illuminate\Session\SessionManager |
session |
Session (Instance) | Illuminate\Session\Store |
session.store |
Storage | Illuminate\Filesystem\FilesystemManager |
filesystem |
No Comments