laravel
3. 架构概念
外观(Facades)

介绍

在整个 Laravel 文档中,您会看到通过“外观”与 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 提供的每个辅助函数都在其相应的功能文档中有记录;然而,完整的列表可在专门的辅助函数文档中找到。

例如,要生成一个 JSON 响应,我们可以使用 response 函数,而不是使用 Illuminate\Support\Facades\Response 外观。因为辅助函数是全局可用的,所以您不需要导入任何类就可以使用它们:

use Illuminate\Support\Facades\Response;

Route::get('/users', function () {
    return Response::json([
        //...
    ]);
});

Route::get('/users', function () {
    return response()->json([
        //...
    ]);
});

何时使用外观

外观有许多好处。它们提供了简洁、易记的语法,使您可以使用 Laravel 的功能,而无需记住必须手动注入或配置的长类名。此外,由于它们对 PHP 动态方法的独特使用,它们很容易测试。

然而,在使用外观时必须小心一些问题。外观的主要危险是类的“范围蔓延”。由于外观很容易使用且不需要注入,很容易让您的类不断增长,并在一个类中使用许多外观。使用依赖注入时,大型构造函数给您的视觉反馈可以减轻这种潜在问题,即您的类变得太大。因此,在使用外观时,要特别注意您的类的大小,以使它的责任范围保持狭窄。如果您的类变得太大,请考虑将其拆分为多个较小的类。

外观与依赖注入

依赖注入的一个主要好处是能够交换注入类的实现。这在测试期间很有用,因为您可以注入一个模拟或存根,并断言在存根上调用了各种方法。

通常,不可能模拟或存根一个真正的静态类方法。然而,由于外观使用动态方法将方法调用代理到从服务容器解析的对象,我们实际上可以像测试注入的类实例一样测试外观。例如,给定以下路由:

use Illuminate\Support\Facades\Cache;

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

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

use Illuminate\Support\Facades\Cache;
 
test('基本示例', function () {
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');
 
    $response = $this->get('/cache');
 
    $response->assertSee('value');
});
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 还包括各种“辅助”函数,这些函数可以执行常见任务,如生成视图、触发事件、调度任务或发送 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');
}

外观(Facades)的工作原理

在 Laravel 应用中,外观(facade)是一个类,用于从容器中访问对象。实现此功能的机制在 Facade 类中。Laravel 的外观以及您创建的任何自定义外观都将扩展基础的 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
{
    /**
     * 显示给定用户的个人资料。
     */
    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)。

实时外观(Real-Time 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);
    }
}

将发布者实现注入到方法中,使我们能够轻松地单独测试该方法,因为我们可以模拟注入的发布者。然而,这要求我们在每次调用 publish 方法时都始终传递一个发布者实例。使用实时外观,我们可以保持相同的可测试性,而无需显式传递 Publisher 实例。要生成实时外观,将导入类的命名空间前缀为 Facades

<?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
{
    /**
     * 发布播客。
     */
    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]
    }
}

当使用实时外观时,发布者实现将使用出现在 Facades 前缀之后的接口或类名的部分从服务容器中解析出来。在测试时,我们可以使用 Laravel 内置的外观测试助手来模拟此方法调用:

<?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;
 
    /**
     * 一个测试示例。
     */
    public function test_podcast_can_be_published(): void
    {
        $podcast = Podcast::factory()->create();
 
        Publisher::shouldReceive('publish')->once()->with($podcast);
 
        $podcast->publish();
    }
}

外观类参考

在下方,您将找到每个外观及其底层类。这是一个有用的工具,可用于快速深入研究给定外观根的 API 文档。在适用的情况下,还包括服务容器绑定键。

外观服务容器绑定
AppIlluminate\Foundation\Application (opens in a new tab)app
ArtisanIlluminate\Contracts\Console\Kernel (opens in a new tab)artisan
Auth(实例)Illuminate\Contracts\Auth\Guard (opens in a new tab)auth.driver
AuthIlluminate\Auth\AuthManager (opens in a new tab)auth
BladeIlluminate\View\Compilers\BladeCompiler (opens in a new tab)blade.compiler
Broadcast(实例)Illuminate\Contracts\Broadcasting\Broadcaster (opens in a new tab)
BroadcastIlluminate\Contracts\Broadcasting\Factory (opens in a new tab)
BusIlluminate\Contracts\Bus\Dispatcher (opens in a new tab)
Cache(实例)Illuminate\Cache\Repository (opens in a new tab)cache.store
CacheIlluminate\Cache\CacheManager (opens in a new tab)cache
ConfigIlluminate\Config\Repository (opens in a new tab)config
ContextIlluminate\Log\Context\Repository (opens in a new tab)
CookieIlluminate\Cookie\CookieJar (opens in a new tab)cookie
CryptIlluminate\Encryption\Encrypter (opens in a new tab)encrypter
DateIlluminate\Support\DateFactory (opens in a new tab)date
DB(实例)Illuminate\Database\Connection (opens in a new tab)db.connection
DBIlluminate\Database\DatabaseManager (opens in a new tab)db
EventIlluminate\Events\Dispatcher (opens in a new tab)events
Exceptions(实例)Illuminate\Contracts\Debug\ExceptionHandler (opens in a new tab)
ExceptionsIlluminate\Foundation\Exceptions\Handler (opens in a new tab)
FileIlluminate\Filesystem\Filesystem (opens in a new tab)files
GateIlluminate\Contracts\Auth\Access\Gate (opens in a new tab)
HashIlluminate\Contracts\Hashing\Hasher (opens in a new tab)hash
HttpIlluminate\Http\Client\Factory (opens in a new tab)
LangIlluminate\Translation\Translator (opens in a new tab)translator
LogIlluminate\Log\LogManager (opens in a new tab)log
MailIlluminate\Mail\Mailer (opens in a new tab)mailer
NotificationIlluminate\Notifications\ChannelManager (opens in a new tab)
Password(实例)Illuminate\Auth\Passwords\PasswordBroker (opens in a new tab)auth.password.broker
PasswordIlluminate\Auth\Passwords\PasswordBrokerManager (opens in a new tab)auth.password
Pipeline(实例)Illuminate\Pipeline\Pipeline (opens in a new tab)
ProcessIlluminate\Process\Factory (opens in a new tab)
Queue(基类)Illuminate\Queue\Queue (opens in a new tab)
Queue(实例)Illuminate\Contracts\Queue\Queue (opens in a new tab)queue.connection
QueueIlluminate\Queue\QueueManager (opens in a new tab)queue
RateLimiterIlluminate\Cache\RateLimiter (opens in a new tab)
RedirectIlluminate\Routing\Redirector (opens in a new tab)redirect
Redis(实例)Illuminate\Redis\Connections\Connection (opens in a new tab)redis.connection
RedisIlluminate\Redis\RedisManager (opens in a new tab)redis
RequestIlluminate\Http\Request (opens in a new tab)request
Response(实例)Illuminate\Http\Response (opens in a new tab)
ResponseIlluminate\Contracts\Routing\ResponseFactory (opens in a new tab)
RouteIlluminate\Routing\Router (opens in a new tab)router
ScheduleIlluminate\Console\Scheduling\Schedule (opens in a new tab)
SchemaIlluminate\Database\Schema\Builder (opens in a new tab)
Session(实例)Illuminate\Session\Store (opens in a new tab)session.store
SessionIlluminate\Session\SessionManager (opens in a new tab)session
Storage(实例)Illuminate\Contracts\Filesystem\Filesystem (opens in a new tab)filesystem.disk
StorageIlluminate\Filesystem\FilesystemManager (opens in a new tab)filesystem
URLIlluminate\Routing\UrlGenerator (opens in a new tab)url
Validator(实例)Illuminate\Validation\Validator (opens in a new tab)
ValidatorIlluminate\Validation\Factory (opens in a new tab)validator
View(实例)Illuminate\View\View (opens in a new tab)
ViewIlluminate\View\Factory (opens in a new tab)view
ViteIlluminate\Foundation\Vite (opens in a new tab)