若将数据库逻辑都写在 model,会造成 model 的肥大而难以维护,基于 SOLID 原则,我们应该使用 Repository 模式辅助 model,将相关的数据库逻辑封装在不同的 repository,方便中大型项目的维护。
数据库逻辑
在 CRUD 中,CUD 比较稳定,但 R 的部分则千变万化,大部分的数据库逻辑都在描述R 的部分,若将数据库逻辑写在 controller 或 model 都不适当,会造成 controller 与 model 肥大,造成日后难以维护。
Model
如果使用 Eloquent ORM 来实现 Repository 模式,需要修改 model 不要包含数据库逻辑,仅保留以下部分:
Property : 如$table,$fillable…等。
Mutator: 包括 mutator 與 accessor。
Method : relation 類的 method,如使用 hasMany() 與 belongsTo()。
简化后的Eloquent 的 model 类我们可以称之为Eloquent Class
,由于继承了Illuminate\Database\Eloquent\Model
类,所以Eloquent Class
仍然有很多方法可以操作数据库。
Eloquent Class 代码结构如下:
Copy namespace MyBlog ;
use Illuminate \ Database \ Eloquent \ Model ;
/**
* MyBlog\User
*
*/
class User extends Model {
/**
* 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' ];
}
而在 ecshopx 中,由于我们采用了 Doctrine ORM,其自带Repository模式。在 Doctrine ORM 中也有简化的 model 类: Entity Class
。
相比 Eloquent Class
Entity Class
更简单,其只包含 protected
或 private
的属性和getter
和 setter
方法(在 JAVA 中,这种对象成为值对象)。
Entity Class 代码结构如下:
Copy <? php
namespace MyBlog ;
use Doctrine \ ORM \ Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
class User
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
protected $id;
/**
* @ORM\Column(type="string")
*/
protected $name;
/**
* @ORM\Column(type="string")
*/
protected $email;
/**
* @ORM\Column(type="string")
*/
protected $password;
/**
* @ORM\Column(type="string")
*/
protected $remember_token;
// .. (other code getter setter )
}
Repository
初学者常会在 controller 直接调用 model 写数据库逻辑:
Copy 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 依赖注入到 service。
以此原则,我们看下,采用 Eloquent ORM 如何使用 Repository 模式。
UserRepository.php
Copy 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;
}
/**
* 返回年龄大于$age的用户
* @param integer $age
* @return Collection
*/
public function getAgeLargerThan ($age)
{
return $this -> user
-> where ( 'age' , '>' , $age )
-> orderBy ( 'age' )
-> get () ;
}
}
第 8 行
Copy /** @var User 注入的User model */
protected $user;
/**
* UserRepository constructor.
* @param User $user
*/
public function __construct ( User $user)
{
$this -> user = $user;
}
将依赖的 User model 依赖注入到 UserRepository。
UserController.php
Copy <? 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' )) ;
}
}
将依赖的 UserRepository 依赖注入到 UserController。
26行
Copy /**
* 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。
改用这种写法,有几个优点:
符合SOLID的单一职责原则:数据库逻辑写在repository,没写在controller。
符合SOLID的依赖转换原则:controller 并非直接相依于存储库,而是将存储库依赖注入进controller。
实际上建议repository仅依赖注入于service,而不要直接注入在controller,本范例因为还没介绍到servie模式,以便简化起见,所以直接注入于controller。
理论上使用依赖注入时,应该使用接口,不过接口目的在于通过抽象化,方便在后期更换接口的具体实现,以便让程序达到开放封闭的要求,但是实际上要更换存储库的机会不高,除非你有更换资料库的需求,如从MySQL抽换到MongoDB,此时就该建立存储库接口。
不过由于我们使用了依赖注入,将来要从类改成interface也很方便,只要在构造方法的类型提示改成interface即可,维护成本很低,所以在此大可使用的存储库类,用接口反而会造成设计过度,等真正需求来时再重构成接口即可。
采用 Doctrine ORM 如何使用 Repository 模式。
由于 Doctrine ORM 自带 Repository 模式,所以直接使用即可。
UserRepository.php
Copy <? php
namespace App \ Repositories ;
use Doctrine \ ORM \ EntityRepository ;
class UserRepository extends EntityRepository {
/**
* 返回年龄大于$age的用户
* @param integer $age
* @return Collection
*/
public function getAgeLargerThan ($age)
{
return $this -> user
-> where ( 'age' , '>' , $age )
-> orderBy ( 'age' )
-> get () ;
}
}
UserController.php
Copy 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' )) ;
}
}