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 相关文章推荐
php 遍历数据表数据并列表横向排列的代码
Sep 05 PHP
使用PHP 5.0创建图形的巧妙方法
Oct 12 PHP
php 中英文语言转换类代码
Aug 11 PHP
PHP合并两个数组的两种方式的异同
Sep 14 PHP
php类声明和php类使用方法示例分享
Mar 29 PHP
thinkphp实现数组分页示例
Apr 13 PHP
PHP文件缓存内容保存格式实例分析
Aug 20 PHP
PHP使用json_encode函数时不转义中文的解决方法
Nov 12 PHP
PHP构造函数与析构函数用法示例
Sep 28 PHP
PHP通过CURL实现定时任务的图片抓取功能示例
Oct 03 PHP
老生常谈PHP面向对象之标识映射
Jun 21 PHP
PHP MVC框架中类的自动加载机制实例分析
Sep 18 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
一个图形显示IP的PHP程序代码
2007/10/19 PHP
PHP分页显示制作详细讲解
2008/11/19 PHP
PHP开源开发框架ZendFramework使用中常见问题说明及解决方案
2014/06/12 PHP
PHP获取客户端真实IP地址的5种情况分析和实现代码
2014/07/08 PHP
php编程每天必学之表单验证
2016/03/01 PHP
php静态成员方法和静态的成员属性的使用方法
2017/10/26 PHP
laravel 查询数据库获取结果实现判断是否为空
2019/10/24 PHP
js中关于String对象的replace使用详解
2011/05/24 Javascript
javascript 进阶篇2 CSS XML学习
2012/03/14 Javascript
异步javascript的原理和实现技巧介绍
2012/11/08 Javascript
JavaScript利用构造函数和原型的方式模拟C#类的功能
2014/03/06 Javascript
使用javascript实现简单的选项卡切换
2015/01/09 Javascript
谈一谈jQuery核心架构设计
2016/03/28 Javascript
微信小程序开发实战教程之手势解锁
2016/11/18 Javascript
JS闭包与延迟求值用法示例
2016/12/22 Javascript
BootStrap Table后台分页时前台删除最后一页所有数据refresh刷新后无数据问题
2016/12/28 Javascript
如何使用JS在HTML中自定义字符串格式化
2017/07/20 Javascript
React Native使用Modal自定义分享界面的示例代码
2017/10/31 Javascript
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
2019/02/27 jQuery
Vue注册组件命名时不能用大写的原因浅析
2019/04/25 Javascript
解决Vue调用springboot接口403跨域问题
2019/09/02 Javascript
[52:29]DOTA2上海特级锦标赛主赛事日 - 2 胜者组第一轮#3Secret VS OG第三局
2016/03/03 DOTA
python多线程扫描端口示例
2014/01/16 Python
Python序列对象与String类型内置方法详解
2019/10/22 Python
基于pycharm 项目和项目文件命名规则的介绍
2021/01/15 Python
AC Lens:购买隐形眼镜
2017/02/26 全球购物
新加坡网上美容店:Hermo新加坡
2019/06/19 全球购物
网上快餐厅创业计划书
2014/02/01 职场文书
法定代表人授权委托书
2014/04/04 职场文书
小学生田径运动会广播稿
2014/09/11 职场文书
夫妻房产协议书的格式
2014/10/11 职场文书
民主评议党员总结
2014/10/20 职场文书
2014年员工工作总结范文
2014/11/18 职场文书
辞职离别感言
2015/08/04 职场文书
Nginx禁止ip访问或非法域名访问
2022/04/07 Servers
Python 图片添加美颜效果
2022/04/28 Python