You are currently reading the 1.2 documentation. Switch to 2.2  2.1  2.0 

Join Table Associations

Many to Many

If you are coming from relational database background it may be familiar to you how many-to-many associations are handled: an additional association table is needed.

In many-to-many relations the relation between the two components is always an aggregate relation and the association table is owned by both ends. For example in the case of users and groups: when a user is being deleted, the groups he/she belongs to are not being deleted. However, the associations between this user and the groups he/she belongs to are instead being deleted. This removes the relation between the user and the groups he/she belonged to, but does not remove the user nor the groups.

Sometimes you may not want that association table rows are being deleted when user / group is being deleted. You can override this behavior by setting the relations to association component (in this case Groupuser) explicitly.

In the following example we have Groups and Users of which relation is defined as many-to-many. In this case we also need to define an additional class called Groupuser.

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('Group as Groups', array(
                'local' => 'user_id',
                'foreign' => 'group_id',
                'refClass' => 'UserGroup'
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
User:
# ...
  relations:
    # ...
    Groups:
      class: Group
      local: user_id
      foreign: group_id
      refClass: UserGroup

The above refClass option is required when setting up many-to-many relationships.

// models/Group.php

class Group extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->setTableName('groups');
        $this->hasColumn('name', 'string', 30);
    }

    public function setUp()
    {
        $this->hasMany('User as Users', array(
                'local' => 'group_id',
                'foreign' => 'user_id',
                'refClass' => 'UserGroup'
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
Group:
  tableName: groups
  columns:
    name: string(30)
  relations:
    Users:
      class: User
      local: group_id
      foreign: user_id
      refClass: UserGroup

Please note that group is a reserved keyword so that is why we renamed the table to groups using the setTableName method. The other option is to turn on identifier quoting using the Doctrine_Core::ATTR_QUOTE_IDENTIFIER attribute so that the reserved word is escaped with quotes.

$manager->setAttribute(Doctrine_Core::Doctrine_Core::ATTR_QUOTE_IDENTIFIER, true);
// models/UserGroup.php

class UserGroup extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user_id', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('group_id', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
UserGroup:
  columns:
    user_id:
      type: integer
      primary: true
    group_id:
      type: integer
      primary: true

Notice how the relationship is bi-directional. Both User has many Group and Group has many User. This is required by Doctrine in order for many-to-many relationships to fully work.

Now lets play around with the new models and create a user and assign it some groups. First create a new User instance:

// test.php

// ...
$user = new User();

Now add two new groups to the User:

// test.php

// ...
$user->Groups[0]->name = 'First Group';

$user->Groups[1]->name = 'Second Group';

Now you can save the groups to the database:

// test.php

// ...
$user->save();

Now you can delete the associations between user and groups it belongs to:

// test.php

// ...
$user->UserGroup->delete();

$groups = new Doctrine_Collection(Doctrine_Core::getTable('Group'));

$groups[0]->name = 'Third Group';

$groups[1]->name = 'Fourth Group';

$user->Groups[2] = $groups[0];
// $user will now have 3 groups

$user->Groups = $groups;
// $user will now have two groups 'Third Group' and 'Fourth Group'

$user->save();

Now if we inspect the $user object data with the Doctrine_Record::toArray():

// test.php

// ...
print_r($user->toArray(true));

The above example would produce the following output:

$ php test.php
Array
(
    [id] => 1
    [is_active] => 1
    [is_super_admin] => 0
    [first_name] =>
    [last_name] =>
    [username] => default username
    [password] =>
    [type] =>
    [created_at] => 2009-01-20 16:48:57
    [updated_at] => 2009-01-20 16:48:57
    [Groups] => Array
        (
            [0] => Array
                (
                    [id] => 3
                    [name] => Third Group
                )

            [1] => Array
                (
                    [id] => 4
                    [name] => Fourth Group
                )

        )

    [UserGroup] => Array
        (
        )

)

Self Referencing (Nest Relations)

Non-Equal Nest Relations
// models/User.php

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('User as Parents', array(
                'local'    => 'child_id',
                'foreign'  => 'parent_id',
                'refClass' => 'UserReference'
            )
        );

        $this->hasMany('User as Children', array(
                'local'    => 'parent_id',
                'foreign'  => 'child_id',
                'refClass' => 'UserReference'
            )
        );
    }
}

// models/UserReference.php

class UserReference extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('parent_id', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('child_id', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Parents:
      class: User
      local: child_id
      foreign: parent_id
      refClass: UserReference
      foreignAlias: Children

UserReference:
  columns:
    parent_id:
      type: integer
      primary: true
    child_id:
      type: integer
      primary: true
Equal Nest Relations

Equal nest relations are perfectly suitable for expressing relations where a class references to itself and the columns within the reference class are equal.

This means that when fetching related records it doesn't matter which column in the reference class has the primary key value of the main class.

The previous clause maybe hard to understand so lets take an example. We define a class called User which can have many friends. Notice here how we use the 'equal' option.

// models/User.php

class User extends BaseUser
{
    public function setUp()
    {
        parent::setUp();

        // ...

        $this->hasMany('User as Friends', array(
                'local'    => 'user1',
                'foreign'  => 'user2',
                'refClass' => 'FriendReference',
                'equal'    => true,
            )
        );
    }
}

// models/FriendReference.php

class FriendReference extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('user1', 'integer', null, array(
                'primary' => true
            )
        );

        $this->hasColumn('user2', 'integer', null, array(
                'primary' => true
            )
        );
    }
}

Here is the same example in YAML format. You can read more about YAML in the YAML Schema Files chapter:

---
# schema.yml

# ...
User:
# ...
  relations:
    # ...
    Friends:
      class: User
      local: user1
      foreign: user2
      refClass: FriendReference
      equal: true

FriendReference:
  columns:
    user1:
      type: integer
      primary: true
    user2:
      type: integer
      primary: true

Now lets define 4 users: Jack Daniels, John Brandy, Mikko Koskenkorva and Stefan Beer with Jack Daniels and John Brandy being buddies and Mikko Koskenkorva being the friend of all of them.

// test.php

// ...
$daniels = new User();
$daniels->username = 'Jack Daniels';

$brandy = new User();
$brandy->username = 'John Brandy';

$koskenkorva = new User();
$koskenkorva->username = 'Mikko Koskenkorva';

$beer = new User();
$beer->username = 'Stefan Beer';

$daniels->Friends[0] = $brandy;

$koskenkorva->Friends[0] = $daniels;
$koskenkorva->Friends[1] = $brandy;
$koskenkorva->Friends[2] = $beer;

$conn->flush();

Calling Doctrine_Connection::flush() will trigger an operation that saves all unsaved objects and wraps it in a single transaction.

Now if we access for example the friends of Stefan Beer it would return one user 'Mikko Koskenkorva':

// test.php

// ...
$beer->free();
unset($beer);
$user = Doctrine_Core::getTable('User')->findOneByUsername('Stefan Beer');

print_r($user->Friends->toArray());

Now when you execute test.php you will see the following:

$ php test.php
Array
(
    [0] => Array
        (
            [id] => 4
            [is_active] => 1
            [is_super_admin] => 0
            [first_name] =>
            [last_name] =>
            [username] => Mikko Koskenkorva
            [password] =>
            [type] =>
            [created_at] => 2009-01-20 16:53:13
            [updated_at] => 2009-01-20 16:53:13
        )

)

Questions and Feedback

If you find a problem with the documentation or have a suggestion, please register and open a ticket.

If you need support or have a technical question, you can post to the user mailing-list.