门面

介绍

在 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 提供了各种全局的 "helper 函数",使与常见 Laravel 功能的交互变得更加简便。您可能会使用的一些常见 helper 函数包括 viewresponseurlconfig 等。每个 Laravel 提供的 helper 函数都有相应的文档,详细介绍它们的功能;不过,完整的列表可以在专门的 helper 文档中找到。

例如,您可以使用 response 函数来生成 JSON 响应,而不是使用 Illuminate\Support\Facades\Response facade。由于 helper 函数是全局可用的,您不需要导入任何类即可使用它们:

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 方法是否使用了我们预期的参数:

  • Pest

  • PHPUnit

use Illuminate\Support\Facades\Cache;

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

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

    $response->assertSee('value');
});
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 还包括各种 "helper" 函数,这些函数可以执行常见任务,如生成视图、触发事件、调度作业或发送 HTTP 响应。这些 helper 函数执行的功能与相应的 facade 相同。例如,下面的 facade 调用和 helper 调用是等效的:

return Illuminate\Support\Facades\View::make('profile');

return view('profile');

facades 和 helper 函数在实际使用中没有任何区别。当使用 helper 函数时,您仍然可以像测试对应的 facade 一样测试它们。例如,给定以下路由:

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

cache helper 会调用 Cache facade 底层类的 get 方法。因此,即使我们使用的是 helper 函数,我们仍然可以编写以下测试来验证该方法是否使用了我们预期的参数:

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');
}

Facades 的工作原理

在 Laravel 应用程序中,facade 是一个提供访问容器中对象的类。使这一切能够正常工作的机制位于 Facade 类中。Laravel 的 facades 以及你自己创建的任何自定义 facades 都将扩展基础的 Illuminate\Support\Facades\Facade 类。

Facade 基类利用了 __callStatic() 魔术方法,将来自 facade 的调用延迟到从容器中解析出的对象。在下面的示例中,我们对 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 会从服务容器中解析出 cache 绑定,并对该对象执行请求的方法(在这个例子中是 get)。

实时 Facades

使用实时 facades,你可以将应用程序中的任何类当作 facade 来使用。为了说明如何使用这一点,我们首先来看一些不使用实时 facades 的代码。例如,假设我们的 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);
    }
}

Publisher 实现注入到方法中,使我们能够轻松地在隔离的情况下测试该方法,因为我们可以模拟注入的 publisher。但是,每次调用 publish 方法时,我们都需要显式地传递一个 publisher 实例。使用实时 facades,我们可以保持相同的可测试性,同时不需要显式地传递一个 Publisher 实例。为了生成一个实时 facade,我们只需将导入类的命名空间前缀加上 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(): void
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
        Publisher::publish($this);
    }
}

当使用实时 facade 时,publisher 实现将通过服务容器解析出来,使用接口或类名中 Facades 前缀后的部分。在测试时,我们可以使用 Laravel 内置的 facade 测试帮助器来模拟这个方法调用:

  • Pest

  • PHPUnit

<?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

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 Service Container Binding

App

Illuminate\Foundation\Application

app

Artisan

Illuminate\Contracts\Console\Kernel

artisan

Auth (Instance)

Illuminate\Contracts\Auth\Guard

auth.driver

Auth

Illuminate\Auth\AuthManager

auth

Blade

Illuminate\View\Compilers\BladeCompiler

blade.compiler

Broadcast (Instance)

Illuminate\Contracts\Broadcasting\Broadcaster

Broadcast

Illuminate\Contracts\Broadcasting\Factory

Bus

Illuminate\Contracts\Bus\Dispatcher

Cache (Instance)

Illuminate\Cache\Repository

cache.store

Cache

Illuminate\Cache\CacheManager

cache

Config

Illuminate\Config\Repository

config

Context

Illuminate\Log\Context\Repository

Cookie

Illuminate\Cookie\CookieJar

cookie

Crypt

Illuminate\Encryption\Encrypter

encrypter

Date

Illuminate\Support\DateFactory

date

DB (Instance)

Illuminate\Database\Connection

db.connection

DB

Illuminate\Database\DatabaseManager

db

Event

Illuminate\Events\Dispatcher

events

Exceptions (Instance)

Illuminate\Contracts\Debug\ExceptionHandler

Exceptions

Illuminate\Foundation\Exceptions\Handler

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

Mail

Illuminate\Mail\Mailer

mailer

Notification

Illuminate\Notifications\ChannelManager

Password (Instance)

Illuminate\Auth\Passwords\PasswordBroker

auth.password.broker

Password

Illuminate\Auth\Passwords\PasswordBrokerManager

auth.password

Pipeline (Instance)

Illuminate\Pipeline\Pipeline

Process

Illuminate\Process\Factory

Queue (Base Class)

Illuminate\Queue\Queue

Queue (Instance)

Illuminate\Contracts\Queue\Queue

queue.connection

Queue

Illuminate\Queue\QueueManager

queue

RateLimiter

Illuminate\Cache\RateLimiter

Redirect

Illuminate\Routing\Redirector

redirect

Redis (Instance)

Illuminate\Redis\Connections\Connection

redis.connection

Redis

Illuminate\Redis\RedisManager

redis

Request

Illuminate\Http\Request

request

Response (Instance)

Illuminate\Http\Response

Response

Illuminate\Contracts\Routing\ResponseFactory

Route

Illuminate\Routing\Router

router

Schedule

Illuminate\Console\Scheduling\Schedule

Schema

Illuminate\Database\Schema\Builder

Session (Instance)

Illuminate\Session\Store

session.store

Session

Illuminate\Session\SessionManager

session

Storage (Instance)

Illuminate\Contracts\Filesystem\Filesystem

filesystem.disk

Storage

Illuminate\Filesystem\FilesystemManager

filesystem

URL

Illuminate\Routing\UrlGenerator

url

Validator (Instance)

Illuminate\Validation\Validator

Validator

Illuminate\Validation\Factory

validator

View (Instance)

Illuminate\View\View

View

Illuminate\View\Factory

view

Vite

Illuminate\Foundation\Vite