使用存储库模式(和查询范围)与关系
使用存储库模式(和查询范围)与关系
在Laravel 4中,查询作用域适用于所有查询(包括由关系查询生成的查询)。这意味着对于以下(示例)模型:
Customer.php:
hasMany('Order'); } }
Order.php:
where('delivered', '=', true); } public function customer() { return $this->belongsTo('Customer'); } }
下面的两种方法都可以工作:
var_dump(Order::delivered()->get()); // 所有已交付的订单 var_dump(Customer::find(1)->orders()->delivered()->get()); // 只有客户#1的已交付订单
这对控制器非常有用,因为查找已交付订单的查询逻辑不需要重复。
然而,最近我被说服了,认为存储库模式不仅适用于关注点分离,而且适用于ORM/DB切换或添加缓存等中间件。存储库非常自然,因为现在与其让作用域膨胀我的模型,相关的查询实际上是存储库的一部分(这更有意义,因为这自然是集合的方法而不是项的方法)。
例如,
order = $order; } public function find($id) { /* ... */ } /* 等... */ public function allDelievered() { return $this->order->where('delivered', '=', true)->get(); } }
然而,现在我重复了已交付的作用域,为了避免违反DRY原则,我从模型中删除了它(根据上述理由,这似乎是合乎逻辑的)。但是现在,我不能再在关系上使用作用域(例如$customer->orders()->delivered()
)。我唯一看到的解决方法是在Relation
基类中以某种方式使用预先制作的查询来实例化存储库(类似于传递给模型中的作用域的方式)。但这涉及更改(和覆盖)大量代码和默认行为,似乎使事情耦合度更高。
鉴于这个困境,这是对存储库的误用吗?如果不是,我的解决方案是恢复我想要的功能的唯一方法吗?或者在模型中使用作用域不够紧密耦合,以证明这个额外的代码是合理的?如果作用域不是紧密耦合的,那么是否有一种方法可以同时使用存储库模式和作用域,同时保持DRY原则?
注意:我知道有关类似主题的一些类似问题,但它们都没有解决这里提出的与关系生成的查询有关的问题,这些查询不依赖于存储库。
使用存储库模式(和查询范围)处理关联时的问题
问题的出现原因:
- 存储库模式无法处理关联模型的查询范围
- 传统的解决方案是在模型类中定义查询范围,但这违背了单一职责原则,不符合良好的设计原则
解决方法:
- 创建一个存储库接口(Repository)和相关的实现类,用于处理与关联模型的交互
- 在存储库中定义查询范围,并将其添加到每个由Eloquent模型创建的查询中
- 创建一个ModelScope类,用于将查询范围应用于查询构建器
- 使用ServiceProvider将存储库绑定到应用程序的容器中
- 在控制器中使用存储库进行数据操作
具体实现步骤如下:
1. 创建存储库接口和相关的实现类
- Repository接口定义了常用的增删改查方法
- OrderRepository接口继承自Repository接口
- EloquentOrderRepository类实现了OrderRepository接口,同时包含了查询范围的定义
2. 创建EloquentBaseRepository类
- EloquentBaseRepository是一个抽象类,实现了Repository接口
- 在EloquentBaseRepository的构造方法中,将查询范围添加到模型中
- 通过使用ReflectionObject类,遍历存储库类中的方法,找到以"scope"开头的方法,并将其添加到ModelScope中
- 调用模型的addGlobalScope方法,将ModelScope应用于模型
3. 创建ModelScope类
- ModelScope类实现了ScopeInterface接口,用于将查询范围应用于查询构建器
- 在apply方法中,将每个查询范围添加到查询构建器中
- 在remove方法中,将每个查询范围添加一个抛出异常的方法,以防止调用未定义的查询范围
- 使用addScope方法将查询范围添加到ModelScope中
4. 创建存储库服务提供者
- RepositoryServiceProvider类继承自ServiceProvider类
- 在register方法中,将存储库接口绑定到相应的实现类
5. 更新composer文件
- 在composer.json文件中,将存储库路径添加到自动加载的路径中
- 运行composer.phar dump-autoload命令更新自动加载
6. 创建模型类
- Customer和Order模型类继承自Eloquent类
- 在Order模型类中定义了与Customer模型类的关联关系
7. 在控制器中使用存储库进行数据操作
- 在OrderController的构造方法中,通过依赖注入方式获取OrderRepository实例
- 在test方法中,使用存储库进行数据操作,包括获取所有订单、获取已完成订单以及获取特定客户的已完成订单
问题的解决方法虽然能够实现所需功能,但存在以下问题:
- 代码量较大,功能相对简单
- 使用了hacky的方法,可能不符合最佳实践
- 需要手动实例化存储库,不够灵活
参考资源:
- [Creating flexible Controllers in Laravel 4 using Repositories](http://culttt.com/2013/07/08/creating-flexible-controllers-in-laravel-4-using-repositories/)
- [Eloquent tricks for better Repositories](http://culttt.com/2014/03/17/eloquent-tricks-better-repositories/)