laravel5中使用Repository Pattern(仓库模式)

如果你真的理解了仓库模式, 那么你用什么框架或者编程语言都无所谓了, 当你真正理解了仓库模式的设计原则之后, 你就能用任何语言或者框架去实现它, 首先, 我们先看一下仓库模式的定义:

仓库就像是业务内部的数据对象集合, 负责协调业务和数据映射层之间的关系, 客户端对象只需要构造一个清晰的查询请求, 然后提交给仓库就行了. 仓库里的对象可以通过客户端的请求进行增删改查, 客户端可以在某个场景下, 通过一个简单的对象集合或者仓库中映射的代码实现合适的操作


这样做有几个好处:
  • 把数据处理逻辑分离使得代码更容易维护
  • 数据处理逻辑和业务逻辑分离,可以对这两个代码分别进行测试
  • 减少代码重复
  • 降低代码出错的几率
  • 让controller代码的可读性大大提高

仓库模式把数据访问逻辑和业务逻辑中实体访问分开了, 数据访问逻辑和业务逻辑只能通过接口来进行数据操作.

laravel5中使用Repository Pattern(仓库模式)

简单来说, 仓库模式就是一种存放数据访问逻辑的容器, 它向业务层屏蔽了数据访问逻辑的细节, 也可以这样理解, 在不清楚数据层设计结构的情况下, 我们也能按照业务逻辑来访问数据层.

关于接口

仓库模式就是接口, 接口就是一个协议, 协议说明某一个具体的类必须被实现. 我们仔细想一下, 现在我们有两个数据对象Actor和Film, 通常我们对这些对象都有哪些操作呢?
  • 获取所有记录
  • 获取分页记录
  • 添加一条记录
  • 根据主键获取记录
  • 根据其他属性获取记录
  • 更新记录
  • 删除记录
如果我们对这两个数据对象都执行这些操作的话, 我们需要写好多重复的代码, 当然, 如果项目比较小的话, 是没有问题的, 但是当项目变得很大的时候, 就比较麻烦了.

现在我们定义这些通用操作, 并且创建一个接口:

  1. interface RepositoryInterface { 
  2.     public function all($columns = array('*')); 
  3.     public function paginate($perPage = 15, $columns = array('*')); 
  4.     public function create(array $data); 
  5.     public function update(array $data$id); 
  6.     public function delete($id); 
  7.     public function find($id$columns = array('*')); 
  8.     public function findBy($field$value$columns = array('*')); 
目录结构
在我们准备设计实现这个接口的仓库之前, 我们先考虑一下如何组织我们的代码, 通常, 我习惯把新功能做成一个component组件, 这样我以后就能重复使用这些代码, 例如这样的文件夹结构:


当然, 你随便, 怎么着都行. 下面我按照我这个组织形式继续讲.

src 文件夹包含了 Contracts, Eloquent, Exceptions 这几个文件夹, 一看文件夹名就知道这几个文件夹放啥了. Contracts 放协议(接口文件), Eloquent 放实现了接口的仓库类, Exceptions 存放异常类

顺便看一下 composer.json 文件的内容吧,

  1.   "name""bosnadev/repositories"
  2.   "description""Laravel Repositories"
  3.   "keywords": [ 
  4.     "laravel"
  5.     "repository"
  6.     "repositories"
  7.     "eloquent"
  8.     "database" 
  9.   ], 
  10.   "licence""MIT"
  11.   "authors": [ 
  12.     { 
  13.       "name""Mirza Pasic"
  14.       "email""mirza.pasic@edu.fit.ba" 
  15.     } 
  16.   ], 
  17.   "require": { 
  18.     "php"">=5.4.0"
  19.     "illuminate/support""5.*"
  20.     "illuminate/database""5.*" 
  21.   }, 
  22.   "autoload": { 
  23.     "psr-4": { 
  24.       "Bosnadev\\Repositories\\": "src/" 
  25.     } 
  26.   }, 
  27.   "autoload-dev": { 
  28.     "psr-4": { 
  29.       "Bosnadev\\Tests\\Repositories\\": "tests/" 
  30.     } 
  31.   }, 
  32.   "extra": { 
  33.     "branch-alias": { 
  34.       "dev-master""0.x-dev" 
  35.     } 
  36.   }, 
  37.   "minimum-stability""dev"
  38.   "prefer-stable": true 

通过 composer.json 我们映射了 src的类到 命名空间Bosnadev\Repository, 所以接口文件的标准格式为:

  1. <?php namespace Bosnadev\Repositories\Contracts; 
  2. interface RepositoryInterface { 
  3. ... 

现在我们开始实现接口

实现接口

使用仓库可以让我们查询数据, 建立业务实体和数据源之间的持续映射关系

laravel5中使用Repository Pattern(仓库模式)

当然, 每一个子仓库都应该继承这个抽象的仓库(实现了接口).

现在从接口第一个方法all()开始. 这个方法负责取出实体中的所有记录, 参数$columns是一个数组, 如果发送这个参数的话, 我们会取出$columns中指定的字段, 不传的话默认取出所有字段.
 

  1. public function all($columns = array('*')) { 
  2.     return $this->model->get($columns); 

然后我们设置$this->model属性为我们想要操作的数据对象, 来个抽象仓库类的完整案例:

 

  1. <?php namespace Bosnadev\Repositories\Eloquent; 
  2. use Bosnadev\Repositories\Contracts\RepositoryInterface; 
  3. use Bosnadev\Repositories\Exceptions\RepositoryException; 
  4. use Illuminate\Database\Eloquent\Model; 
  5. use Illuminate\Container\Container as App; 
  6. /** 
  7.  * Class Repository 
  8.  * @package Bosnadev\Repositories\Eloquent 
  9.  */ 
  10. abstract class Repository implements RepositoryInterface { 
  11.     /** 
  12.      * @var App 
  13.      */ 
  14.     private $app
  15.     /** 
  16.      * @var 
  17.      */ 
  18.     protected $model
  19.     /** 
  20.      * @param App $app 
  21.      * @throws \Bosnadev\Repositories\Exceptions\RepositoryException 
  22.      */ 
  23.     public function __construct(App $app) { 
  24.         $this->app = $app
  25.         $this->makeModel(); 
  26.     } 
  27.     /** 
  28.      * Specify Model class name 
  29.      *  
  30.      * @return mixed 
  31.      */ 
  32.     abstract function model(); 
  33.     /** 
  34.      * @return Model 
  35.      * @throws RepositoryException 
  36.      */ 
  37.     public function makeModel() { 
  38.         $model = $this->app->make($this->model()); 
  39.         if (!$model instanceof Model) 
  40.             throw new RepositoryException("Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model"); 
  41.         return $this->model = $model
  42.     } 

我们定一个这个抽象类之后, 现在我们实现一个具体的仓库类, 只需要实现 model()方法就行了

  1. <?php namespace App\Repositories; 
  2. use Bosnadev\Repositories\Contracts\RepositoryInterface; 
  3. use Bosnadev\Repositories\Eloquent\Repository; 
  4. class ActorRepository extends Repository { 
  5.     /** 
  6.      * Specify Model class name 
  7.      * 
  8.      * @return mixed 
  9.      */ 
  10.     function model() 
  11.     { 
  12.         return 'Bosnadev\Models\Actor'
  13.     } 

现在实现接口中的其他方法:

 

  1. <?php namespace Bosnadev\Repositories\Eloquent; 
  2. use Bosnadev\Repositories\Contracts\RepositoryInterface; 
  3. use Bosnadev\Repositories\Exceptions\RepositoryException; 
  4. use Illuminate\Database\Eloquent\Model; 
  5. use Illuminate\Container\Container as App; 
  6. /** 
  7.  * Class Repository 
  8.  * @package Bosnadev\Repositories\Eloquent 
  9.  */ 
  10. abstract class Repository implements RepositoryInterface { 
  11.     /** 
  12.      * @var App 
  13.      */ 
  14.     private $app
  15.     /** 
  16.      * @var 
  17.      */ 
  18.     protected $model
  19.     /** 
  20.      * @param App $app 
  21.      * @throws \Bosnadev\Repositories\Exceptions\RepositoryException 
  22.      */ 
  23.     public function __construct(App $app) { 
  24.         $this->app = $app
  25.         $this->makeModel(); 
  26.     } 
  27.     /** 
  28.      * Specify Model class name 
  29.      * 
  30.      * @return mixed 
  31.      */ 
  32.     abstract function model(); 
  33.     /** 
  34.      * @param array $columns 
  35.      * @return mixed 
  36.      */ 
  37.     public function all($columns = array('*')) { 
  38.         return $this->model->get($columns); 
  39.     } 
  40.     /** 
  41.      * @param int $perPage 
  42.      * @param array $columns 
  43.      * @return mixed 
  44.      */ 
  45.     public function paginate($perPage = 15, $columns = array('*')) { 
  46.         return $this->model->paginate($perPage$columns); 
  47.     } 
  48.     /** 
  49.      * @param array $data 
  50.      * @return mixed 
  51.      */ 
  52.     public function create(array $data) { 
  53.         return $this->model->create($data); 
  54.     } 
  55.     /** 
  56.      * @param array $data 
  57.      * @param $id 
  58.      * @param string $attribute 
  59.      * @return mixed 
  60.      */ 
  61.     public function update(array $data$id$attribute="id") { 
  62.         return $this->model->where($attribute'='$id)->update($data); 
  63.     } 
  64.     /** 
  65.      * @param $id 
  66.      * @return mixed 
  67.      */ 
  68.     public function delete($id) { 
  69.         return $this->model->destroy($id); 
  70.     } 
  71.     /** 
  72.      * @param $id 
  73.      * @param array $columns 
  74.      * @return mixed 
  75.      */ 
  76.     public function find($id$columns = array('*')) { 
  77.         return $this->model->find($id$columns); 
  78.     } 
  79.     /** 
  80.      * @param $attribute 
  81.      * @param $value 
  82.      * @param array $columns 
  83.      * @return mixed 
  84.      */ 
  85.     public function findBy($attribute$value$columns = array('*')) { 
  86.         return $this->model->where($attribute'='$value)->first($columns); 
  87.     } 
  88.     /** 
  89.      * @return \Illuminate\Database\Eloquent\Builder 
  90.      * @throws RepositoryException 
  91.      */ 
  92.     public function makeModel() { 
  93.         $model = $this->app->make($this->model()); 
  94.         if (!$model instanceof Model) 
  95.             throw new RepositoryException("Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model"); 
  96.         return $this->model = $model->newQuery(); 
  97.     } 

现在我们实现控制器试试
 

  1. <?php namespace App\Http\Controllers; 
  2. use App\Repositories\ActorRepository as Actor; 
  3. class ActorsController extends Controller { 
  4.     /** 
  5.      * @var Actor 
  6.      */ 
  7.     private $actor
  8.     public function __construct(Actor $actor) { 
  9.         $this->actor = $actor
  10.     } 
  11.     public function index() { 
  12.         return \Response::json($this->actor->all()); 
  13.     } 


标准查询方式

刚刚只是一些简单的操作, 在大型项目中, 我们有可能会定义一些其他的复杂操作.

为了实现这个, 我们要先顶一个抽象类, 用来扩展 repository 的查询能力.

  1. <?php namespace Bosnadev\Repositories\Criteria; 
  2. use Bosnadev\Repositories\Contracts\RepositoryInterface as Repository; 
  3. use Bosnadev\Repositories\Contracts\RepositoryInterface; 
  4. abstract class Criteria { 
  5.     /** 
  6.      * @param $model 
  7.      * @param RepositoryInterface $repository 
  8.      * @return mixed 
  9.      */ 
  10.     public abstract function apply($model, Repository $repository); 

这个方法是用来处理查询请求的, 现在我们要扩展 repository, 所以我们再定义一个接口文件:

 

  1. <?php namespace Bosnadev\Repositories\Contracts; 
  2. use Bosnadev\Repositories\Criteria\Criteria; 
  3. /** 
  4.  * Interface CriteriaInterface 
  5.  * @package Bosnadev\Repositories\Contracts 
  6.  */ 
  7. interface CriteriaInterface { 
  8.     /** 
  9.      * @param bool $status 
  10.      * @return $this 
  11.      */ 
  12.     public function skipCriteria($status = true); 
  13.     /** 
  14.      * @return mixed 
  15.      */ 
  16.     public function getCriteria(); 
  17.     /** 
  18.      * @param Criteria $criteria 
  19.      * @return $this 
  20.      */ 
  21.     public function getByCriteria(Criteria $criteria); 
  22.     /** 
  23.      * @param Criteria $criteria 
  24.      * @return $this 
  25.      */ 
  26.     public function pushCriteria(Criteria $criteria); 
  27.     /** 
  28.      * @return $this 
  29.      */ 
  30.     public function  applyCriteria(); 

现在让 repository 类实现这个接口:

  1. <?php namespace Bosnadev\Repositories\Eloquent; 
  2. use Bosnadev\Repositories\Contracts\CriteriaInterface; 
  3. use Bosnadev\Repositories\Criteria\Criteria; 
  4. use Bosnadev\Repositories\Contracts\RepositoryInterface; 
  5. use Bosnadev\Repositories\Exceptions\RepositoryException; 
  6. use Illuminate\Database\Eloquent\Model; 
  7. use Illuminate\Support\Collection; 
  8. use Illuminate\Container\Container as App; 
  9. /** 
  10.  * Class Repository 
  11.  * @package Bosnadev\Repositories\Eloquent 
  12.  */ 
  13. abstract class Repository implements RepositoryInterface, CriteriaInterface { 
  14.     /** 
  15.      * @var App 
  16.      */ 
  17.     private $app
  18.     /** 
  19.      * @var 
  20.      */ 
  21.     protected $model
  22.     /** 
  23.      * @var Collection 
  24.      */ 
  25.     protected $criteria
  26.     /** 
  27.      * @var bool 
  28.      */ 
  29.     protected $skipCriteria = false; 
  30.     /** 
  31.      * @param App $app 
  32.      * @param Collection $collection 
  33.      * @throws \Bosnadev\Repositories\Exceptions\RepositoryException 
  34.      */ 
  35.     public function __construct(App $app, Collection $collection) { 
  36.         $this->app = $app
  37.         $this->criteria = $collection
  38.         $this->resetScope(); 
  39.         $this->makeModel(); 
  40.     } 
  41.     /** 
  42.      * Specify Model class name 
  43.      * 
  44.      * @return mixed 
  45.      */ 
  46.     public abstract function model(); 
  47.     /** 
  48.      * @param array $columns 
  49.      * @return mixed 
  50.      */ 
  51.     public function all($columns = array('*')) { 
  52.         $this->applyCriteria(); 
  53.         return $this->model->get($columns); 
  54.     } 
  55.     /** 
  56.      * @param int $perPage 
  57.      * @param array $columns 
  58.      * @return mixed 
  59.      */ 
  60.     public function paginate($perPage = 1, $columns = array('*')) { 
  61.         $this->applyCriteria(); 
  62.         return $this->model->paginate($perPage$columns); 
  63.     } 
  64.     /** 
  65.      * @param array $data 
  66.      * @return mixed 
  67.      */ 
  68.     public function create(array $data) { 
  69.         return $this->model->create($data); 
  70.     } 
  71.     /** 
  72.      * @param array $data 
  73.      * @param $id 
  74.      * @param string $attribute 
  75.      * @return mixed 
  76.      */ 
  77.     public function update(array $data$id$attribute="id") { 
  78.         return $this->model->where($attribute'='$id)->update($data); 
  79.     } 
  80.     /** 
  81.      * @param $id 
  82.      * @return mixed 
  83.      */ 
  84.     public function delete($id) { 
  85.         return $this->model->destroy($id); 
  86.     } 
  87.     /** 
  88.      * @param $id 
  89.      * @param array $columns 
  90.      * @return mixed 
  91.      */ 
  92.     public function find($id$columns = array('*')) { 
  93.         $this->applyCriteria(); 
  94.         return $this->model->find($id$columns); 
  95.     } 
  96.     /** 
  97.      * @param $attribute 
  98.      * @param $value 
  99.      * @param array $columns 
  100.      * @return mixed 
  101.      */ 
  102.     public function findBy($attribute$value$columns = array('*')) { 
  103.         $this->applyCriteria(); 
  104.         return $this->model->where($attribute'='$value)->first($columns); 
  105.     } 
  106.     /** 
  107.      * @return \Illuminate\Database\Eloquent\Builder 
  108.      * @throws RepositoryException 
  109.      */ 
  110.     public function makeModel() { 
  111.         $model = $this->app->make($this->model()); 
  112.         if (!$model instanceof Model) 
  113.             throw new RepositoryException("Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model"); 
  114.         return $this->model = $model->newQuery(); 
  115.     } 
  116.     /** 
  117.      * @return $this 
  118.      */ 
  119.     public function resetScope() { 
  120.         $this->skipCriteria(false); 
  121.         return $this
  122.     } 
  123.     /** 
  124.      * @param bool $status 
  125.      * @return $this 
  126.      */ 
  127.     public function skipCriteria($status = true){ 
  128.         $this->skipCriteria = $status
  129.         return $this
  130.     } 
  131.     /** 
  132.      * @return mixed 
  133.      */ 
  134.     public function getCriteria() { 
  135.         return $this->criteria; 
  136.     } 
  137.     /** 
  138.      * @param Criteria $criteria 
  139.      * @return $this 
  140.      */ 
  141.     public function getByCriteria(Criteria $criteria) { 
  142.         $this->model = $criteria->apply($this->model, $this); 
  143.         return $this
  144.     } 
  145.     /** 
  146.      * @param Criteria $criteria 
  147.      * @return $this 
  148.      */ 
  149.     public function pushCriteria(Criteria $criteria) { 
  150.         $this->criteria->push($criteria); 
  151.         return $this
  152.     } 
  153.     /** 
  154.      * @return $this 
  155.      */ 
  156.     public function  applyCriteria() { 
  157.         if($this->skipCriteria === true) 
  158.             return $this
  159.         foreach($this->getCriteria() as $criteria) { 
  160.             if($criteria instanceof Criteria) 
  161.                 $this->model = $criteria->apply($this->model, $this); 
  162.         } 
  163.         return $this
  164.     } 

添加一些查询规则

通过添加的标准查询, 我们能更容易的组织我们的代码, repository 也不至于会有一堆臃肿的代码

laravel5中使用Repository Pattern(仓库模式)

添加的查询规则文件
 

  1. <?php namespace App\Repositories\Criteria\Films; 
  2. use Bosnadev\Repositories\Contracts\CriteriaInterface; 
  3. use Bosnadev\Repositories\Contracts\RepositoryInterface as Repository; 
  4. use Bosnadev\Repositories\Contracts\RepositoryInterface; 
  5. class LengthOverTwoHours implements CriteriaInterface { 
  6.     /** 
  7.      * @param $model 
  8.      * @param RepositoryInterface $repository 
  9.      * @return mixed 
  10.      */ 
  11.     public function apply($model, Repository $repository
  12.     { 
  13.         $query = $model->where('length''>', 120); 
  14.         return $query
  15.     } 

在控制器中使用这些标准查询
有两种方式使用, 第一种:

  1. <?php namespace App\Http\Controllers; 
  2. use App\Repositories\Criteria\Films\LengthOverTwoHours; 
  3. use App\Repositories\FilmRepository as Film; 
  4. class FilmsController extends Controller { 
  5.     /** 
  6.      * @var Film 
  7.      */ 
  8.     private $film
  9.     public function __construct(Film $film) { 
  10.         $this->film = $film
  11.     } 
  12.     public function index() { 
  13.         $this->film->pushCriteria(new LengthOverTwoHours()); 
  14.         return \Response::json($this->film->all()); 
  15.     } 


使用这种方法你能添加任意数量的标准查询规则, 但是如果你只想添加一个, 那么你可以使用这个方法getByCriteria():

  1. <?php namespace App\Http\Controllers; 
  2. use App\Repositories\Criteria\Films\LengthOverTwoHours; 
  3. use App\Repositories\FilmRepository as Film; 
  4. class FilmsController extends Controller { 
  5.     /** 
  6.      * @var Film 
  7.      */ 
  8.     private $film
  9.     public function __construct(Film $film) { 
  10.         $this->film = $film
  11.     } 
  12.     public function index() { 
  13.         $criteria = new LengthOverTwoHours(); 
  14.         return \Response::json($this->film->getByCriteria($criteria)->all()); 
  15.     } 

安装这个包

在你的composer.json 文件中添加"bosnadev/repositories": "0.*", 然后运行composer update.

总结
在应用中使用 repository 设计模式有很多好处, 最基本的就是能减少你的代码量, 是你的代码更容易维护, 测试和扩展.

从程序的设计架构角度来说, 你的控制器不需要知道你在哪儿存储的数据, 这些数据怎么来的, 这样是非常漂亮的结构.

有一个 laravel 实现的包推荐一下, 可能大多数人都知道吧

https://github.com/andersao/l5-repository

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值