Facades

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

Facades

介绍

贯穿整个 Laravel 文档, 可以看到许多通过 Facades 与 Laravel 功能交互的代码示例。Facades 为应用 服务容器 中的类提供「静态」接口。 并且 Laravel 自带了许多 Facades, 它们几乎可以调用到 Laravel 中所有的功能。

Laravel Facades 充当服务容器中底层类的「静态代理」,提供简洁、富有表现力的语法的优点, 同时保持比传统静态方法更高的可测试性和灵活性。如果你不完全理解 Facades 是如何工作的, 那也没关系 - 只需正常学习 Laravel 即可, 后面自然而然就会明白了。

Laravel 的所有 Facades 都定义在 Illuminate\Support\Facades 命名空间。可以像这样访问 Facades:

    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Facades\Route;

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

辅助函数

为了补充 Facades 能力, Laravel 提供了全局「辅助函数」, 和常见一些功能交互变得更加容易, 常见辅助函数有 viewresponseurlconfig 等。 Laravel 提供的每个辅助函数都有其相应作用;帮助文档 提供了完整的列表可供查看所有辅助函数。

例如,可以使用 response 函数,而不是使用 Illuminate\Support\Facades\Response Facade 来生成 JSON 响应。辅助函数是全局可用的,无需导入任何类即可使用:

    use Illuminate\Support\Facades\Response;

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

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

何时使用 Facades

Facades 提供了简洁、易于记忆的语法, 无需记住要手动注入或配置的长类名。此外,由于它是 PHP 动态调用的, 很容易测试。

然而,在使用 Facades 时必须小心。它的主要危险是类的「作用范围扩张」。Facades 的易于使用且不需要注入, 这就导致在类的持续开发过程中, 很容易不知不觉用到许多的 Facades。使用依赖注入时, 这种潜在问题通过构造函数变得更为明显,类慢慢变得越来越大。所以,在使用 Facades 时,需要特别关注类的大小, 以便它保持责任范围单一, 如果类变得太大, 请考虑将它拆分成多个较小的类。

Facades vs. 依赖注入

依赖注入的主要好处之一是能够交换注入类的实现。这在测试时非常有用,可以注入 mock 或 stub,并断言在 stub 上调用了各种方法。

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

    use Illuminate\Support\Facades\Cache;

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

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

// Pest
use Illuminate\Support\Facades\Cache;

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

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

    $response->assertSee('value');
});
// PHPUnit
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 vs. 辅助函数

除了 Facades,Laravel 还包含许多 「辅助」函数,这些函数可以执行常见的任务,如生成视图、触发事件、调度任务或发送 HTTP 响应。许多这些辅助函数与对应的 Facades 执行相同的功能。例如,以下 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;

    /**
     * 一个简单的功能测试示例。
     */
    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
    {
        /**
         * 显示给定用户的个人资料。
         */
        public function showProfile(string $id): View
        {
            $user = Cache::get('user:'.$id);

            return view('profile', ['user' => $user]);
        }
    }

注意观察上面的代码, 在顶部 「导入」了 Cache Facade。这个 Facade 作为代理来访问 Illuminate\Contracts\Cache\Factory 接口的底层实现。使用 Facade 进行的任何调用都将传递给底层实例的相应方法。由于 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);
        }
    }

将发布者实现注入方法中可以让我们轻松地隔离测试该方法,因为我们可以模拟被注入的发布者。然而,它要求我们每次调用 publish 方法时总是传递一个发布者实例。使用实时 Facades,我们可以在不需要显式传递 Publisher 实例的情况下保持相同的可测试性。为了生成一个实时 facade,将导入类的命名空间前缀加上 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]
        }
    }

当使用实时 Facade 时,Publisher 的实现将从服务容器中解析出来,解析的依据是接口或类名中出现在 Facades 前缀之后的部分。在测试时,可以使用 Laravel 内置的 Facade 测试辅助函数来模拟这个方法调用:

<?php
// Pest
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
// PHPUnit
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();
    }
}

Facade 类引用

下面表格列出每个 Facade 及其底层类, 可以快速查阅给定 Facade 根目录的 API 文档, 还包含了 服务容器绑定 的键名称(key)。

Facade 服务容器绑定名称
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
Exceptions Illuminate\Foundation\Exceptions\Handler  
Exceptions (Instance) Illuminate\Contracts\Debug\ExceptionHandler  
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
Pipeline (Instance) Illuminate\Pipeline\Pipeline  
Process Illuminate\Process\Factory  
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue  
RateLimiter Illuminate\Cache\RateLimiter  
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
Schedule Illuminate\Console\Scheduling\Schedule  
Schema Illuminate\Database\Schema\Builder  
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator  
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View  
Vite Illuminate\Foundation\Vite  

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/11.x/fa...

译文地址:https://learnku.com/docs/laravel/11.x/fa...

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
贡献者:4
讨论数量: 0
发起讨论 只看当前版本


暂无话题~