Laravel 的契约是一组定义框架提供的核心服务的接口, 例如咱们在介绍用户认证的章节中到的用户看守器契约IllumninateContractsAuthGuard 和用户提供器契约IlluminateContractsAuthUserProviderphp
以及框架自带的App\User
模型所实现的IlluminateContractsAuthAuthenticatable契约。git
经过上面几个契约的源码文件咱们能够看到,Laravel提供的契约是为核心模块定义的一组interface。Laravel为每一个契约都提供了相应的实现类,下表列出了Laravel为上面提到的三个契约提供的实现类。github
契约 | Laravel内核提供的实现类 |
---|---|
IllumninateContractsAuthGuard | IlluminateAuthSessionGuard |
IlluminateContractsAuthUserProvider | IlluminateAuthEloquentUserProvider |
IlluminateContractsAuthAuthenticatable | IlluminateFoundationAuthAuthenticatable(User Model的父类) |
因此在本身开发的项目中,若是Laravel提供的用户认证系统没法知足需求,你能够根据需求定义看守器和用户提供器的实现类,好比我以前作的项目就是用户认证依赖于公司的员工管理系统的API,因此我就本身写了看守器和用户提供器契约的实现类,让Laravel经过自定义的Guard和UserProvider来完成用户认证。自定义用户认证的方法在介绍用户认证的章节中咱们介绍过,读者能够去翻阅那块的文章。数据库
因此Laravel为全部的核心功能都定义契约接口的目的就是为了让开发者可以根据本身项目的须要本身定义实现类,而对于这些接口的消费者(好比:Controller、或者内核提供的 AuthManager这些)他们不须要关心接口提供的方法具体是怎么实现的, 只关心接口的方法能提供什么功能而后去使用这些功能就能够了,咱们能够根据需求在必要的时候为接口更换实现类,而消费端不用进行任何改动。框架
上面咱们提到的都是Laravel内核提供的契约, 在开发大型项目的时候咱们也能够本身在项目中定义契约和实现类,你有可能会以为自带的Controller、Model两层就已经足够你编写代码了,凭空多出来契约和实现类会让开发变得繁琐。咱们先从一个简单的例子出发,考虑下面的代码有什么问题:ide
class OrderController extends Controller { public function getUserOrders() { $orders= Order::where('user_id', '=', \Auth::user()->id)->get(); return View::make('order.index', compact('orders')); } }
这段代码很简单,但咱们要想测试这段代码的话就必定会和实际的数据库发生联系。也就是说, ORM和这个控制器有着紧耦合。若是不使用Eloquent ORM,不链接到实际数据库,咱们就没办法运行或者测试这段代码。这段代码同时也违背了“关注分离”这个软件设计原则。简单讲:这个控制器知道的太多了。 控制器不须要去了解数据是从哪儿来的,只要知道如何访问就行。控制器也不须要知道这数据是从MySQL或哪儿来的,只须要知道这数据目前是可用的。学习
Separation Of Concerns 关注分离Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class.测试
每一个类都应该只有单一的职责,而且职责里全部的东西都应该由这个类封装this
接下来咱们定义一个接口,而后实现该接口设计
interface OrderRepositoryInterface { public function userOrders(User $user); } class OrderRepository implements OrderRepositoryInterface { public function userOrders(User $user) { Order::where('user_id', '=', $user->id)->get(); } }
将接口的实现绑定到Laravel的服务容器中
App::singleton('OrderRepositoryInterface', 'OrderRespository');
而后咱们将该接口的实现注入咱们的控制器
class UserController extends Controller { public function __construct(OrderRepositoryInterface $orderRepository) { $this->orders = $orderRespository; } public function getUserOrders() { $orders = $this->orders->userOrders(); return View::make('order.index', compact('orders')); } }
如今咱们的控制器就彻底和数据层面无关了。在这里咱们的数据可能来自MySQL,MongoDB或者Redis。咱们的控制器不知道也不须要知道他们的区别。这样咱们就能够独立于数据层来测试Web层了,未来切换存储实现也会很容易。
当你的团队在开发大型应用时,不一样的部分有着不一样的开发速度。好比一个开发人员在开发数据层,另外一个开发人员在作控制器层。写控制器的开发者想测试他的控制器,不过数据层开发较慢无法同步测试。那若是两个开发者能先以interface的方式达成协议,后台开发的各类类都遵循这种协议。一旦创建了约定,就算约定还没实现,开发者也能够为这接口写个“假”实现
class DummyOrderRepository implements OrderRepositoryInterface { public function userOrders(User $user) { return collect(['Order 1', 'Order 2', 'Order 3']); } }
一旦假实现写好了,就能够被绑定到IoC容器里
App::singleton('OrderRepositoryInterface', 'DummyOrderRepository');
而后这个应用的视图就能够用假数据填充了。接下来一旦后台开发者写完了真正的实现代码,好比叫RedisOrderRepository
。那么使用IoC容器切换接口实现,应用就能够轻易地切换到真正的实现上,整个应用就会使用从Redis读出来的数据了。
创建好接口约定后也更有利于咱们在测试时进行Mock
public function testIndexActionBindsUsersFromRepository() { // Arrange... $repository = Mockery::mock('OrderRepositoryInterface'); $repository->shouldReceive('userOrders')->once()->andReturn(['order1', 'order2]); App::instance('OrderRepositoryInterface', $repository); // Act... $response = $this->action('GET', 'OrderController@getUserOrders'); // Assert... $this->assertResponseOk(); $this->assertViewHas('order', ['order1', 'order2']); }
接口在程序设计阶段很是有用,在设计阶段与团队讨论完成功能须要制定哪些接口,而后设计出每一个接口具体要实现的方法,方法的入参和返回值这些,每一个人就能够按照接口的约定来开发本身的模块,遇到还没实现的接口彻底能够先定义接口的假实现等到真正的实现开发完成后再进行切换,这样既下降了软件程序结构中上层对下层的耦合也能保证各部分的开发进度不会过分依赖其余部分的完成状况。
本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。