web-dev-qa-db-ja.com

リポジトリパターンに準拠した、Laravelでの関係の管理

Laravel 4でアプリを作成しているときに、T。OtwellのLaravel.

次のテーブル構造になりました。

  • 学生:ID、名前
  • コース:id、name、teacher_id
  • 教師:ID、名前
  • 割り当て:id、name、course_id
  • スコア(学生と課題の間のピボットとして機能):student_id、assignment_id、scores

これらすべてのテーブルの検索、作成、更新、削除メソッドを備えたリポジトリクラスがあります。各リポジトリには、データベースと対話するEloquentモデルがあります。関係はLaravelのドキュメントごとにモデルで定義されます: http://laravel.com/docs/eloquent#relationships

新しいコースを作成するときは、コースリポジトリでcreateメソッドを呼び出すだけです。そのコースには課題があるため、コースを作成するときに、コースの各生徒のスコア表にエントリを作成する必要もあります。これは、割り当てリポジトリを使用して行います。これは、課題リポジトリが課題モデルと生徒モデルの2つのEloquentモデルと通信することを意味します。

私の質問は、このアプリがおそらくサイズが大きくなり、より多くの関係が導入されるため、リポジトリ内の異なるEloquentモデルと通信することをお勧めしますか、代わりに他のリポジトリを使用してこれを行う必要がありますか? )またはEloquentモデルですべて一緒に行う必要がありますか?

また、課題と学生の間のピボットとしてスコア表を使用するのは良い習慣ですか、それは別の場所で行う必要がありますか?

112
ehp

あなたが意見を求めていることに留意してください:D

これが私のものです:

TL; DR:はい、大丈夫です。

あなたはよくやっている!

私はあなたが頻繁にやっていることを正確にやって、それがうまくいくと思う。

ただし、テーブルごとにリポジトリを作成するのではなく、ビジネスロジックを中心にリポジトリを整理することがよくあります。これは、アプリケーションが「ビジネス上の問題」をどのように解決するかを中心とした視点であるため便利です。

コースは「エンティティ」であり、属性(タイトル、IDなど)だけでなく、他のエンティティ(独自の属性と場合によってはエンティティを持つ割り当て)もあります。

「コース」リポジトリは、コースとコースの属性/割り当て(課題を含む)を返すことができるはずです。

幸運にも、Eloquentでそれを達成できます。

(多くの場合、テーブルごとにリポジトリが作成されますが、一部のリポジトリは他のリポジトリよりもはるかに多く使用されているため、より多くのメソッドがあります。たとえば、「コース」リポジトリは、アプリケーションの中心はコースであり、コースの割り当てのコレクションではありません)。

トリッキーな部分

いくつかのデータベースアクションを実行するために、リポジトリ内でリポジトリをよく使用します。

データを処理するためにEloquentを実装するリポジトリは、Eloquentモデルを返す可能性があります。その観点から、割り当て(またはその他のユースケース)を取得または保存するために、コースモデルで組み込みの関係を使用すれば問題ありません。私たちの「実装」はEloquentを中心に構築されています。

実用的な観点から、これは理にかなっています。データソースをEloquentが処理できないもの(SQL以外のデータソース)に変更することはほとんどありません。

ORMS

少なくとも私にとって、このセットアップの最も難しい部分は、Eloquentが実際に私たちを助けたり害したりしているかどうかを判断することです。 ORMは実用的な観点からは非常に役立ちますが、「ビジネスロジックエンティティ」コードとデータ取得を行うコードを組み合わせているため、難しいテーマです。

リポジトリの責任が実際にデータを処理するのか、エンティティ(ビジネスドメインエンティティ)の取得/更新を処理するのかは、この種の混乱を招きます。

さらに、ビューに渡すオブジェクトそのものとして機能します。後でリポジトリでEloquentモデルを使用する必要がなくなった場合は、ビューに渡される変数が同じように動作するか、同じメソッドを使用できるようにする必要があります。そうしないと、データソースを変更すると、ビューを使用すると、最初にロジックをリポジトリに抽象化する目的を(部分的に)失いました-プロジェクトの保守性は低下します。

とにかく、これらはやや不完全な考えです。述べられているように、彼らは私の意見であり、これはたまたま Domain Driven Design を読み、 "uncle bob's" keynote at Ruby昨年の中西部。

68
fideloper

私はLaravel 4を使用して大規模なプロジェクトを仕上げていますが、今あなたが尋ねているすべての質問に答えなければなりませんでした。利用可能なLaravel= LeanpubとたくさんのGooglingで、次の構造を思いつきました。

  1. データテーブルごとに1つのEloquent Modelクラス
  2. 説得力のあるモデルごとに1つのリポジトリクラス
  3. 複数のリポジトリクラス間で通信できるサービスクラス。

それで、私が映画データベースを構築しているとしましょう。少なくとも次のEloquent Modelクラスがあります。

  • 映画
  • スタジオ
  • ディレクター
  • 俳優
  • レビュー

リポジトリクラスは、各Eloquent Modelクラスをカプセル化し、データベースのCRUD操作を担当します。リポジトリクラスは次のようになります。

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • レビューリポジトリ

各リポジトリクラスは、次のインターフェイスを実装するBaseRepositoryクラスを拡張します。

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

Serviceクラスは、複数のリポジトリを結合するために使用され、アプリケーションの実際の「ビジネスロジック」を含みます。コントローラーのみ作成、更新、削除アクションのサービスクラスと通信します。

そのため、データベースに新しいMovieレコードを作成する場合、MovieControllerクラスには次のメソッドがあります。

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();
    }

    // New movie was saved successfully. Do whatever you need to do here.
}

POSTデータをコントローラーに送る方法を決定するのはあなた次第ですが、postCreate()メソッドのInput :: all()によって返されるデータは次のようになります。

$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はデータベースにアクター、ディレクター、またはスタジオのレコードを作成する方法を知らないはずなので、MovieServiceクラスを使用します。これは次のようになります。

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

したがって、私たちが残しているのは、ナイスで賢明な懸念の分離です。リポジトリは、データベースから挿入および取得するEloquentモデルのみを認識します。コントローラーはリポジトリを気にせず、ユーザーから収集したデータを渡して適切なサービスに渡します。サービスは気にしませんhow受け取ったデータはデータベースに保存され、コントローラーから与えられた関連データをハンドオフします適切なリポジトリ。

209
Kyle Noland

私は、「正しいか間違っている」というよりも、自分のコードが何をしていて、それが何をしているのかという観点から考えるのが好きです。これは私が私の責任を分解する方法です:

  • コントローラーはHTTPレイヤーであり、リクエストを基になるAPIにルーティングします(別名、フローを制御します)
  • モデルはデータベーススキーマを表し、データの外観、データの関係、必要なグローバル属性(連結された姓と名を返すための名前メソッドなど)をアプリケーションに伝えます
  • リポジトリは、より複雑なクエリとモデルとの対話を表します(モデルメソッドでクエリを実行しません)。
  • 検索エンジン-複雑な検索クエリの作成を支援するクラス。

これを念頭に置いて、リポジトリを使用することは毎回理にかなっています(interfacesなどを作成するかどうかはまったく別のトピックです)。私はこのアプローチが好きです。なぜなら、特定の仕事をする必要があるときにどこに行くべきかを正確に知っているからです。

また、基本リポジトリを作成する傾向があります。通常は、主なデフォルト(基本的にCRUD操作)を定義する抽象クラスであり、各子は必要に応じてメソッドを拡張および追加したり、デフォルトをオーバーロードしたりできます。モデルを挿入すると、このパターンが非常に堅牢になります。

5
Oddman

リポジトリは、(ORMだけでなく)データの一貫したファイリングキャビネットと考えてください。アイデアは、一貫した使いやすいAPIでデータを取得することです。

Model :: all()、Model :: find()、Model :: create()だけを実行していることに気付いた場合、リポジトリを抽象化してもあまりメリットはないでしょう。一方、クエリまたはアクションに対してもう少しビジネスロジックを実行したい場合は、リポジトリを作成して、データを処理するためのAPIを使いやすくすることができます。

リポジトリは、関連するモデルを接続するために必要なより詳細な構文のいくつかを処理するための最良の方法であるかどうかを尋ねていたと思います。状況に応じて、私ができることがいくつかあります。

  1. 新しい子モデルを親モデル(1対1または1対多)からぶら下げて、createWithParent($attributes, $parentModelInstance)のような子リポジトリにメソッドを追加します。これにより、$parentModelInstance->idparent_id属性のフィールドとcreateを呼び出します。

  2. 多対多の関係をアタッチして、実際にモデルに関数を作成して、$ instance-> attachChild($ childInstance)を実行できるようにします。これには、両側に既存の要素が必要であることに注意してください。

  3. 1回の実行で関連モデルを作成し、ゲートウェイと呼ぶものを作成します(ファウラーの定義から少し外れている場合があります)。コントローラーやコマンドにあるロジックを変更したり、複雑にしたりするロジックの代わりに、$ gateway-> createParentAndChild($ parentAttributes、$ childAttributes)を呼び出す方法。

5
Ryan Tablada