门面(Facade)


介绍

在 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 来演示框架的各种特性。

辅助函数

为了补充门面类,Laravel 提供了许多全局的“辅助函数”,让与常见的 Laravel 功能交互更加简单。其中一些常用的辅助函数是 viewresponseurlconfig 等。Laravel 提供的每个辅助函数都有对应的功能文档,不过完整的列表可以在专门的辅助函数文档中找到。

例如,我们可以使用 response 函数来生成 JSON 响应,而不是使用 Illuminate\Support\Facades\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 Vs. 依赖注入

依赖注入最大的优点之一就是能够替换注入的类的实现。在测试时,这是很有用的,因为你可以注入一个模拟或存根并断言存根上调用了各种方法。

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

use Illuminate\Support\Facades\Cache;
 
Route::get('/cache', function () {
    return Cache::get('key');
});

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

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 Vs. 辅助函数

除了使用 Laravel 的 Facades,Laravel 还包括一些 "helper" 函数,可以执行常见的任务,如生成视图、触发事件、调度作业或发送 HTTP 响应等。许多这些帮助函数执行与相应 Facade 相同的功能。例如,这个 Facade 调用和 helper 调用是等效的:

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;
 
/**
 * 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 应用中提供对容器对象的访问的类。使这一特性生效的类是 Facade 类。Laravel 自带的 Facades,以及开发者自己创建的,都会继承 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
{
    /**
     * 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 将从服务容器中解析出缓存绑定,并在该对象上运行所请求的方法(在本例中为 get)。

实时 Facades

使用实时 Facade,您可以将应用程序中的任何类视为外观。为了说明这可以如何使用,让我们先检查一些不使用实时 Fascade 的代码。例如,假设我们的 Podcast 模型有一个 publish 方法。但是,为了发布播客,我们需要注入一个 Publisher 实例:

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

将发布者实现注入到方法中使我们能够轻松地测试该方法的隔离性,因为我们可以模拟注入的发布者。但是,它要求我们每次调用 publish 方法时都传递一个发布者实例。使用实时 Fascade,我们可以在不需要显式传递 Publisher 实例的情况下保持相同的可测试性。要生成实时外观,请使用导入类的命名空间前缀 Facades:

<?php

namespace App\Models;

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

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

        Publisher::publish($this);
    }
}

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

<?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 服务容器绑定键
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast Illuminate\Contracts\Broadcasting\Factory
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\CacheManager cache
Cache (Instance) Illuminate\Cache\Repository cache.store
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
Date Illuminate\Support\DateFactory date
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection db.connection
Event Illuminate\Events\Dispatcher events
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 Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\RedisManager redis
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Response (Instance) Illuminate\Http\Response
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Builder
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 服务提供者

>> 下一篇: 路由