基本的な関係(Relationships)

Propelはデータベースがテーブル同士の関係を外部キーでサポートしているのと同じようにオブジェクト同士の関係(Relationships)をサポートします。ということでPropelでの関係は簡単に理解できるはずですが、このモデルのシンプルさも継承してしまっています。例えばPropelは多対多のテーブルの間に参照テーブルを置くことを必要とします。(このトピックについての詳細は多対多の関係を参照してください。)

関係を定義する

関係はschema.xmlファイル内の関係する<table>セクションの中に外部キーを作ることで指定します。データベースでテーブルを他のテーブルと関連づけるように、<foreign-key>タグを使ってテーブルとテーブルの間の関係を作ります。

<table name="book">
 <column name="book_id" type="INTEGER" required="true" primaryKey="true"/>
 <column name="title" type="VARCHAR" size="100" required="true"/>
 <column name="author_id" type="INTEGER" required="true"/>
 <foreign-key foreignTable="author">
   <reference
     local="author_id"
     foreign="author_id"/>
 </foreign-key>
</table>
<table name="author">
 <column name="author_id" type="INTEGER" required="true" primaryKey="true"/>
 <column name="fullname" type="VARCHAR" size="40" required="true"/>
</table>

Propelはこの情報を以下のように使います:

  • (schema.sqlなどの)DDLファイル内にネイティブな外部キー(FOREIGN KEY)を作ります
  • getterとsetter関数を生成します(例:BookgetAuthor()setAuthor()関数を持つようになります)

1対1の関係

Propel 1.3からは1対1の関係をサポートします(ticket:279 を参照)。primary keyが同時に外部キーである時に1対1の関係が作られます。例えば(bookstore schema.xml をシンプルにしたもの):

  <table name="bookstore_employee" description="Employees of a bookstore">
        <column
          name="id"
          type="INTEGER"
          primaryKey="true"
          autoIncrement="true"
          description="Employee ID number"/>
        <column
          name="name"
          type="VARCHAR"
          size="32"
          description="Employee name"/>
  </table>

  <table name="bookstore_employee_account" description="Bookstore employees' login credentials">
        <column
          name="employee_id"
          type="INTEGER"
          primaryKey="true"
          description="Primary key for "
          />
        <column
          name="login"
          type="VARCHAR"
          size="32"/>
        <column
          name="password"
          type="VARCHAR"
          size="100"/>
        <foreign-key foreignTable="bookstore_employee" onDelete="cascade">
          <reference local="employee_id" foreign="id"/>
        </foreign-key>
  </table>

bookstore_employee_accountのprimary keyが同時にbookstore_employee_account外部キーであるため、Propelがこれを1対1の関係だと解釈し、関係の両側に相手のオブジェクトを一つだけ返す関数を生成します。ここではBookstoreEmployee->getBookstoreEmployeeAccount()BookstoreEmployeeAccount->getBookstoreEmployee()

関係しているオブジェクトの取得

(既に用意されているbookstoreスキーマの)上の例から指定されている外部キーを介してBook->getAuthor()からAuthorオブジェクトが返されます。

<?php

$books = BookPeer::doSelect(new Criteria());
foreach($books as $book) {
 $author = $book->getAuthor();
}

nを本の数として(そしてforeachのループ数も同じ数)、この上のコードから1+nのSQL構文が実行されることになります:

  1. 1 x SELECT * FROM book
  2. n x SELECT * FROM author WHERE author_id = $book->getAuthorId()

このやり方は問題なく動作しますが、1つ1つのループに対し2つめのクエリを実行することはパフォーマンス上よくありません。そこでPropelにはBase Peerクラス内にBookとAuthorの情報を一度に取得するための関数を生成します。

<?php

$books = BookPeer::doSelectJoinAuthor(new Criteria());
foreach($books as $book) {
 $author = $book->getAuthor();
}

この上の場合1つのクエリしか実行されません:

In the above case only a single query is performed:

  1. SELECT * FROM book INNER JOIN author ON author.author_id = book.author_id

多対多の関係ページにあるもっと複雑な関係の扱い方を参照してください。

Criteriaの中で関係を使う

doSelectJoin*()関数を使ったりCriteriaオブジェクトの中で明示的にjoinを指定することで関係を作ることができます。 (Ja/Documentation/1.3/Criteria?を参照してください)

どちらにせよ、関係のあるテーブルをCriteriaオブジェクト内で追加することができます:

<?php

$c = new Criteria(AuthorPeer::DATABASE_NAME);

$c->addJoin(AuthorPeer::ID, BookPeer::AUTHOR_ID, Criteria::INNER_JOIN);
$c->addJoin(BookPeer::PUBLISHER_ID, PublisherPeer::ID, Criteria::INNER_JOIN);

$c->add(PublisherPeer::NAME, 'Some Name');

$authors = AuthorPeer::doSelect($c);

... これは以下と同じことです:

SELECT * 
FROM author 
  INNER JOIN book ON book.author_id = author.id 
  INNER JOIN publisher ON publisher.id = book.publisher_id
WHERE publisher.name = 'Some Name'

関係で使うGetterとSetterのPHP名の上書き(Overriding)

Propel 1.3から(ticket:23 を参照)外部キーでつながっているオブジェクトを取得するのに使う関数のPHP名を指定できるようになりました。<foreign-key>の要素内の外部キーのphpNamerefPhpName属性を指定してください。この例でこの機能の利点が分かり、この機能を使っていただければと思います:

<!-- 同じテーブルを参照する外部キーと継承のテスト -->
  <table name="bookstore_employee">
        <column
          name="id"
          type="INTEGER"
          primaryKey="true"
          autoIncrement="true"
          description="Employee ID number"/>
        <column
          name="name"
          type="VARCHAR"
          size="32"
          description="Employee name"/>
        <column
          name="job_title"
          type="VARCHAR"
          size="32"
          description="Employee job title"/>
        <column
          name="supervisor_id"
          type="INTEGER"
          description="Fkey to supervisor."/>
        <foreign-key foreignTable="bookstore_employee" 
                     phpName="Supervisor" 
                     refPhpName="Subordinate">
          <reference local="supervisor_id" foreign="id"/>
        </foreign-key>
  </table>

これはbookstoreのschema.xmlから同じテーブルを参照する例です。

このphpName属性がsetSupervisor()の名前に反映され、refPhpNameaddSubordinate()などgetSubordinates()参照する外部キーの名前に反映されます。

更新(ON UPDATE)や削除の(ON DELETE)トリガー

PropelはまたON UPDATEON DELETEといった外部キーの側面もサポートします。これらの属性は<foreign-key>タグ内でonUpdateonDeleteを使って指定します。Propelはbこれらの属性に'CASCADE', 'SETNULL', 'RESTRICT'といった値を持たせることをサポートします。データベースが外部キーをネイティブにサポートしている場合はこれらのトリガーは外部キーが作られたときに指定され、サポートしていない場合はPropelがそれらの機能をエミュレーションします。

<table name="review">
 <column name="review_id" type="INTEGER" primaryKey="true" required="true"/>
 <column name="reviewer" type="VARCHAR" size="50" required="true"/>
 <column name="book_id" required="true" type="INTEGER"/>
        <foreign-key foreignTable="book" onDelete="CASCADE">
   <reference local="book_id" foreign="book_id"/>
 </foreign-key>   
</table>

この上の例では関係しているbookの列が削除された場合review列が自動的に削除されます。