Relationships

Propel supports basic one-to-one relationships. More complex many-to-many relationships are also possible by defining the cross-reference tables in your data model and explicitly using this cross-reference table when selecting records. This chapter describes how to create inter-related entities and use optimized methods for returning related entities.

Defining Entity Relationships

The Propel representation of entity relationships corresponds very closely to the way relationships are represented at the database level: namely through foreign keys. In the data model XML, you can use the <foreign-key> tag to specify a column as a 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 will generate SQL definitions that use native foreign keys if your database driver supports them. Propel will also use foreign key information to generate methods in your peer and object classes to fetch related objects.

Fetching Related Objects

Using the example above (based on provided bookstore schema), you would have a Book->getAuthor() that would return an Author object using the specified foreign key.

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

The above code would result in the execution of two SQL statements:

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

While clearly this method works, it is not optimal -- especially if your database has native support for foreign keys. Propel also generates methods in your base peer class to fetch both book and author information in a single query.

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

In the above case only a single query is performed:

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

Note: in order to limit the methods in the public API, the join methods are protected methods of the base peer class; in order to use them you must create public methods in your stub peer class that invoke the protected parent methods.

class BookPeer {
 public function doSelectJoinAuthor(Criteria $c) {
  return parent::doSelectJoinAuthor($c);
 }

}

Many-to-Many Relationships

As mentioned in the chapter introduction, Propel's support for many-to-many relationships involves a middle step: defining the cross-reference table in your data model, and using results to perform lookups.

Take for example, a need to relate books with the people reading them -- many people reading a single book, one person reading many books:

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

In your PHP script you will need to make use of the "middleman" cross-reference table to retrieve the related entities:

$books = BookPeer::doSelect(new Criteria());

// for every book get all readers
foreach($books as $book) {
 $readers = $book->getBookReaderRefsJoinReader();
}

The code above will execute two SQL statements:

  1. SELECT * FROM book
  2. 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()

While this method is not excessively wasteful -- as performing a single select to retrieve many-to-many joined results doesn't usually make sense -- but it is also less elegant than the support for one-to-one joins. Requiring the explicitly reference of the cross-reference table is a drawback to using the very literal data modeling approach adopted by Propel (inherited from Torque).

Cascading Delete

Propel also supports cascading deletes, which can be specified using the onDelete="cascade" option of the <foreign-key> tag. Torque also supported an onUpdate="cascade" option, but since Propel does not allow updates to include changes to the primary key it would never actually be invoked -- and some database simply don't support this. Propel provides a new cascading delete emulation for databases that don't support this trigger (e.g. MySQL).

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

In the example above, the review rows will be automatically removed if the related book is deleted.