Lzh on GitHub

简介

在整个 Laravel 文档中,您会看到通过 “门面(facades)” 与 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 功能交互变得更加容易。您可能会遇到的常见辅助函数包括 viewresponseurlconfig 等。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;
 
/**
 * 一个基本的功能测试示例。
 */
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;
 
/**
 * 一个基本的功能测试示例。
 */
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 Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
 
class UserController extends Controller
{
    /**
     * 显示给定用户的个人资料。
     */
    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
{
    /**
     * 获取组件的注册名称。
     */
    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
{
    /**
     * 发布播客。
     */
    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
{
    /**
     * 发布播客。
     */
    // 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;

pest()->use(RefreshDatabase::class);

test('podcast can be published', function () {
    $podcast = Podcast::factory()->create();

    Publisher::shouldReceive('publish')->once()->with($podcast);

    $podcast->publish();
});

门面类参考

下面您将找到每个门面及其底层类。这是一个有用的工具,可以快速深入研究给定门面根的 API 文档。服务容器绑定 键也包含在适用之处。

门面服务容器绑定
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
Auth (实例)Illuminate\Contracts\Auth\Guardauth.driver
AuthIlluminate\Auth\AuthManagerauth
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
Broadcast (实例)Illuminate\Contracts\Broadcasting\Broadcaster
BroadcastIlluminate\Contracts\Broadcasting\Factory
BusIlluminate\Contracts\Bus\Dispatcher
Cache (实例)Illuminate\Cache\Repositorycache.store
CacheIlluminate\Cache\CacheManagercache
ConfigIlluminate\Config\Repositoryconfig
ContextIlluminate\Log\Context\Repository
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DateIlluminate\Support\DateFactorydate
DB (实例)Illuminate\Database\Connectiondb.connection
DBIlluminate\Database\DatabaseManagerdb
EventIlluminate\Events\Dispatcherevents
Exceptions (实例)Illuminate\Contracts\Debug\ExceptionHandler
ExceptionsIlluminate\Foundation\Exceptions\Handler
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate
HashIlluminate\Contracts\Hashing\Hasherhash
HttpIlluminate\Http\Client\Factory
LangIlluminate\Translation\Translatortranslator
LogIlluminate\Log\LogManagerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager
Password (实例)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Pipeline (实例)Illuminate\Pipeline\Pipeline
ProcessIlluminate\Process\Factory
Queue (基类)Illuminate\Queue\Queue
Queue (实例)Illuminate\Contracts\Queue\Queuequeue.connection
QueueIlluminate\Queue\QueueManagerqueue
RateLimiterIlluminate\Cache\RateLimiter
RedirectIlluminate\Routing\Redirectorredirect
Redis (实例)Illuminate\Redis\Connections\Connectionredis.connection
RedisIlluminate\Redis\RedisManagerredis
RequestIlluminate\Http\Requestrequest
Response (实例)Illuminate\Http\Response
ResponseIlluminate\Contracts\Routing\ResponseFactory
RouteIlluminate\Routing\Routerrouter
ScheduleIlluminate\Console\Scheduling\Schedule
SchemaIlluminate\Database\Schema\Builder
Session (实例)Illuminate\Session\Storesession.store
SessionIlluminate\Session\SessionManagersession
Storage (实例)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
URLIlluminate\Routing\UrlGeneratorurl
Validator (实例)Illuminate\Validation\Validator
ValidatorIlluminate\Validation\Factoryvalidator
View (实例)Illuminate\View\View
ViewIlluminate\View\Factoryview
ViteIlluminate\Foundation\Vite