In Doctrine all record relations are being set with {{hasMany}}, {{hasOne}}, {{ownsMany}} and {{ownsOne}} methods. Doctrine supports almost any kind of database relation from simple one-to-one foreign key relations to join table self-referencing relations.
++ Relation aliases
Doctrine supports relation aliases through {{as}} keyword.
<code type="php">
class Forum_Board extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('name', 'string', 100);
$this->hasColumn('description', 'string', 5000);
}
public function setUp() {
// notice the 'as' keyword here
$this->ownsMany('Forum_Thread as Threads', 'Forum_Thread.board_id');
}
}
class Forum_Thread extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('board_id', 'integer', 10);
$this->hasColumn('updated', 'integer', 10);
$this->hasColumn('closed', 'integer', 1);
}
public function setUp() {
// notice the 'as' keyword here
$this->hasOne('Forum_Board as Board', 'Forum_Thread.board_id');
}
}
$board = new Board();
$board->Threads[0]->updated = time();
</code>
++ Foreign key associations
+++ One-To-One
Binding One-To-One foreign key associations is done with {{Doctrine_Record::ownsOne()}} and {{Doctrine_Record::hasOne()}} methods. In the following example user owns one email and has one address. So the relationship between user and email is one-to-one composite. The relationship between user and address is one-to-one aggregate.
The {{Email}} component here is mapped to {{User}} component's column {{email_id}} hence their relation is called LOCALKEY relation. On the other hand the {{Address}} component is mapped to {{User}} by it's {{user_id}} column hence the relation between {{User}} and {{Address}} is called FOREIGNKEY relation.
$this->hasMany('Task as Subtask','Subtask.parent_id');
}
public function setTableDefinition() {
$this->hasColumn('name','string',100);
$this->hasColumn('parent_id','integer');
}
}
</code>
++ 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 user is being deleted the groups it belongs to are not being deleted and the associations between this user and the groups it belongs to are being deleted.
Sometimes you may not want that association table rows are being deleted when user / group is being deleted. You can override this behoviour 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}}.
<code type="php">
class User extends Doctrine_Record {
public function setUp() {
$this->hasMany('Group','Groupuser.group_id');
}
public function setTableDefinition() {
$this->hasColumn('name','string',30);
}
}
class Group extends Doctrine_Record {
public function setUp() {
$this->hasMany('User','Groupuser.user_id');
}
public function setTableDefinition() {
$this->hasColumn('name','string',30);
}
}
class Groupuser extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('user_id','integer');
$this->hasColumn('group_id','integer');
}
}
$user = new User();
// add two groups
$user->Group[0]->name = 'First Group';
$user->Group[1]->name = 'Second Group';
// save changes into database
$user->save();
// deleting the associations between user and groups it belongs to
$user->Groupuser->delete();
$groups = new Doctrine_Collection($conn->getTable('Group'));
$groups[0]->name = 'Third Group';
$groups[1]->name = 'Fourth Group';
$user->Group[2] = $groups[0];
// $user will now have 3 groups
$user->Group = $groups;
// $user will now have two groups 'Third Group' and 'Fourth Group'
</code>
+++ Self-referencing
Self-referencing with join tables is done as follows:
<code type="php">
class User extends Doctrine_Record {
public function setUp() {
$this->hasMany('User as Friend','UserReference.user_id-user_id2');
}
public function setTableDefinition() {
$this->hasColumn('name','string',30);
}
}
class UserReference extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('user_id','integer');
$this->hasColumn('user_id2','integer');
}
}
</code>
++ Inheritance
+++ One table, many classes
When it comes to handling inheritance Doctrine is very smart. In the following example we have one database table called {{entity}}. Users and groups are both entities and they share the same database table. The only thing we have to make is 3 records ({{Entity}}, {{Group}} and {{User}}).
...
...
@@ -20,7 +221,7 @@ class Group extends Entity { }
</code>
++++ One table, one class
+++ One table, one class
In the following example we have three database tables called {{entity}}, {{user}} and {{group}}. Users and groups are both entities. The only thing we have to do is write 3 classes ({{Entity}}, {{Group}} and {{User}}) and make iterative {{setTableDefinition}} method calls.
...
...
@@ -52,7 +253,7 @@ class Group extends Entity {
</code>
++++ Column aggregation
+++ Column aggregation
In the following example we have one database table called {{entity}}. Users and groups are both entities and they share the same database table.
The user object is here an instance of {{User}} while the group object is an instance of {{Group}}.
++ Foreign key constraints
+++ Introduction
A foreign key constraint specifies that the values in a column (or a group of columns) must match the values appearing in some row of another table. In other words foreign key constraints maintain the referential integrity between two related tables.
Say you have the product table with the following definition:
Let's also assume you have a table storing orders of those products. We want to ensure that the order table only contains orders of products that actually exist. So we define a foreign key constraint in the orders table that references the products table:
When exported the class {{Order}} would execute the following SQL:
<code type="sql">
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_id integer REFERENCES products (id),
quantity integer,
INDEX product_id_idx (product_id)
)
</code>
Now it is impossible to create orders with product_no entries that do not appear in the products table.
We say that in this situation the orders table is the referencing table and the products table is the referenced table. Similarly, there are referencing and referenced columns.
+++ Constraint actions
//CASCADE//:
Delete or update the row from the parent table and automatically delete or update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column in the parent table or in the child table.
//SET NULL// :
Delete or update the row from the parent table and set the foreign key column or columns in the child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
//NO ACTION// :
In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary key value is not allowed to proceed if there is a related foreign key value in the referenced table.
//RESTRICT// :
Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as omitting the ON DELETE or ON UPDATE clause.
//SET DEFAULT// :
In the following example we define two classes, User and Phonenumber with their relation being one-to-many. We also add a foreign key constraint with onDelete cascade action.
Binding One-To-One foreign key associations is done with {{Doctrine_Record::ownsOne()}} and {{Doctrine_Record::hasOne()}} methods. In the following example user owns one email and has one address. So the relationship between user and email is one-to-one composite. The relationship between user and address is one-to-one aggregate.
The {{Email}} component here is mapped to {{User}} component's column {{email_id}} hence their relation is called LOCALKEY relation. On the other hand the {{Address}} component is mapped to {{User}} by it's {{user_id}} column hence the relation between {{User}} and {{Address}} is called FOREIGNKEY relation.
A foreign key constraint specifies that the values in a column (or a group of columns) must match the values appearing in some row of another table. In other words foreign key constraints maintain the referential integrity between two related tables.
Say you have the product table with the following definition:
Let's also assume you have a table storing orders of those products. We want to ensure that the order table only contains orders of products that actually exist. So we define a foreign key constraint in the orders table that references the products table:
When exported the class {{Order}} would execute the following SQL:
<code type="sql">
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_id integer REFERENCES products (id),
quantity integer,
INDEX product_id_idx (product_id)
)
</code>
Now it is impossible to create orders with product_no entries that do not appear in the products table.
We say that in this situation the orders table is the referencing table and the products table is the referenced table. Similarly, there are referencing and referenced columns.
++++ Constraint actions
//CASCADE//:
Delete or update the row from the parent table and automatically delete or update the matching rows in the child table. Both ON DELETE CASCADE and ON UPDATE CASCADE are supported. Between two tables, you should not define several ON UPDATE CASCADE clauses that act on the same column in the parent table or in the child table.
//SET NULL// :
Delete or update the row from the parent table and set the foreign key column or columns in the child table to NULL. This is valid only if the foreign key columns do not have the NOT NULL qualifier specified. Both ON DELETE SET NULL and ON UPDATE SET NULL clauses are supported.
//NO ACTION// :
In standard SQL, NO ACTION means no action in the sense that an attempt to delete or update a primary key value is not allowed to proceed if there is a related foreign key value in the referenced table.
//RESTRICT// :
Rejects the delete or update operation for the parent table. NO ACTION and RESTRICT are the same as omitting the ON DELETE or ON UPDATE clause.
//SET DEFAULT// :
In the following example we define two classes, User and Phonenumber with their relation being one-to-many. We also add a foreign key constraint with onDelete cascade action.
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 user is being deleted the groups it belongs to are not being deleted and the associations between this user and the groups it belongs to are being deleted.
Sometimes you may not want that association table rows are being deleted when user / group is being deleted. You can override this behoviour 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}}.
<code type="php">
class User extends Doctrine_Record {
public function setUp() {
$this->hasMany('Group','Groupuser.group_id');
}
public function setTableDefinition() {
$this->hasColumn('name','string',30);
}
}
class Group extends Doctrine_Record {
public function setUp() {
$this->hasMany('User','Groupuser.user_id');
}
public function setTableDefinition() {
$this->hasColumn('name','string',30);
}
}
class Groupuser extends Doctrine_Record {
public function setTableDefinition() {
$this->hasColumn('user_id','integer');
$this->hasColumn('group_id','integer');
}
}
$user = new User();
// add two groups
$user->Group[0]->name = 'First Group';
$user->Group[1]->name = 'Second Group';
// save changes into database
$user->save();
// deleting the associations between user and groups it belongs to
$user->Groupuser->delete();
$groups = new Doctrine_Collection($conn->getTable('Group'));
$groups[0]->name = 'Third Group';
$groups[1]->name = 'Fourth Group';
$user->Group[2] = $groups[0];
// $user will now have 3 groups
$user->Group = $groups;
// $user will now have two groups 'Third Group' and 'Fourth Group'
</code>
++++ Self-referencing
Self-referencing with join tables is done as follows:
<code type="php">
class User extends Doctrine_Record {
public function setUp() {
$this->hasMany('User as Friend','UserReference.user_id-user_id2');