> For the complete documentation index, see [llms.txt](https://espier.gitbook.io/espier/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://espier.gitbook.io/espier/kuang-jia/jia-gou-si-xiang/architecture-repository.md).

# Repository

{% hint style="info" %}
使用 Repository 辅助 Model
{% endhint %}

若将数据库逻辑都写在 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 代码结构如下：

```php
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 代码结构如下：

```php
<?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 写数据库逻辑：

```php
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。

以此原则，我们看下，采用 Eloquent ORM 如何使用 Repository 模式。

UserRepository.php

```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;
    }

    /**
     * 返回年龄大于$age的用户
     * @param integer $age
     * @return Collection
     */
    public function getAgeLargerThan($age)
    {
        return $this->user
            ->where('age', '>', $age)
            ->orderBy('age')
            ->get();
    }
}
```

第 8 行

```php
/** @var User 注入的User model */
protected $user;

/**
 * UserRepository constructor.
 * @param User $user
 */
public function __construct(User $user)
{
    $this->user = $user;
}
```

将依赖的 User model 依赖注入到 UserRepository。

UserController.php

```php
<?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行

```php
/**
 * 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。

{% hint style="danger" %}
实际上建议repository仅依赖注入于service，而不要直接注入在controller，本范例因为还没介绍到servie模式，以便简化起见，所以直接注入于controller。
{% endhint %}

{% hint style="info" %}
是否该建立存储库接口？
{% endhint %}

理论上使用依赖注入时，应该使用接口，不过接口目的在于通过抽象化，方便在后期更换接口的具体实现，以便让程序达到开放封闭的要求，但是实际上要更换存储库的机会不高，除非你有更换资料库的需求，如从MySQL抽换到MongoDB，此时就该建立存储库接口。

不过由于我们使用了依赖注入，将来要从类改成interface也很方便，只要在构造方法的类型提示改成interface即可，维护成本很低，所以在此大可使用的存储库类，用接口反而会造成设计过度，等真正需求来时再重构成接口即可。

采用 Doctrine ORM 如何使用 Repository 模式。

由于 Doctrine ORM 自带 Repository 模式，所以直接使用即可。

UserRepository.php

```php
<?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

```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'));
    }
}
```
