Skip to content
全部文档

Facades

简介

在 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 来演示框架的各项功能。

辅助函数

作为 Facades 的补充,Laravel 还提供了多种全局「辅助函数」,让与常用 Laravel 功能的交互更加轻松。你可能会用到的常见辅助函数包括 viewresponseurlconfig 等。Laravel 提供的每个辅助函数都会在对应功能文档中说明;完整列表请参阅专门的 辅助函数文档

例如,不必使用 Illuminate\Support\Facades\Response Facade 生成 JSON 响应,可以直接使用 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 与依赖注入

依赖注入的主要好处之一,是能够替换被注入类的实现。这在测试时很有用,因为你可以注入 mock 或 stub,并断言 stub 上调用了哪些方法。

通常,真正的静态类方法无法被 mock 或 stub。然而,由于 Facades 使用动态方法将方法调用代理到从服务容器解析出的对象,我们实际上可以像测试注入的类实例一样测试 Facades。例如,给定以下路由:

use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

使用 Laravel 的 Facade 测试方法,我们可以编写如下测试,验证 Cache::get 是否以我们期望的参数被调用:

php
use Illuminate\Support\Facades\Cache;

test('basic example', function () {
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
});
php
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 还包含多种「辅助」函数,可执行生成视图、触发事件、分发任务或发送 HTTP 响应等常见任务。其中许多辅助函数与对应 Facade 的功能相同。例如,下面的 Facade 调用与辅助函数调用是等价的:

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;
php
/**
 * 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 如何工作

在 Laravel 应用中,Facade 是一个提供从容器访问对象的类。实现这一机制的核心位于 Facade 类中。Laravel 的 Facades 以及你创建的任何自定义 Facades,都会扩展基类 Illuminate\Support\Facades\Facade

Facade 基类利用 __callStatic() 魔术方法,将你对 Facade 的调用转发给从容器解析出的对象。在下面的示例中,调用了 Laravel 缓存系统。粗看这段代码,可能会以为是在 Cache 类上调用静态 get 方法:

php
<?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 会从 服务容器 解析 cache 绑定,并在该对象上运行所请求的方法(本例中为 get)。

实时 Facades

使用实时 Facades,你可以将应用中的任意类当作 Facade 来使用。为说明用法,我们先看一段未使用实时 Facades 的代码。例如,假设我们的 Podcast 模型有一个 publish 方法。但要发布播客,需要注入一个 Publisher 实例:

php
<?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);
    }
}

向方法注入 publisher 实现,让我们可以轻松地单独测试该方法,因为可以 mock 被注入的 publisher。不过,每次调用 publish 方法时都必须传入 publisher 实例。使用实时 Facades,我们可以保持相同的可测试性,而无需显式传入 Publisher 实例。要生成实时 Facade,请在导入类的命名空间前加上 Facades 前缀:

php
<?php

namespace App\Models;

use App\Contracts\Publisher; // [tl! remove]
use Facades\App\Contracts\Publisher; // [tl! add]
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(Publisher $publisher): void // [tl! remove]
    public function publish(): void // [tl! add]
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this); // [tl! remove]
        Publisher::publish($this); // [tl! add]
    }
}

使用实时 Facade 时,会使用 Facades 前缀之后的接口或类名部分,从服务容器解析 publisher 实现。测试时,可以使用 Laravel 内置的 Facade 测试辅助方法来 mock 该方法调用:

php
<?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
<?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 文档。在适用处也包含了 服务容器绑定 键。

FacadesClassService Container Binding
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
Auth (Instance)Illuminate\Contracts\Auth\Guardauth.driver
AuthIlluminate\Auth\AuthManagerauth
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
Broadcast (Instance)Illuminate\Contracts\Broadcasting\Broadcaster 
BroadcastIlluminate\Contracts\Broadcasting\Factory 
BusIlluminate\Contracts\Bus\Dispatcher 
Cache (Instance)Illuminate\Cache\Repositorycache.store
缓存Illuminate\Cache\CacheManagercache
ConfigIlluminate\Config\Repositoryconfig
上下文Illuminate\Log\Context\Repository 
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
dateIlluminate\Support\DateFactorydate
DB (Instance)Illuminate\Database\Connectiondb.connection
DBIlluminate\Database\DatabaseManagerdb
EventIlluminate\Events\Dispatcherevents
Exceptions (Instance)Illuminate\Contracts\Debug\ExceptionHandler 
异常Illuminate\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
邮件Illuminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager 
Password (Instance)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
密码Illuminate\Auth\Passwords\PasswordBrokerManagerauth.password
Pipeline (Instance)Illuminate\Pipeline\Pipeline 
ProcessIlluminate\Process\Factory 
Queue (Base Class)Illuminate\Queue\Queue 
Queue (Instance)Illuminate\Contracts\Queue\Queuequeue.connection
QueueIlluminate\Queue\QueueManagerqueue
RateLimiterIlluminate\Cache\RateLimiter 
RedirectIlluminate\Routing\Redirectorredirect
Redis (Instance)Illuminate\Redis\Connections\Connectionredis.connection
RedisIlluminate\Redis\RedisManagerredis
RequestIlluminate\Http\Requestrequest
Response (Instance)Illuminate\Http\Response 
ResponseIlluminate\Contracts\Routing\ResponseFactory 
RouteIlluminate\Routing\Routerrouter
ScheduleIlluminate\Console\Scheduling\Schedule 
SchemaIlluminate\Database\Schema\Builder 
Session (Instance)Illuminate\Session\Storesession.store
SessionIlluminate\Session\SessionManagersession
Storage (Instance)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
存储Illuminate\Filesystem\FilesystemManagerfilesystem
urlIlluminate\Routing\UrlGeneratorurl
Validator (Instance)Illuminate\Validation\Validator 
ValidatorIlluminate\Validation\Factoryvalidator
View (Instance)Illuminate\View\View 
ViewIlluminate\View\Factoryview
ViteIlluminate\Foundation\Vite