若将数据库逻辑都写在model,会造成model的肥大而难以维护,基于SOLID原则,我们应该使用Repository模式辅助model,将相关的数据库逻辑封装在不同的repository,方便中大型项目的维护。

Version:Laravel 5.1.22


数据库逻辑

在CRUD中,CUD比较稳定,但R的部分则千变万化,大部分的数据库逻辑都在描述R的部分,若将数据库逻辑写在controller或model都不适当,会造成controller与model肥大,造成日后难以维护。

Model

使用repository之后,model仅当成Eloquent class即可,不要包含数据库逻辑,仅保留以下部分:

  • Property:如$table,$fillable…等。
  • Mutator:包括mutator与accessor。
  • Method:relation类的method,如使用hasMany()与belongsTo()。
  • 注释:因为Eloquent会根据数据库字段动态产生property与method,等。若使用Laravel IDE Helper,会直接在model加上@property与@method描述model的动态property与method。

User.php

app/User.php
namespace MyBlog;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
/**
* MyBlog\User
*
* @property integer $id
* @property string $name
* @property string $email
* @property string $password
* @property string $remember_token
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereId($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereName($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereEmail($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User wherePassword($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereRememberToken($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereUpdatedAt($value)
*/
class User extends Model implements AuthenticatableContract,AuthorizableContract,CanResetPasswordContract
{use Authenticatable, Authorizable, CanResetPassword;/*** The database table used by the model.** @var string*/protected $table = 'users';/*** The attributes that are mass assignable.** @var array*/protected $fillable = ['name', 'email', 'password'];/*** The attributes excluded from the model's JSON form.** @var array*/protected $hidden = ['password', 'remember_token'];
}

12行

/**
* MyBlog\User
*
* @property integer $id
* @property string $name
* @property string $email
* @property string $password
* @property string $remember_token
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereId($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereName($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereEmail($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User wherePassword($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereRememberToken($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\MyBlog\User whereUpdatedAt($value)
*/

IDE-Helper帮我们替model加上注释,让我们可以在PhpStorm的语法提示使用model的property与method

Repository

初学者常会在controller直接调用model写数据库逻辑:

public function index()
{$users = User::where('age', '>', 20)->orderBy('age')->get();return view('users.index', compact('users'));
}

数据库逻辑是要抓20岁以上的数据。
在中大型项目,会有几个问题:

  1. 将数据库逻辑写在controller,造成controller的肥大难以维护。
  2. 违反SOLID的单一职责原则:数据库逻辑不应该写在controller。
  3. controller直接相依于model,使得我们无法对controller做单元测试。
    比较好的方式是使用repository:
  4. 将model依赖注入到repository。
  5. 将数据库逻辑写在repository。
  6. 将repository依赖注入到service。
    UserRepository.php

    app/Repositories/UserRepository.php
    namespace MyBlog\Repositories;
    use Doctrine\Common\Collections\Collection;
    use MyBlog\User;
    class UserRepository
    {
    /** @var User 注入的User model */
    protected $user;
    /**
    * UserRepository constructor.
    * @param User $user
    */
    public function __construct(User $user)
    {$this->user = $user;
    }
    /**
    * 回传大于?年纪的数据
    * @param integer $age
    * @return Collection
    */
    public function getAgeLargerThan($age)
    {return $this->user->where('age', '>', $age)->orderBy('age')->get();
    }
    }
    

    第 8 行

    /** @var User 注入的User model */
    protected $user;
    /**
    * UserRepository constructor.
    * @param User $user
    */
    public function __construct(User $user)
    {
    $this->user = $user;
    }
    

    将相依的User model依赖注入到UserRepository。
    21 行

    /**
    * 回传大于?年纪的数据
    * @param integer $age
    * @return Collection
    */
    public function getAgeLargerThan($age)
    {
    return $this->user->where('age', '>', $age)->orderBy('age')->get();
    }
    

    将抓20岁以上的数据的数据库逻辑写在getAgeLargerThan()。
    不是使用User facade,而是使用注入的$this->user
    UserController.php
    app/Http/Controllers/UserController.php

    namespace App\Http\Controllers;
    use App\Http\Requests;
    use MyBlog\Repositories\UserRepository;
    class UserController extends Controller
    {
    /** @var  UserRepository 注入的UserRepository */
    protected $userRepository;/**
    * UserController constructor.
    *
    * @param UserRepository $userRepository
    */
    public function __construct(UserRepository $userRepository)
    {$this->userRepository = $userRepository;
    }/**
    * Display a listing of the resource.
    *
    * @return \Illuminate\Http\Response
    */
    public function index()
    {$users = $this->userRepository->getAgeLargerThan(20);return view('users.index', compact('users'));
    }
    }
    

    第8行

    /** @var  UserRepository 注入的UserRepository */
    protected $userRepository;
    /**
    * UserController constructor.
    *
    * @param UserRepository $userRepository
    */
    public function __construct(UserRepository $userRepository)
    {
    $this->userRepository = $userRepository;
    }
    

    将相依的UserRepository依赖注入到UserController。
    26行

    /**
    * Display a listing of the resource.
    *
    * @return \Illuminate\Http\Response
    */
    public function index()
    {
    $users = $this->userRepository->getAgeLargerThan(20);return view('users.index', compact('users'));
    }
    

    从原本直接相依的User model,改成依赖注入的UserRepository。
    改用这种写法,有几个优点:

  • 将数据库逻辑写在repository,解决controller肥大问题。
  • 符合SOLID的单一职责原则:数据库逻辑写在repository,没写在controller。
  • 符合SOLID的依赖反转原则:controller并非直接相依于repository,而是将repository依赖注入进controller。

实务上建议repository仅依赖注入于service,而不要直接注入在controller,本示例因为还没介绍到servie模式,为了简化起见,所以直接注入于controller。

是否该建立Repository Interface?
理论上使用依赖注入时,应该使用interface,不过interface目的在于抽象化方便抽换,让代码达到开放封闭的要求,但是实务上要抽换repository的机会不高,除非你有抽换数据库的需求,如从MySQL抽换到MongoDB,此时就该建立repository interface。
不过由于我们使用了依赖注入,将来要从class改成interface也很方便,只要在constructor的type hint改成interface即可,维护成本很低,所以在此大可使用repository class即可,不一定得用interface而造成over design,等真正需求来时再重构成interface即可。
是否该使用Query Scope?
Laravel 4.2就有query scope,到5.1都还留着,它让我们可以将商业逻辑写在model,解决了维护与重复使用的问题。
User.php
app/User.php

namespace MyBlog;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
/*** (註解:略)*/
class User extends Model implements AuthenticatableContract,AuthorizableContract,CanResetPasswordContract
{use Authenticatable, Authorizable, CanResetPassword;/*** The database table used by the model.** @var string*/protected $table = 'users';/*** The attributes that are mass assignable.** @var array*/protected $fillable = ['name', 'email', 'password'];/*** The attributes excluded from the model's JSON form.** @var array*/protected $hidden = ['password', 'remember_token'];/*** 回传大于?年纪的数据* @param Builder $query* @param integer $age* @return Builder*/public function scopeGetAgerLargerThan($query, $age){return $query->where('age', '>', $age)->orderBy('age');}
}

42行

/*** 回传大于?年纪的数据* @param Builder $query* @param integer $age* @return Builder*/
public function scopeGetAgerLargerThan($query, $age)
{return $query->where('age', '>', $age)->orderBy('age');
}

Query scope必须以scope为prefix,第1个参数为query builder,一定要加,是Laravel要用的。
第2个参数以后为自己要传入的参数。
由于回传也必须是一个query builder,因此不加上get()。
UserController.php

app/Http/Controllers/UserController.php
namespace App\Http\Controllers;
use App\Http\Requests;
use MyBlog\User;
class UserController extends Controller
{/*** Display a listing of the resource.** @return \Illuminate\Http\Response*/public function index(){$users = User::getAgerLargerThan(20)->get();return view('users.index', compact('users'));}
}

在controller呼叫query scope时,不要加上prefix,由于其本质是query builder,所以还要加上get()才能抓到Collection。
由于query scope是写在model,不是写在controller,所以基本上解决了controller肥大与违反SOLID的单一职责原则的问题,controller也可以重复使用query scope,已经比直接将数据库逻辑写在controller好很多了。

不过若在中大型项目,仍有以下问题:

  1. Model已经有原来的责任,若再加上query scope,造成model过于肥大难以维护。
  2. 若数据库逻辑很多,可以拆成多repository,可是却很难拆成多model。
  3. 单元测试困难,必须面临mock Eloquent的问题。

Conclusion

实务上可以一开始1个repository对应1个model,但不用太执着于1个repository一定要对应1个model,可将repository视为逻辑上的数据库逻辑类别即可,可以横跨多个model处理,也可以1个model拆成多个repository,端看需求而定。
Repository使得数据库逻辑从controller或model中解放,不仅更容易维护、更容易扩展、更容易重复使用,且更容易测试。
Sample Code
完整的示例可以在我的GitHub上找到。

转载自:http://www.sangeng.org/blog/detail_518.html

Laravel框架中使用 Repository 模式相关推荐

  1. Laravel框架中使用Service模式

    Laravel框架中使用 Presenter 模式 Laravel框架中使用 Repository 模式 Laravel的中大型项目构架和优雅的插件扩展l5-repository 若将商业逻辑都写在c ...

  2. laravel services.php,「Laravel框架中使用Service模式」- 海风纷飞Blog

    若将商业逻辑都写在controller,会造成controller肥大而难以维护,基于SOLID原则,我们应该使用Service模式辅助controller,将相关的商业逻辑封装在不同的service ...

  3. laravel 框架中使用数据库迁移添加注释

    laravel 框架中数据库迁移添加注释 在使用laravel框架过程中,估计很多人都有用过数据库迁移文件.可能大家都会在建表时为字段添加注释.我在此要说明的是为表添加注释 首先我们需要引入larav ...

  4. MVC架构中的Repository模式 个人理解

    关于MVC架构中的Repository模式 个人理解:Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提 ...

  5. php辅助框架,【PHP开发框架】Laravel框架中辅助函数:optional ()函数的介绍

    laravel框架中的辅助函数有很多,那么,在 Laravel 新版本中又有什么非常好用的辅助函数呢?接下来的这篇文章中,ki4网将给大家介绍一个非常有用的辅助方法:optional()函数,这个函数 ...

  6. 阿里物联网套件在laravel框架中的使用--第一弹

    最近一直在研究物联网套件,也算是有点心得.然后研究归研究,终归是要回归实践的.在网上大致百度下,发现专门写阿里物联网套件的文章很少,所以就大致总结下,大致说一下物联网提供的phpSDK在laravel ...

  7. Laravel框架中config配置文件的使用

    在进行程序开发时,为了后期维护的方便,我们习惯上将配置信息单独写在一个配置文件中.在laravel框架中为我们提供了config目录专门用来存放配置文件.如果我们需要在config目录中添加自定义配置 ...

  8. 关于laravel框架中and 和orWhere 的多条件嵌套

    最近的项目一直在使用laravel框架,在使用过程中突然发现自带的助手函数where()与orWhere()使用起来和自己预想中的不一样,特此记录学习防止以后忘记. 举个例子我们需要查找状态为1,名称 ...

  9. java repository模式_MVC架构中的Repository模式 个人理解

    个人理解:Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问.Reposit ...

  10. Laravel框架中Guard的底层实现分析

    1. 什么是Guard 在Laravel/Lumen框架中,用户的登录/注册的认证基本都已经封装好了,开箱即用.而登录/注册认证的核心就是: 用户的注册信息存入数据库(登记) 从数据库中读取数据和用户 ...

最新文章

  1. Bzoj2957: 楼房重建
  2. (0007) iOS 开发之Xcode8上传AppStore遇到的TencentOpenApi_IOS_Bundle.bundle
  3. 关于Arrays类中toArray方法的总结
  4. 数学--数论--中国剩余定理 拓展 HDU 1788
  5. 自定义sql_一个简单易用的开源BI软件,专为SQL用户设计的开源库
  6. 【学习】DataFrameSeries类【pandas】
  7. 图片的压缩(上传图片太大的话,上传不到服务器)
  8. dataframe 绘图——按照每列出一个图(df.plot)
  9. java实现身份证识别
  10. K3救砖,梅林刷回官方
  11. 减速电机计算公式中功率(P),扭力(NM),转速(RPM),减速比(RATIO)四大因素互相转化的重要性
  12. 微信王者登录太多服务器怎样删除,使用微信登录游戏或其他app时,可以使用不同的个人信息登录 选项已满 怎么删除?如图...
  13. Matlab文件IO操作函数,fgetl和textscan介绍以及使用时可能遇到的问题
  14. 主数据同步与分发实现
  15. 【矩阵论】矩阵微积分的一些公式
  16. Ubuntu 安装netstat网络工具
  17. org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body
  18. centos7利用docker 快速搭建苹果CMS站点
  19. RGB颜色值与十六进制颜色码
  20. python需要cpu还是显卡问题_买新电脑是cpu重要还是显卡重要?该怎么选择?

热门文章

  1. VTK实现多个体数据映射到一起进行渲染
  2. 全排列、排列组合(去重区别)
  3. c语言else需要条件,C语言else条件判断
  4. Pytorch Gradient Checkpoint使用示例
  5. Integer计算保留小数点位数
  6. Emule Edonkey server
  7. 怎样开启Win7快速启动栏以及怎样显示右下角运行程序通知
  8. 网易云/QQ音乐导入Apple Music
  9. MATLAB画Correlation plots
  10. Ajax选项卡、隔行换色、弹出遮罩层…