多対多の関係

データベースが関係を操作するのと同じようにPropelは関係をサポートしているため、Propelでは多対多の関係のサポートにはマジックはありません。JavaのHibernateなどではこのようなことがさらにORM層を抽象化されてサポートされています。つまりPropelで多対多の関係を作るには間に参照テーブルを置く必要があるということです。いつでも生成されたスタブクラス内に独自の関数を用意して対応することは可能です。将来的にはPropelに参照テーブルを指定することも可能になるかもしれませんが、現状ではできません。

例えば、多数の本を多数の人との関係 - 多数の人が1つの本を読んだり、1人が多数の本を読んだり:

<table name="book_reader_ref">
 <column name="book_id" type="INTEGER" required="true" primaryKey="true"/>
 <column name="reader_id" type="INTEGER" required="true" primaryKey="true"/>
 <foreign-key foreignTable="book">
   <reference
     local="book_id"
     foreign="book_id"/>
 </foreign-key>
 <foreign-key foreignTable="reader">
   <reference
     local="reader_id"
     foreign="reader_id"/>
 </foreign-key>
</table>

PHPスクリプトの中では"ミドルマン"を使って互いに参照しているテーブルから関係しているオブジェクトを取得する必要があります:

<?php
$books = BookPeer::doSelect(new Criteria());

// for every book get all readers
foreach($books as $book) {
  $readerRefs = $book->getBookReaderRefsJoinReader();
  foreach($readerRefs as $ref) {
    $reader = $ref->getReader(); // <-- Join関数が呼ばれているため、
                                 // ここでは追加クエリが発行されません
  }
}

ここではbook列の数をnとした場合、1+n回SQL文が実行されます。

  1. SELECT * FROM book
  2. n x SELECT * FROM book_reader_ref INNER JOIN reader ON reader.reader_id = book_reader_ref.reader_id WHERE book_reader_ref.book_id = $book->getBookId()

多対多の結果を1つのSELECT文で取得することはあまりいいやり方ではないということを考えると、この方法はとんでもなく無駄が多いわけではありませんが1つの関数を呼べばいいのに比べるとあまりエレガントな方法ではありません。お互いに参照しているテーブルを明示的に参照させるのはデータベースのデータモデルを使った(Torqueから受け継がれた)Propelのアプローチの欠点でもあります。

ここで提案:カスタムスタブ関数を作ってみる

少しだけこの方法を楽にするため、BookReaderクラス内にスタブ関数を作るといいかもしれません。例えばBookクラス内にgetReaders()関数を用意して上にある処理をカプセル化してしまうことも可能です。

Book.phpスタブクラスを編集してこの関数を追加します:

<?php
class Book extends BaseBook {
  
  /**
   * 関連したReaderオブジェクト全てを取得する便利な関数。
   * @パラメーター Criteria $c (必要であれば)Criteriaを追加して一部の結果だけを取得
   * @配列Reader[]を返す
   */
  public function getReaders($c = null) {
     $readers = array();
     foreach($book->getBookReaderRefsJoinReader($c) as $ref) {
        $readers[] = $ref->getReader();
     }
     return $readers;
  }
}