ContractsLaravel的契约是一组接口,定义了框架提供的核心服务,例如用户守卫契约IlluminateContractsAuthGuard和用户提供者契约IlluminateContractsAuthUserProvider以及框架自建的。IlluminateContractsAuthAuthenticatable合约由带的App\User模型实现。为什么使用契约我们从上述契约的源代码文件可以看出,Laravel提供的契约是为核心模块定义的一组接口。Laravel为每个合约提供了相应的实现类。下表列出了Laravel为上述三种合约提供的实现类。Laravel核心提供的实现类是IlluminateContractsAuthGuardIlluminateAuthSessionGuardIlluminateContractsAuthUserProviderIlluminateAuthEloquentUserProviderIlluminateContractsAuthAuthenticatableIlluminateFoundationAuthAuthenticatable(UserModel的父类),所以在自己开发的项目中,如果Laravel提供的用户实现类不能满足Laravel提供的用户认证系统定义的要求。之前做的项目是用户认证依赖公司员工管理系统的API,所以自己写了guard和userprovidercontract的实现类,让Laravel通过自定义的Guard和UserProvider完成用户认证。自定义用户认证的方法在用户认证介绍章节已经介绍过,读者可以阅读那篇文章。因此,Laravel为所有核心功能定义契约接口的目的是让开发者可以根据自己项目的需要定义自己的实现类,而对于这些接口的消费者(如:Controller,或者内核提供的AuthManager))他们不需要我们关心接口提供的方法是如何实现的。我们只关心接口方法可以提供什么功能,然后使用这些功能。我们可以根据需要在需要的时候为接口更换实现类,消费者不需要做任何事情。改变。合约的定义和使用我们上面提到的是Laravel核心提供的合约。在开发大型项目时,我们也可以在项目中定义契约和实现类。你可能会认为内置的Controller和Model两层只是让你写代码就够了,凭空添加更多的契约和实现类会让开发变得繁琐。让我们从一个简单的例子开始,并考虑以下代码有什么问题:>id)->get();返回View::make('order.index',compact('orders'));}}这段代码非常简单,但是如果我们要测试这段代码,我们必须将与实际的数据库链接起来。也就是说,ORM与该控制器紧密耦合。如果不使用EloquentORM并连接到实际数据库,我们无法运行或测试此代码。这段代码也违反了“关注点分离”的软件设计原则。简单地说:这个控制器知道的太多了。控制器不需要知道数据来自哪里,只需要知道如何访问它。控制器也不需要知道数据来自MySQL或者从哪里来,它只需要知道数据当前是可用的。关注点分离每个类都应该有单一的职责,并且该职责应该完全由类封装。每个类应该只有一个职责,职责中的所有内容都应该由这个类封装。接下来我们定义一个接口,然后实现该接口,$user->id)->get();}}将接口的实现绑定到Laravel的服务容器App::singleton('OrderRepositoryInterface','OrderRepository');然后我们将接口的实现注入到我们的控制器类UserControllerextendsController{publicfunction__construct(OrderRepositoryInterface$orderRepository){$this->orders=$orderRepository;}publicfunctiongetUserOrders(){$orders=$this->orders->userOrders();returnView::make('order.index',compact('orders'));现在我们的控制器完全独立于数据平面。这里我们的数据可能来自MySQL、MongoDB或者Redis。我们的控制器不知道也不需要知道区别。这样我们就可以独立于数据层测试web层,将来切换存储实现也很容易。界面和团队开发当你的团队在开发一个大型应用程序时,不同的部分有不同的开发速度。例如,一个开发人员正在开发数据层,而另一个开发人员正在开发控制器层。写controller的开发者想测试他的controller,但是数据层开发速度慢,无法同步测试。那么如果两个开发者可以先在接口的形式上达成一致,那么后台开发的各种类都会遵循这个约定。一旦约定成立,即使约定尚未实现,开发人员也可以编写该接口的“假”实现classDummyOrderRepositoryimplementsOrderRepositoryInterface{publicfunctionuserOrders(User$user){returncollect(['Order1','订单2','订单3']);一旦编写了虚拟实现,它就可以绑定到IoC容器App::singleton('OrderRepositoryInterface','DummyOrderRepository');然后应用程序的视图可以使用dummy数据填充。接下来,等后台开发人员写完真正的实现代码,比如RedisOrderRepository。然后使用IoC容器来切换接口实现,应用可以轻松切换到真正的实现,整个应用都会使用从Redis读取的数据。接口与测试接口约定建立好后,对我们MockpublicfunctiontestIndexActionBindsUsersFromRepository(){//Arrange...$repository=Mockery::mock('OrderRepositoryInterface');更有利$repository->shouldReceive('userOrders')->once()->andReturn(['order1','order2]);App::instance('OrderRepositoryInterface',$repository);//行动...$response=$this->action('GET','OrderController@getUserOrders');//断言...$this->assertResponseOk();$this->assertViewHas('order',['order1','order2']);}总结程序中的界面设计阶段非常有用。在设计阶段和团队讨论需要开发哪些接口来完成功能,然后设计每个接口具体要实现的方法,方法的入参和返回值,大家可以自己开发根据接口协议。如果遇到没有实现的接口,可以先定义接口的假实现,等开发出真正的实现再切换。这样既可以降低软件程序结构中上下层的耦合度,又可以保证各部分的开发进度。不要过分依赖其他部分的完成。本文已收录在Laravel源码学习系列文章中,欢迎访问阅读。
