本文将介绍Repository的实作,基于的github项目是:l5-repository,源码是做好的教科书,代码面前所有设计意图都无所遁形。
我们首先来明确下需要解决的问题是什么,为什么会出现l5-repository这个项目。
我想你肯定遇到过这个问题:刚开始我们的Model只有几百行,但是呢,随着项目功能的不断复杂,model需要实现的功能也越来越多,当我们有一天突然回过头去看的时候,发现Model已经上千行了,我们自己也想不起来里面都有哪些功能了。
那model为什么会越来越胖呢?我们来看下model中都可能会有哪些功能。
- php写的业务逻辑
- 依据显示需求,我们做的数据格式转换,字段的选择性返回
- 各种条件的查询
- 对于创建参数、查询参数的验证
- …..
根据分类,我们可以归纳为下面几类
- presenter,显示需求
- repository,存取需求
- validator,参数验证需求
- services,业务逻辑
于是我们就有了下面的架构图:
有了这个图以后,我们再来看l5-repository这个项目的实现,就会容易理解很多。
下面我会从3个方面来讲解l5-repository的实现:repository,presenter,validator。我们先来看repository的实现。
repository原理
开始时,我也免不了一般的套路,先来个repository的定义
A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.
其核心有两点
- repository类似于集合,负责对象的存取,隐藏了具体的data mapping的实现
- repository能根据client构建的查询条件,返回特定的数据
我们先来看第一点,对象存取,说到底就是CRUD操作,于是就有了repository的接口定义RepositoryInterface
,具体的接口有:
对于CRUD来说,其最难的是C操作,因为会有各种各样的查询方式,提供的查询接口有:
1 2 3 4 5 |
public function find($id, $columns = [‘*‘]); public function findByField($field, $value, $columns = [‘*‘]); public function findWhere(array $where, $columns = [‘*‘]); public function findWhereIn($field, array $values, $columns = [‘*‘]); public function findWhereNotIn($field, array $values, $columns = [‘*‘]); |
然后默认的实现是Prettus\Repository\Eloquent\BaseRepository
,基本上就是对Eloquent\Builder
的一个封装。
但是一些更复杂的查询怎么满足呢?我们难道需要每次有新的查询都去新增findCondition接口吗?显然我们不能这么做,这个时候Criteria
就隆重登场了,先看个接口:
1 2 3 4 5 6 7 8 9 10 11 12 |
interface CriteriaInterface { /** * Apply criteria in query repository * * @param $model * @param RepositoryInterface $repository * * @return mixed */ public function apply($model, RepositoryInterface $repository); } |
所有的Criteria都需要实现apply方法,看一个可能是实现:
1 2 3 4 5 6 7 |
class LengthOverTwoHours implements CriteriaInterface { public function apply($model, Repository $repository) { $query = $model->where(‘length‘, ‘>‘, 120); return $query; } } |
通过定义LengthOverTwoHours来对Model新增查询,这样子我们每次有新的查询条件,只要新建Criteria即可,满足了开放封闭原则。
接着我们来使用下l5-repository。首先通过命令php artisan make:entity Book
来生成文件,然后在[email protected]中新增
1 |
$this->app->register( RepositoryServiceProvider::class); |
接着产生一个Controller
1 |
php artisan make:controller -r BookController |
在里面我们可以使用注入进Repository
1 2 3 4 |
public function __construct( BookRepository $bookRepository ) { $this->bookRepository = $bookRepository; } |
然后一些具体的操作可以去看https://github.com/andersao/l5-repository,写的非常详细。
最后介绍下怎么产生criteria,通过下面的命令
1 |
php artisan make:criteria My |
然后添加下面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MyCriteria implements CriteriaInterface { /** * Apply criteria in query repository * * @param Builder $model * @param RepositoryInterface $repository * * @return mixed */ public function apply($model, RepositoryInterface $repository) { $model = $model->where(‘user_id‘,‘=‘, \Auth::user()->id ); return $model; } } |
就能够使用了,然后在controller中,我们通过下面的方式查询
1 2 3 4 5 6 |
public function index() { $this->repository->pushCriteria(new MyCriteria()); $books = $this->repository->all(); ... } |
Presenters优化
接着我们讲Presenters部分。
我们再强调下Presenter解决的问题:把日期、金额、名称之类的呈现(presentation)逻辑抽离出来。在l5-repository这个功能其实不是很满意,我们希望的是1 Presenter中介绍的那种样子,原先样子是:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Article extends Eloquent { public function getDate(){/*...*/} public function getTaiwaneseDateTime(){/*...*/} public function getWesternDateTime(){/*...*/} public function getTaiwaneseDate(){/*...*/} public function getWesternDate(){/*...*/} } |
抽离出来后是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Article extends Eloquent { public function present() { return new ArticlePresenter($this); } } class ArticlePresenter extends Presenter { public function getTaiwaneseDateTime(){/*...*/} public function getWesternDateTime(){/*...*/} public function getTaiwaneseDate(){/*...*/} public function getWesternDate(){/*...*/} } |
下面是一些实现方案
https://github.com/laracasts/Presenter
https://github.com/robclancy/presenter
https://github.com/laravel-auto-presenter/laravel-auto-presenter
参数验证
最后我们介绍validator
validator的逻辑从model中抽离出来,单独放入一个类中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
use \Prettus\Validator\Contracts\ValidatorInterface; use \Prettus\Validator\LaravelValidator; class PostValidator extends LaravelValidator { protected $rules = [ ValidatorInterface::RULE_CREATE => [ ‘title‘ => ‘required‘, ‘text‘ => ‘min:3‘, ‘author‘=> ‘required‘ ], ValidatorInterface::RULE_UPDATE => [ ‘title‘ => ‘required‘ ] ]; } |
能够制定create和update操作的时候不同的验证规则。
总结
以上就是repository的全部,文章开头由实际项目中model越来越胖引出如何给model瘦身,接着对model中的功能进行了划分,给出了合理的项目组织方式,接着通过从repository,presenter,validator分析了具体的一些优化方式。
最后本文只是简单的对l5-repository进行了介绍,更详细的功能,更多的实现细节,你都可以clone项目下来,自己好好去看,相信会学到很多。
参考
Using Repository Pattern in Laravel 5