契约

简介

你在 Laravel 中肯定用过 Cache::get()、Log::info() 这类写法——通过门面(Facade)来调用框架的各种服务。但你可能在别人的包里见过另一种写法:在构造函数里注入一个 Illuminate\Contracts\Cache\Repository 接口,而不是直接用 Cache 门面。这个接口就是「契约」。

契约就是一组接口(interface),定义了框架核心服务的能力边界。 比如 Illuminate\Contracts\Queue\Queue 契约定义了队列服务需要哪些方法(推送任务、弹出任务等),Illuminate\Contracts\Mail\Mailer 契约定义了邮件服务需要哪些方法(发送邮件等)。每个契约都有框架提供的具体实现——队列有 Redis、数据库等多种驱动实现,邮件由 Symfony Mailer 驱动。

所有 Laravel 契约都放在独立的 GitHub 仓库里,方便你查阅所有可用契约,也方便第三方包只依赖这个轻量包而不引入整个 Laravel 框架。

契约 vs. 门面

门面和契约是同一套服务的两种不同访问方式。门面让你不用类型提示、不用从容器里解析,直接静态调用就能用;契约则要求你在构造函数里声明依赖,由服务容器自动注入。

两种方式各有各的好处:门面写着简单、用着方便,大多数应用开发中直接用门面完全没问题;契约让你的依赖关系更显式,类的可测试性更好。总的来说,应用开发用门面就够了,开发给多个框架用的第三方包时用契约更合适。 它们并不互斥——同一个应用里可以一部分用门面、一部分用契约。

什么时候用契约

用契约还是门面,很大程度上是个人和团队的偏好。两种方式都能写出健壮的、经过充分测试的 Laravel 应用。只要你保持类的职责单一,实际体验上差别很小。

一般的 Web 应用直接用门面开发就好。但如果你在开发一个需要兼容多个 PHP 框架的包,用 illuminate/contracts 包来定义对 Laravel 服务的依赖会更合适——这样你的包不需要在 composer.json 里引入 Laravel 的具体实现,只依赖接口契约就行。

怎么用契约

用法很简单:在类的构造函数里类型提示契约接口,Laravel 的服务容器会自动解析并注入对应的具体实现。

控制器、事件监听器、中间件、队列任务、路由闭包——这些类都是通过服务容器解析的,所以你都可以在构造函数里注入契约。比如这个事件监听器,它在构造函数里注入了 Illuminate\Contracts\Redis\Factory 契约:

<?php

namespace App\Listeners;

use App\Events\OrderWasPlaced;
use App\Models\User;
use Illuminate\Contracts\Redis\Factory;

class CacheOrderInformation
{
    /**
     * Create the event listener.
     */
    public function __construct(
        protected Factory $redis,
    ) {}

    /**
     * Handle the event.
     */
    public function handle(OrderWasPlaced $event): void
    {
        // ...
    }
}

当这个监听器被解析时,服务容器会读取构造函数的类型提示,找到对应的 Redis 实现并注入。想了解更多关于服务容器注册和绑定,看服务容器文档。

契约参考

下表列出了所有 Laravel 契约及其对应的门面,方便查阅:

契约 对应门面
Illuminate\Contracts\Auth\Access\Authorizable
Illuminate\Contracts\Auth\Access\Gate Gate
Illuminate\Contracts\Auth\Authenticatable
Illuminate\Contracts\Auth\CanResetPassword
Illuminate\Contracts\Auth\Factory Auth
Illuminate\Contracts\Auth\Guard Auth::guard()
Illuminate\Contracts\Auth\PasswordBroker Password::broker()
Illuminate\Contracts\Auth\PasswordBrokerFactory Password
Illuminate\Contracts\Auth\StatefulGuard
Illuminate\Contracts\Auth\SupportsBasicAuth
Illuminate\Contracts\Auth\UserProvider
Illuminate\Contracts\Broadcasting\Broadcaster Broadcast::connection()
Illuminate\Contracts\Broadcasting\Factory Broadcast
Illuminate\Contracts\Broadcasting\ShouldBroadcast
Illuminate\Contracts\Broadcasting\ShouldBroadcastNow
Illuminate\Contracts\Bus\Dispatcher Bus
Illuminate\Contracts\Bus\QueueingDispatcher Bus::dispatchToQueue()
Illuminate\Contracts\Cache\Factory Cache
Illuminate\Contracts\Cache\Lock
Illuminate\Contracts\Cache\LockProvider
Illuminate\Contracts\Cache\Repository Cache::driver()
Illuminate\Contracts\Cache\Store
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Console\Application
Illuminate\Contracts\Console\Kernel Artisan
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Cookie\QueueingFactory Cookie::queue()
Illuminate\Contracts\Database\ModelIdentifier
Illuminate\Contracts\Debug\ExceptionHandler
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud Storage::cloud()
Illuminate\Contracts\Filesystem\Factory Storage
Illuminate\Contracts\Filesystem\Filesystem Storage::disk()
Illuminate\Contracts\Foundation\Application App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Http\Kernel
Illuminate\Contracts\Mail\Mailable
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Mail\MailQueue Mail::queue()
Illuminate\Contracts\Notifications\Dispatcher Notification
Illuminate\Contracts\Notifications\Factory Notification
Illuminate\Contracts\Pagination\LengthAwarePaginator
Illuminate\Contracts\Pagination\Paginator
Illuminate\Contracts\Pipeline\Hub
Illuminate\Contracts\Pipeline\Pipeline Pipeline
Illuminate\Contracts\Queue\EntityResolver
Illuminate\Contracts\Queue\Factory Queue
Illuminate\Contracts\Queue\Job
Illuminate\Contracts\Queue\Monitor Queue
Illuminate\Contracts\Queue\Queue Queue::connection()
Illuminate\Contracts\Queue\QueueableCollection
Illuminate\Contracts\Queue\QueueableEntity
Illuminate\Contracts\Queue\ShouldQueue
Illuminate\Contracts\Redis\Factory Redis
Illuminate\Contracts\Routing\BindingRegistrar Route
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFactory Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Routing\UrlRoutable
Illuminate\Contracts\Session\Session Session::driver()
Illuminate\Contracts\Support\Arrayable
Illuminate\Contracts\Support\Htmlable
Illuminate\Contracts\Support\Jsonable
Illuminate\Contracts\Support\MessageBag
Illuminate\Contracts\Support\MessageProvider
Illuminate\Contracts\Support\Renderable
Illuminate\Contracts\Support\Responsable
Illuminate\Contracts\Translation\Loader
Illuminate\Contracts\Translation\Translator Lang
Illuminate\Contracts\Validation\Factory Validator
Illuminate\Contracts\Validation\ValidatesWhenResolved
Illuminate\Contracts\Validation\ValidationRule
Illuminate\Contracts\Validation\Validator Validator::make()
Illuminate\Contracts\View\Engine
Illuminate\Contracts\View\Factory View
Illuminate\Contracts\View\View View::make()