PHP群:95885625 Hbuilder+MUI群:81989597 站长QQ:634381967
    您现在的位置: 首页 > 开发编程 > Laravel教程 > 正文

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

    作者:admin来源:王吕浏览:时间:2020-09-30 00:07:50我要评论
    导读:如果你真的理解了仓库模式, 那么你用什么框架或者编程语言都无所谓了, 当你真正理解了仓库模式的设计原则之后, 你就能用任何语言或者框...
    如果你真的理解了仓库模式, 那么你用什么框架或者编程语言都无所谓了, 当你真正理解了仓库模式的设计原则之后, 你就能用任何语言或者框架去实现它, 首先, 我们先看一下仓库模式的定义:

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


    这样做有几个好处:
    • 把数据处理逻辑分离使得代码更容易维护
    • 数据处理逻辑和业务逻辑分离,可以对这两个代码分别进行测试
    • 减少代码重复
    • 降低代码出错的几率
    • 让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
     

    转载请注明(B5教程网)原文链接:https://b5.mxunkeji.com/content-153-6229-1.html
    相关热词搜索: