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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://espier.gitbook.io/espier/kuang-jia/jia-gou-si-xiang/architecture-repository.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
