跳转到主要内容

什么是门面

门面为应用服务容器中可用的类提供了「静态」接口。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 应用中,门面是一种提供访问容器中对象的类。这一机制由 Facade 类实现。Laravel 所有门面(以及自定义门面)都继承基类 Illuminate\Support\Facades\Facade 基类 Facade 通过 __callStatic() 魔术方法,把对门面的调用转发给容器解析出的实际对象。
<?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)。

何时使用门面,何时避免使用

门面的优点

门面有很多优点:无需记住繁长的类名,也不必手动注入或配置,就能以简洁易记的语法使用 Laravel 功能。此外,凭借 PHP 动态方法的独特用法,门面非常易于测试。

注意「作用范围蔓延」

使用门面的主要风险是类的「作用范围蔓延(scope creep)」。因为门面易于使用且无需注入,类很容易膨胀并持续使用大量门面。使用依赖注入时,庞大的构造器能带来直观的反馈。使用门面时,请留意类的规模,将职责范围保持在较小的范围内。
一旦发现类变得过大,请考虑将其拆分为多个更小的类。

门面 vs. 依赖注入

依赖注入的主要好处之一是可以替换被注入类的实现,这在测试时非常有用。你可以注入 mock 或 stub,然后断言其上的方法被以何种方式调用。 通常真正的静态方法难以 mock 或 stub,但门面借助动态方法把调用转发给服务容器解析出的对象,所以对门面进行测试就和测试注入的类实例一样简单。
use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});
针对这条路由,可以编写如下测试来验证 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');
});

门面 vs. 辅助函数

除了门面,Laravel 还提供了执行常见任务(如生成视图、触发事件、派发任务、发送 HTTP 响应等)的「辅助函数」。许多辅助函数的功能与其对应的门面相同。
// 通过门面调用
return Illuminate\Support\Facades\View::make('profile');

// 使用辅助函数的等价调用
return view('profile');
门面与辅助函数在本质上没有区别。即使使用辅助函数,也可以像测试对应门面一样进行测试。
use Illuminate\Support\Facades\Cache;

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

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

    $response->assertSee('value');
});

实时门面

使用实时门面,可以将应用中的任意类当作门面使用。先看一个没有使用实时门面的例子。 例如,Podcast 模型有一个 publish 方法。但发布 podcast 需要注入一个 Publisher 实例。
<?php

namespace App\Models;

use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 发布 podcast
     */
    public function publish(Publisher $publisher): void
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
    }
}
使用实时门面后,就无需显式传入 Publisher 实例,同时也保持了同等的可测试性。要生成实时门面,只需在要导入的类的命名空间前加上 Facades 前缀即可。
<?php

namespace App\Models;

use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 发布 podcast
     */
    public function publish(): void
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this);
    }
}
使用实时门面时,Laravel 会根据 Facades 前缀后面的接口或类名,从服务容器解析出对应的实现。
<?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();
});
当希望简化测试 mock,同时避免将依赖作为参数显式传递时,实时门面非常方便。

测试门面

测试门面时使用 shouldReceive 方法,会返回一个 Mockery mock 实例。由于门面实际上是通过服务容器解析和管理的,因此比普通静态类更易于测试。
use Illuminate\Support\Facades\Cache;

test('显示用户个人资料', function () {
    Cache::shouldReceive('get')
        ->once()
        ->with('user:1')
        ->andReturn(['name' => 'Taylor']);

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

    $response->assertSee('Taylor');
});
常用的 mock 方法如下:
方法说明
shouldReceive('method')期望方法被调用
once()期望只调用 1 次
times(n)期望调用 n 次
with(args)期望以特定参数调用
andReturn(value)返回指定值
andReturnNull()返回 null

常用门面一览

以下是常用门面与其对应实际类、服务容器绑定名的对照表。
门面绑定
AppIlluminate\Foundation\Applicationapp
AuthIlluminate\Auth\AuthManagerauth
CacheIlluminate\Cache\CacheManagercache
ConfigIlluminate\Config\Repositoryconfig
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DBIlluminate\Database\DatabaseManagerdb
EventIlluminate\Events\Dispatcherevents
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate
HashIlluminate\Contracts\Hashing\Hasherhash
HttpIlluminate\Http\Client\Factory
LogIlluminate\Log\LogManagerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager
QueueIlluminate\Queue\QueueManagerqueue
RateLimiterIlluminate\Cache\RateLimiter
RedirectIlluminate\Routing\Redirectorredirect
RequestIlluminate\Http\Requestrequest
RouteIlluminate\Routing\Routerrouter
SchemaIlluminate\Database\Schema\Builder
SessionIlluminate\Session\SessionManagersession
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
URLIlluminate\Routing\UrlGeneratorurl
ValidatorIlluminate\Validation\Factoryvalidator
ViewIlluminate\View\Factoryview

下一步

Contracts(契约)

了解与门面成对的 Contracts 概念及选择方式。
最后修改于 2026年7月13日