l5-repositoryでrepositoryに統一性を与えられるか

# 表題のl5-repositoryとは

repositoryに統一されたインターフェースを持たせられそうなplugin
githubのスター数: 1191
対応laravel < 5.2 (リポジトリのコメントみる限り)

https://github.com/andersao/l5-repository

## 実行環境

- laravel5.1.*
- php7.1.0

## なぜl5-repositoryを使ってみようと思ったか

チーム開発においてデータベースへのアクセスを
repositoryに任せるのはいいがメソッドの書き方が属人的に
なるので統一された命名規則を使いたいと思った。

repositoryに実装されているのは本当にそのモデルに
依存しているものだけにしてあとは強制的に継承先のものを使う。

## 使ってみる

### 準備

インストール

```bash
$ composer require prettus/l5-repository
```

サービスプロバイダに登録

```php
'providers' => [
    ...
    Prettus\Repository\Providers\RepositoryServiceProvider::class,
],
```

l5-repositoryのconfigファイルを生成

```
$ php artisan vendor:publish

Copied File [/vendor/prettus/l5-repository/src/resources/config/repository.php] To [/config/repository.php]
```

### Repositroyを作る

```php
<?php

namespace App\Repositories;

use Prettus\Repository\Eloquent\BaseRepository;
use App\Models\User;

class UserRepository extends BaseRepository
{
    /**
     * このリポジトリーで使うモデルのパスを返す
     * BaseRepository内で$this->app->make($this->model())
     * として渡される
     *
     * @return string
     */
    function model()
    {
        return User::class;
    }
}
```

model()は継承しているBaseRepositoryのabstractで
定義されているので実装必須。

ちなみに上記インストールにて/config/repository.phpを作成している場合
下記コマンドにてrepositoryの雛形は作ってくれる

```
php artisan make:repository [Model]
```

### BaseRepositoryに実装されているもの

userRepositoryを使っているとします

```php
//全データを取得
$this->userRepository->all();


//idで検索
$this->userRepository->find($id);


/**
 * リレーション先のモデルを指定して取得
 * Userモデルにpost()でリレーションが定義されている前提
 */
$this->userRepository->with(['post'])->find($id);


//フィールド名を指定してデータを取得
$this->userRepository->findByField('name', 'root');


//複数条件を指定してデータを取得
$this->userRepository->findWhere([
    //Default Condition =
    'name' => 'root',
    //Custom Condition
    ['created_at', '>', '2016-01-01 00:00:00']
]);


//１つのフィールドに対し、複数の値から検索
$this->userRepository->findWhereIn('id', [1, 2, 3, 4]);

// 上記の逆も用意されている
$this->userRepository->findWhereNotIn('id', [6, 7, 8, 9]);


/**
 * 条件をクロージャでも渡せる
 *
 * 各メソッドにて$this->applyScope()が呼ばれていて
 * そこで指定した条件は追加される
 */
$this->userRepository->scopeQuery(function($query){
    return $query->orderBy('updated_at', 'asc');
})->all();


/**
 * 一件取得
 * ただ、これは引数に条件は渡せないので、scopeQueryや
 * 後述のCriteriaを使う前提での実装
 */
$this->userRepository->first($columns);


/**
 * create, update, deleteも実装されている(ラッパーみたいな感じ)
 */
$this->userRepository->create(Input::all());
$this->userRepository->update(Input::all(), $id);
$this->userRepos
```

### Criteria

必要に応じてCriteriaで定義した条件をRepositoryに適用させ
クエリを変えることができるようになる

__Criteria作成__

```
php artisan make:criteria User
```

今回はUserRepositoryに関するCriteriaを作るので
Userを指定しました。命名規則はないと思います。

デフォルトでは、app/Criteria/にUserCriteriaが作られました。
中身はこんな感じ

```php:UserCriteria.php
<?php

namespace App\Criteria;

use Prettus\Repository\Contracts\CriteriaInterface;
use Prettus\Repository\Contracts\RepositoryInterface;

/**
 * Class UserCriteria
 * @package namespace App\Criteria;
 */
class UserCriteria implements CriteriaInterface
{
    /**
     * Apply criteria in query repository
     *
     * @param                     $model
     * @param RepositoryInterface $repository
     *
     * @return mixed
     */
    public function apply($model, RepositoryInterface $repository)
    {
        return $model;
    }
}
```

__使用例__
emailがgmailのユーザーをとってくる

```php:UserCriteria.php
/**
 * Class UserCriteria
 * @package namespace App\Criteria;
 */
class UserCriteria implements CriteriaInterface
{
    /**
     * Apply criteria in query repository
     *
     * @param                     $model
     * @param RepositoryInterface $repository
     *
     * @return mixed
     */
    public function apply($model, RepositoryInterface $repository)
    {
        $model = $model->where('email', 'like', '%@gmail%');
        return $model;
    }
}
```


__実行側__

```php
//Criteria適用
$this->userRepository->pushCriteria(UserCriteria::class);
dd($this->userRepository->all());
```

またはこんな書き方

```php
$this->userRepository->getByCriteria(new UserCriteria());
```

__Repository内にデフォで使用する場合は設定しておける__

```php
<?php

namespace App\Repositories;

use Prettus\Repository\Eloquent\BaseRepository;
use App\Models\User;
use App\Criteria\UserCriteria;

class UserRepository extends BaseRepository
{
    /**
     *
     */

    /**
     * defaultで使用するCriteriaがある場合は設定できる
     */
    public function boot(){
        $this->pushCriteria(UserCriteria::class);
    }

}
```

__いやいや、デフォで設定してあるけどこの時はデフォの条件使いたくない。。__

スキップしましょう

```php
$this->repository->skipCriteria()->all();
```

削除もできるみたいですが用途は如何に。。

```php
$this->repository->popCriteria(Criteria1::class);
```

### まとめ

チームにrepositoryを使う上での制約をもたらすには十分なのではないでしょうか

簡単なクエリでrepositoryが変に膨らむこともないだろうし
こちらで共通の処理を行う抽象的なメソッドを再発明しなくて
済むのもいいですね。

次回のプロジェクトではこいつ使ってみたいと思います。

__RequestCriteriaやPresenters(オブジェクト出力のラッパー)
Validatorなども便利なものがあるようですが今回は割愛__
だってFormRequest今のところ便利だし。。笑

### Model周りの疑問
laravelはディレクトリ構成に自由が効くのでCriteriaなど
どう運用していってるのか既にプラクティスがある方いれば
是非教えていただきたいです。

自分的にはrepositoryでsaveなど任せるのは
違和感あるのでそこはmodelにやらせてました。
ただ、modelを注入したサービスクラスを別に作るべきなのかとも思っています。
みなさんどうしてるんでしょうか。笑