Laravel框架下的Contracts契约详解


Posted in PHP onMarch 17, 2020

Contracts

Laravel 的契约是一组定义框架提供的核心服务的接口, 例如我们在介绍用户认证的章节中到的用户看守器契约IllumninateContractsAuthGuard 和用户提供器契约IlluminateContractsAuthUserProvider以及框架自带的App\User模型所实现的IlluminateContractsAuthAuthenticatable契约。

为什么使用契约

通过上面几个契约的源码文件我们可以看到,Laravel提供的契约是为核心模块定义的一组interface。Laravel为每个契约都提供了相应的实现类,下表列出了Laravel为上面提到的三个契约提供的实现类。

Laravel框架下的Contracts契约详解

所以在自己开发的项目中,如果Laravel提供的用户认证系统无法满足需求,你可以根据需求定义看守器和用户提供器的实现类,比如我之前做的项目就是用户认证依赖于公司的员工管理系统的API,所以我就自己写了看守器和用户提供器契约的实现类,让Laravel通过自定义的Guard和UserProvider来完成用户认证。自定义用户认证的方法在介绍用户认证的章节中我们介绍过,读者可以去翻阅那块的文章。

所以Laravel为所有的核心功能都定义契约接口的目的就是为了让开发者能够根据自己项目的需要自己定义实现类,而对于这些接口的消费者(比如:Controller、或者内核提供的 AuthManager这些)他们不需要关心接口提供的方法具体是怎么实现的, 只关心接口的方法能提供什么功能然后去使用这些功能就可以了,我们可以根据需求在必要的时候为接口更换实现类,而消费端不用进行任何改动。

定义和使用契约

上面我们提到的都是Laravel内核提供的契约, 在开发大型项目的时候我们也可以自己在项目中定义契约和实现类,你有可能会觉得自带的Controller、Model两层就已经足够你编写代码了,凭空多出来契约和实现类会让开发变得繁琐。我们先从一个简单的例子出发,考虑下面的代码有什么问题:

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.

每个类都应该只有单一的职责,并且职责里所有的东西都应该由这个类封装

接下来我们定义一个接口,然后实现该接口

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框架下的Contracts契约的文章就介绍到这了,更多相关laravel contracts契约 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
Blitz templates 最快的PHP模板引擎
Apr 06 PHP
php操作JSON格式数据的实现代码
Dec 24 PHP
PHP生成唯一的促销/优惠/折扣码(附源码)
Dec 28 PHP
php缓冲 output_buffering和ob_start使用介绍
Jan 30 PHP
php调整服务器时间的方法
Apr 03 PHP
PHP实现对png图像进行缩放的方法(支持透明背景)
Jul 15 PHP
WordPress中创建用户角色的相关PHP函数使用详解
Dec 25 PHP
一波PHP中cURL库的常见用法代码示例
May 06 PHP
用PHP去掉文件头的Unicode签名(BOM)方法
Jun 22 PHP
PHP中使用CURL发送get/post请求上传图片批处理功能
Oct 15 PHP
PHP7导出Excel报ERR_EMPTY_RESPONSE解决方法
Apr 16 PHP
解决PhpStorm64不能启动的问题
Jun 20 PHP
使用Entrust扩展包在laravel 中实现RBAC的功能
Mar 16 #PHP
PHP代码加密的方法总结
Mar 13 #PHP
YII2框架中behavior行为的理解与使用方法示例
Mar 13 #PHP
YII2框架中actions的作用与使用方法示例
Mar 13 #PHP
PHP正则之正向预查与反向预查讲解与实例
Apr 06 #PHP
TP5框架安全机制实例分析
Apr 05 #PHP
TP5框架实现自定义分页样式的方法示例
Apr 05 #PHP
You might like
一个自定义位数的php多用户计数器代码
2007/03/11 PHP
PHP快速按行读取CSV大文件的封装类分享(也适用于其它超大文本文件)
2014/04/10 PHP
ThinkPHP采用GET方式获取中文参数查询无结果的解决方法
2014/06/26 PHP
PHP 实现代码复用的一个方法 traits新特性
2015/02/22 PHP
php实现表单多按钮提交action的处理方法
2015/10/24 PHP
yii2.0实现创建简单widgets示例
2016/07/18 PHP
文本加密解密
2006/06/23 Javascript
通过JS自动隐藏手机浏览器的地址栏实现原理与代码
2013/01/02 Javascript
jQuery 遍历-nextUntil()方法以及prevUntil()方法的使用介绍
2013/04/26 Javascript
一个封装js代码-----展开收起效果示例
2013/07/03 Javascript
比较新旧两个数组值得增加和删除的JS代码
2013/10/30 Javascript
html的DOM中document对象images集合用法实例
2015/01/21 Javascript
模仿password输入框的实现代码
2016/06/07 Javascript
深入浅出讲解ES6的解构
2016/08/03 Javascript
Jquery循环截取字符串的方法(多出的字符串处理成"...")
2016/11/28 Javascript
jQuery插件FusionCharts实现的2D饼状图效果【附demo源码下载】
2017/03/03 Javascript
React-Native 组件之 Modal的使用详解
2017/08/08 Javascript
使用node.js对音视频文件加密的实例代码
2017/08/30 Javascript
解决Vue.js由于延时显示了{{message}}引用界面的问题
2018/08/25 Javascript
vue踩坑记-在项目中安装依赖模块npm install报错
2019/04/02 Javascript
Javascript实现贪吃蛇小游戏(含详细注释)
2020/10/23 Javascript
浅析微信小程序自定义日历组件及flex布局最后一行对齐问题
2020/10/29 Javascript
详解Vue的mixin策略
2020/11/19 Vue.js
解决await在forEach中不起作用的问题
2021/02/25 Javascript
python_matplotlib改变横坐标和纵坐标上的刻度(ticks)方式
2020/05/16 Python
Python脚本如何在bilibili中查找弹幕发送者
2020/06/04 Python
keras读取h5文件load_weights、load代码操作
2020/06/12 Python
Python getattr()函数使用方法代码实例
2020/08/10 Python
python获得命令行输入的参数的两种方式
2020/11/02 Python
Python numpy大矩阵运算内存不足如何解决
2020/11/19 Python
详解html5 canvas常用api总结(二)--绘图API
2016/12/14 HTML / CSS
美国球鞋寄卖网站:Stadium Goods
2018/05/09 全球购物
2014年小学元旦活动方案
2014/02/12 职场文书
领导班子四风查摆对照检查材料思想汇报
2014/10/05 职场文书
2015年领导干部廉洁自律工作总结
2015/05/26 职场文书
Go使用协程交替打印字符
2021/04/29 Golang