概述
Laravel 的 Contracts
是一组定义了框架核心服务的接口( interfaces )。 例如Illuminate\Contracts\Queue\Queue
契约定义了队列任务需要实现的方法,Illuminate\Contracts\Mail\Mailer
契约定义了发送邮件所需要实现的方法。
每一个契约都有框架提供的相应实现。例如,Laravel 为队列提供了多个驱动的实现,邮件则由 SwiftMailer 驱动实现 。
所有 Laravel 契约都有其对应的GitHub库,这为所有有效的契约提供了快速入门指南,同时也可以作为独立、解耦的包被包开发者使用。当程序变得越来大,这种通过合同或者接口来解耦所带来的可扩展性和可维护性是无可比拟的。
上图不使用Contracts的情况下,对于一种逻辑,我们只能得到一种结果(方块),如果变更需求,意味着我们必须重构代码和逻辑。但是在使用Contracts的情况下,我们只需要按照接口写好逻辑,然后提供不同的实现,就可以在不改动代码逻辑的情况下获得更加多态的结果。
契约(Contracts) Vs. 门面(Facades)
Laravel 门面为 Laravel 服务的使用提供了便捷方式——不再需要从服务容器中类型提示 和 契约(Contracts)解析即可直接通过静态 门面(Facade) 调用。
不同于 门面(Facade) 不需要再构造器中进行类型提示,契约(Contracts) 允许你在类中定义显式的依赖。有些开发者喜欢门面(Facade)带来的便捷,也有些开发者倾向于使用契约(Contracts),他们喜欢定义明确的依赖。
注:大多数应用中,不管你使用门面还是契约,合适就好。不过,如果你是在构建一个扩展包,那么就应该使用契约,因为更容易测试。
何时使用契约
为什么要定义接口
定义接口目的为了解耦
使用接口的优点:松耦合和简单。
首先,让我们看看一些缓存实现的紧耦合代码:
<?php
namespace App\Orders;
class Repository
{
/**
* 缓存
*/
protected $cache;
/**
* 创建一个新的Repository实例
*
* @param \SomePackage\Cache\Memcached $cache
* @return void
*/
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
/**
* 通过ID获取订单
*
* @param int $id
* @return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
问题
在这个类中,代码和给定缓存实现紧密耦合,由于我们基于一个来自包的具体的缓存类,如果包的API变了,那么相应的,我们的代码必须做修改。
类似的,如果我们想要替换底层的缓存技术(Memcached)为别的技术实现(Redis),我们将再一次不得不修改我们的代码库。我们的代码库应该并不知道谁提供的数据或者数据是怎么提供的。
我们可以创建一个简单的、与提供者无关的接口:
namespace App\Contracts;
use Closure;
interface Repository
{
public function setTag($tag);
public function setTime($time_in_minute);
public function remember($key, Closure $entity, $tag = null);
public function forget($key, $tag = null);
public function clearCache($tag = null);
public function clearAllCache();
}
然后再利用容器的绑定,根据不同的配置,返回不同的实现:
public function register()
{
$this->app->bind('Repository', function ($app) {
if (config('cache.enable') == 'true') {
return new Memcached();
} else {
return new Redis();
}
});
}
我们可以基于一种简单的、与提供者无关的接口来优化我们的代码,从而替代上述那种实现:
<?php
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class Repository
{
/**
* 创建一个新的Repository实例
*
* @param Cache $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
现在代码就不与任何特定提供者耦合,甚至与 Laravel 都是无关的。由于契约包不包含任何实现和依赖,你可以轻松的为给定契约编写可选实现代码,你可以随意替换缓存实现而不用去修改任何缓存消费代码。
简单
当所有 Laravel 服务都统一在简单接口中定义,很容易判断给定服务提供的功能。契约可以充当框架特性的简明文档。
此外,基于简单接口,代码也更容易理解和维护。在一个庞大而复杂的类中,与其追踪哪些方法是有效的,不如转向简单、干净的接口。
如何使用契约
那么,如何实现契约呢?这很简单。
Laravel中很多类都是通过服务容器进行解析,包括控制器,以及监听器、中间件、队列任务,甚至路由闭包。所以,要实现一个契约,需要在解析类的构造函数中类型提示这个契约接口。
<?php
namespace App\Listeners;
use App\User;
use App\Events\OrderWasPlaced;
use Illuminate\Contracts\Redis\Database;
class CacheOrderInformation
{
/**
* Redis数据库实现。
*/
protected $redis;
/**
* 创建一个新的事件处理器实例。
*
* @param Database $redis
* @return void
*/
public function __construct(Database $redis)
{
$this->redis = $redis;
}
/**
* 处理事件。
*
* @param OrderWasPlaced $event
* @return void
*/
public function handle(OrderWasPlaced $event)
{
//
}
}
事件监听器被解析的时候,服务容器会读取构造函数中的类型提示,并注入适当的值。
契约列表
下面是 Laravel 契约列表,以及其对应的“门面”:
契约(Contract) | 门面(Facade) |
---|---|
Illuminate\Contracts\Auth\Factory | Auth |
Illuminate\Contracts\Auth\PasswordBroker | Password |
Illuminate\Contracts\Bus\Dispatcher | Bus |
Illuminate\Contracts\Broadcasting\Broadcaster | |
Illuminate\Contracts\Cache\Repository | Cache |
Illuminate\Contracts\Cache\Factory | Cache::driver() |
Illuminate\Contracts\Config\Repository | Config |
Illuminate\Contracts\Container\Container | App |
Illuminate\Contracts\Cookie\Factory | Cookie |
Illuminate\Contracts\Cookie\QueueingFactory | Cookie::queue() |
Illuminate\Contracts\Encryption\Encrypter | Crypt |
Illuminate\Contracts\Events\Dispatcher | Event |
Illuminate\Contracts\Filesystem\Cloud | |
Illuminate\Contracts\Filesystem\Factory | File |
Illuminate\Contracts\Filesystem\Filesystem | File |
Illuminate\Contracts\Foundation\Application | App |
Illuminate\Contracts\Hashing\Hasher | Hash |
Illuminate\Contracts\Logging\Log | Log |
Illuminate\Contracts\Mail\MailQueue | Mail::queue() |
Illuminate\Contracts\Mail\Mailer | |
Illuminate\Contracts\Queue\Factory | Queue::driver() |
Illuminate\Contracts\Queue\Queue | Queue |
Illuminate\Contracts\Redis\Database | Redis |
Illuminate\Contracts\Routing\Registrar | Route |
Illuminate\Contracts\Routing\ResponseFactory | Response |
Illuminate\Contracts\Routing\UrlGenerator | URL |
Illuminate\Contracts\Support\Arrayable | |
Illuminate\Contracts\Support\Jsonable | |
Illuminate\Contracts\Support\Renderable | |
Illuminate\Contracts\Validation\Factory | Validator::make() |
Illuminate\Contracts\Validation\Validator | |
Illuminate\Contracts\View\Factory | View::make() |
Illuminate\Contracts\View\View |