Blog: Many-to-many Relationships: Check!

The Propel Team – 04 February 2010

Propel users used to complain about the lack of support for many-to-many relationships in the generated model classes. Writing a custom getter by hand for this kind of relationship wasn't so hard, but since people kept doing so repeatedly, it became clear that Propel had to support it. After all, the purpose of an ORM is to automate repetitive tasks and to make the life of a developer easier.

Starting with Propel 1.5, many-to-many relationships are first class citizens in Propel. In order to declare them, you must set the `isCrossRef` attribute to `true` in the `<table>` element of the cross-reference table (or "junction" table). For instance, if the `user` and `group` tables are related by a many-to-many relationship, this happens through the rows of a `user_group` table:

<table name="user">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="name" type="VARCHAR" size="32"/>
</table>
<table name="group">
  <column name="id" type="INTEGER" primaryKey="true" autoIncrement="true"/>
  <column name="name" type="VARCHAR" size="32"/>
</table>
<table name="user_group" isCrossRef="true">
  <column name="user_id" type="INTEGER" primaryKey="true"/>
  <column name="group_id" type="INTEGER" primaryKey="true"/>
  <foreign-key foreignTable="user">
    <reference local="user_id" foreign="id"/>
  </foreign-key>
  <foreign-key foreignTable="group">
    <reference local="group_id" foreign="id"/>
  </foreign-key>
</table>

From both sides of the relation, a many-to-many relationship is seen as a one-to-many relationship ; besides, Propel takes care of creating and retrieving instances of the middle class, so you never actually need to deal with them. That means that manipulating many-to-many relationships is nothing new if you already deal with one-to-many relationships:

// create and relate objects as if they shared a one-to-many relationship
$user = new User();
$user->setName('John Doe');
$group = new Group();
$group->setName('Anonymous');
// relate $user and $group
$user->addGroup($group);
// save the $user object, the $group object, and a new instance of the UserGroup class
$user->save();
// retrieve objects as if they shared a one-to-many relationship
$groups = $user->getGroups();
// the model query also features a smart filter method for the relation
$groups = GroupQuery::create()
  ->filterByUser($user)
  ->find();

So besides the `isCrossRef` attribute, there is nothing to learn - Propel avoids introducing new conventions when existing ones fit a new use case.