我是
靠谱客的博主
苹果跳跳糖,最近开发中收集的这篇文章主要介绍
如何使用 Repository 模式?如何使用 Repository 模式?,觉得挺不错的,现在分享给大家,希望可以做个参考。
概述
如何使用 Repository 模式?
使用 Repository 辅助 Model
Contents
- Version
- 资料库逻辑
- Model
- Repository
- Conclusion
- Sample Code
若将资料库逻辑都写在 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| namespace MyBlog;
use IlluminateAuthAuthenticatable;
use IlluminateDatabaseEloquentModel;
use IlluminateAuthPasswordsCanResetPassword;
use IlluminateFoundationAuthAccessAuthorizable;
use IlluminateContractsAuthAuthenticatable as AuthenticatableContract;
use IlluminateContractsAuthAccessAuthorizable as AuthorizableContract;
use IlluminateContractsAuthCanResetPassword as CanResetPasswordContract;
/**
* MyBlogUser
*
* @property integer $id
* @property string $name
* @property string $email
* @property string $password
* @property string $remember_token
* @property CarbonCarbon $created_at
* @property CarbonCarbon $updated_at
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereId($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereName($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereEmail($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser wherePassword($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereRememberToken($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereCreatedAt($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser 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行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| /**
* MyBlogUser
*
* @property integer $id
* @property string $name
* @property string $email
* @property string $password
* @property string $remember_token
* @property CarbonCarbon $created_at
* @property CarbonCarbon $updated_at
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereId($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereName($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereEmail($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser wherePassword($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereRememberToken($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereCreatedAt($value)
* @method static IlluminateDatabaseQueryBuilder|MyBlogUser whereUpdatedAt($value)
*/
|
IDE-Helper 帮我们替 model 加上注解,让我们可以在 PhpStorm 的语法提示使用 model 的 property 与 method。
Repository
初学者常会在 controller 直接调用 model 写资料库逻辑 :
1
2
3
4
5
6
7
8
| public function index()
{
$users = User::where('age', '>', 20)
->orderBy('age')
->get();
return view('users.index', compact('users'));
}
|
资料库逻辑是要抓 20 岁以上的资料
。
在中大型专案中,会有几个问题 :
- 将资料库逻辑写在 controller,造成 controller 的肥大难以维护。
- 违反 SOLID 的单一职责原則 : 资料库逻辑不应该写在 controller。
- controller 直接相依于model,使得我们无法对 controller 做单元测试。
比较好的方式是使用 repository :
- 将 model 依赖注入到 repository。
- 将资料库逻辑写在 repository。
- 将 repository 依赖注入到 service。
UserRepository.php
app/Repositories/UserRepository.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| namespace MyBlogRepositories;
use DoctrineCommonCollectionsCollection;
use MyBlogUser;
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 行
1
2
3
4
5
6
7
8
9
10
11
| /** @var User 注入的User model */
protected $user;
/**
* UserRepository constructor.
* @param User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
|
将相依的 User
model 依赖注入到 UserRepository
。
21 行
1
2
3
4
5
6
7
8
9
10
11
12
| /**
* 回传大于?年级的资料
* @param integer $age
* @return Collection
*/
public function getAgeLargerThan($age)
{
return $this->user
->where('age', '>', $age)
->orderBy('age')
->get();
}
|
将抓 20 岁以上的资料
的资料库逻辑写在 getAgeLargerThan()
。
不是使用User
facade,而是使用注入的$this->user
。33这里也可以使用User
facade 的方式,并不会影响可测试性,因为在测试 repository 时,会真的去读写资料库,而不会去 mock User
model,因此可以依可测试性決定要用依赖注入还是 Facade。
UserController.php
app/Http/Controllers/UserController.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| namespace AppHttpControllers;
use AppHttpRequests;
use MyBlogRepositoriesUserRepository;
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 IlluminateHttpResponse
*/
public function index()
{
$users = $this->userRepository
->getAgeLargerThan(20);
return view('users.index', compact('users'));
}
}
|
第8行
1
2
3
4
5
6
7
8
9
10
11
12
| /** @var UserRepository 注入的UserRepository */
protected $userRepository;
/**
* UserController constructor.
*
* @param UserRepository $userRepository
*/
public function __construct(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
|
将相依的 UserRepository
依赖注入到 UserController
。
26行
1
2
3
4
5
6
7
8
9
10
11
12
| /**
* Display a listing of the resource.
*
* @return IlluminateHttpResponse
*/
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| namespace MyBlog;
use IlluminateAuthAuthenticatable;
use IlluminateDatabaseEloquentBuilder;
use IlluminateDatabaseEloquentModel;
use IlluminateAuthPasswordsCanResetPassword;
use IlluminateFoundationAuthAccessAuthorizable;
use IlluminateContractsAuthAuthenticatable as AuthenticatableContract;
use IlluminateContractsAuthAccessAuthorizable as AuthorizableContract;
use IlluminateContractsAuthCanResetPassword 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行
1
2
3
4
5
6
7
8
9
10
11
| /**
* 回传大于?年级的资料
* @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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| namespace AppHttpControllers;
use AppHttpRequests;
use MyBlogUser;
class UserController extends Controller
{
/**
* Display a listing of the resource.
*
* @return IlluminateHttpResponse
*/
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 好很多了。
不过若在中大型项目,仍有以下问题 :
- Model 已经有原来的责任,若再加上 query scope,造成 model 过于肥大难以维护。
- 若资料库逻辑很多,可以拆成多 repository,可是却很难拆成多 model。
- 单元测试困难,必须面临 mock Eloquent 的问题。
Conclusion
- 业务上可以一开始 1 个 repository 对应 1 个 model,但不用太执着于 1 个 repository 一定要对应 1 个 model,可将 repository 视为逻辑上的资料库逻辑类别即可,可以横跨多个 model 处理,也可以 1 个 model 拆成多个 repository,这看需求而定。
- Repository 使得资料库逻辑从 controller 或 model中解放,不仅更容易维护、更容易扩展、更容易重复使用,且更容易测试。
文章来源:https://oomusou.io/laravel/repository
最后
以上就是苹果跳跳糖为你收集整理的如何使用 Repository 模式?如何使用 Repository 模式?的全部内容,希望文章能够帮你解决如何使用 Repository 模式?如何使用 Repository 模式?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复