契约
简介
你在 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 契约及其对应的门面,方便查阅: