StackOverflowの質問「Managing relationships in Laravel, adhering to the repository pattern」に対するベストアンサーの和訳

#  本記事で見れるもの
LaravelのService/Repositoryパターンの使い方や具体例

#  概要
[LaravelのService/Repoのそれぞれの違いについてのLaracastの質問](https://laracasts.com/discuss/channels/laravel/is-service-pattern-and-repository-pattern-the-same)にて、[貼っているStackOverflowの投稿](https://stackoverflow.com/questions/18817615/managing-relationships-in-laravel-adhering-to-the-repository-pattern/18992000#18992000)が具体例を用いていて理解しやすい良解答だったので、和訳し共有してみようと思いました（既出かも）。

#  和訳
Laravel4を使っている大規模なプロジェクトを丁度終えたところなので、ここで質問されていること全てに解答ができるかと思います。LeanpubでLaravelに関する書籍を隈なく読んだり、大量にググったことを通じて、以下のような構造に至りました。

1. 1テーブル毎に1 Eloquent Modelクラス
2. 1 Eloquent Model毎に1 Repositoryクラス
3. 1 Serviceクラスは複数のRepositoryとやり取りすることがある

映画のデータベースを構築することを考えてみます。少なくとも以下のEloquent Modelクラスを用意することになるでしょう：
- Movie
- Studio
- Director
- Actor
- Review

1 Repositoryクラスは対応したEloquent Modelをカプセル化し、そのDBにおけるCRUD操作に責任を負います。この場合Repositoryクラスは以下のようになるでしょう：
- MovieRepository
- StudioRepository
- DirectorRepository
- ActorRepository
- ReviewRepository

各RepositoryクラスはBaseRepositoryクラスを継承し、またBaseRepositoryクラスには以下のようなインターフェイスが実装されているでしょう。

```php:BaseRepository.php
interface BaseRepositoryInterface
{
    public function errors();

    public function all(array $related = null);

    public function get($id, array $related = null);

    public function getWhere($column, $value, array $related = null);

    public function getRecent($limit, array $related = null);

    public function create(array $data);

    public function update(array $data);

    public function delete($id);

    public function deleteWhere($column, $value);
}
```
1 Serviceクラスは複数のRepositoryを組み合わせるために使われると共に、アプリケーションの実際の「ビジネスロジック」を含んでいます。ControllerがすることはCreate,Update,DeleteをするためにServiceクラスとやり取りをすること**だけです**

新しいMovieレコードを作ろうと思ったら、MovieControllerクラスは以下のようなメソッドになるでしょう：
```php:MovieController.php
public function __construct(MovieRepositoryInterface $movieRepository, MovieServiceInterface $movieService)
{
    $this->movieRepository = $movieRepository;
    $this->movieService = $movieService;
}

public function postCreate()
{
    if( ! $this->movieService->create(Input::all()))
    {
        return Redirect::back()->withErrors($this->movieService->errors())->withInput();
    }

    // 映画が正常に登録されました。必要な処理をここに書いてください。
}
```

どのようにデータをControllerにPOSTするかは自由ですが、ここではpostCreate()メソッド内の``Input::all()``の戻り値がこのようであるとします：
```php
$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\'s father, an Air Force Pilot, is shot down by MiGs belonging to a radical Middle Eastern state, no one seems able to get him out. Doug finds Chappy, an Air Force Colonel who is intrigued by the idea of sending in two fighters piloted by himself and Doug to rescue Doug\'s father after bombing the MiG base.'
    ),
    'actors' => array(
        0 => 'Louis Gossett Jr.',
        1 => 'Jason Gedrick',
        2 => 'Larry B. Scott'
    ),
    'director' => 'Sidney J. Furie',
    'studio' => 'TriStar Pictures'
)
```
MovieRepositoryはActor,Director,Studioの各レコードの作成を分かっているべきではないので、以下のようなMovieServiceクラスを作成することにしましょう：

```php:MovieService.php
public function __construct(MovieRepositoryInterface $movieRepository, ActorRepositoryInterface $actorRepository, DirectorRepositoryInterface $directorRepository, StudioRepositoryInterface $studioRepository)
{
    $this->movieRepository = $movieRepository;
    $this->actorRepository = $actorRepository;
    $this->directorRepository = $directorRepository;
    $this->studioRepository = $studioRepository;
}

public function create(array $input)
{
    $movieData    = $input['movie'];
    $actorsData   = $input['actors'];
    $directorData = $input['director'];
    $studioData   = $input['studio'];

    // In a more complete example you would probably want to implement database transactions and perform input validation using the Laravel Validator class here.

    // Create the new movie record
    $movie = $this->movieRepository->create($movieData);

    // Create the new actor records and associate them with the movie record
    foreach($actors as $actor)
    {
        $actorModel = $this->actorRepository->create($actor);
        $movie->actors()->save($actorModel);
    }

    // Create the director record and associate it with the movie record
    $director = $this->directorRepository->create($directorData);
    $director->movies()->associate($movie);

    // Create the studio record and associate it with the movie record
    $studio = $this->studioRepository->create($studioData);
    $studio->movies()->associate($movie);

    // Assume everything worked. In the real world you'll need to implement checks.
    return true;
}
```
こうして出来上がったのは、道理にかなった良い[関心の分離](https://qiita.com/MinoDriven/items/37599172b2cd27c38a33)です。RepositoryはDBとデータを送受信するEloquent Modelのみを認識しています。ControllerはRepositoryを気にすることなく、ユーザーから受け取ったデータを適切なServiceに渡しているだけです。Serviceは受け取ったデータが*どのように*DBに保存されるかを気にせずに、Controllerに渡されたデータを適切なRepositoryに渡しているだけとなります。

#  まとめ
DB:Model:Repository:Service = 1:1:1:n

Serviceと同様にRepositoryも複数のModelにアクセス出来るようにしたほうが良いという~~bullshit~~考えを持っている人がいたので、~~爆殺~~何としてでも説得して理解を促さないといけないなあと思いました。