Delegate Behavior
The delegate
behavior allows a model to delegate methods to one of its relationships. This helps to isolate logic in a dedicated model, or to simulate class table inheritance.
Basic Usage
In the schema.xml
, use the <behavior>
tag to add the delegate
behavior to a table. In the <parameter>
tag, specify the table that the current table delegates to as the to
parameter:
<table name="account">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="login" type="VARCHAR" required="true" />
<column name="password" type="VARCHAR" required="true" />
<behavior name="delegate">
<parameter name="to" value="profile" />
</behavior>
</table>
<table name="profile">
<column name="email" type="VARCHAR" />
<column name="telephone" type="VARCHAR" />
</table>
Rebuild your model, insert the table creation sql again, and you're ready to go. The delegate profile
table is now related to the account
table using a one-to-one relationship. That means that the behavior creates a foreign primary key in the profile
table. In fact, everything happens as if you had defined the following schema:
<table name="account">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="login" type="VARCHAR" required="true" />
<column name="password" type="VARCHAR" required="true" />
</table>
<table name="profile">
<column name="id" required="true" primaryKey="true" type="INTEGER" />
<column name="email" type="VARCHAR" />
<column name="telephone" type="VARCHAR" />
<foreign-key foreignTable="account" onDelete="setnull" onUpdate="cascade">
<reference local="id" foreign="id" />
</foreign-key>
</table>
Tip
If the delegate table already has a foreign key to the main table, the behavior doesn't recreate it. It allows you to have full control over the relationship between the two tables.
In addition, the ActiveRecord Account
class now provides integrated delegation capabilities. That means that it offers to handle directly the columns of the Profile
model, while in reality it finds or create a related Profile
object and calls the methods on this delegate:
<?php
$account = new Account();
$account->setLogin('francois');
$account->setPassword('S€cr3t');
// Fill the profile via delegation
$account->setEmail('[email protected]');
$account->setTelephone('202-555-9355');
// same as
$profile = new Profile();
$profile->setEmail('[email protected]');
$profile->setTelephone('202-555-9355');
$account->setProfile($profile);
// save the account and its profile
$account->save();
// retrieve delegated data directly from the main object
echo $account->getEmail(); // [email protected]
Getter and setter methods for delegate columns don't exist on the main object ; the delegation is handled by the magical __call()
method. Therefore, the delegation also works for custom methods in the delegate table.
<?php
class Profile extends BaseProfile
{
public function setFakeEmail()
{
$n = rand(10e16, 10e20);
$fakeEmail = base_convert($n, 10, 36) . '@example.com';
$this->setEmail($fakeEmail);
}
}
$account = new Account();
$account->setFakeEmail(); // delegates to Profile::setFakeEmail()
Delegating Using a Many-To-One Relationship
Instead of adding a one-to-one relationship, the delegate
behavior can take advantage of an existing many-to-one relationship. For instance:
<table name="player">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="first_name" type="VARCHAR" />
<column name="last_name" type="VARCHAR" />
</table>
<table name="basketballer">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="points" type="INTEGER" />
<column name="field_goals" type="INTEGER" />
<column name="three_points_field_goals" type="INTEGER" />
<column name="player_id" type="INTEGER" />
<foreign-key foreignTable="player">
<reference local="player_id" foreign="id" />
</foreign-key>
<behavior name="delegate">
<parameter name="to" value="player" />
</behavior>
</table>
In that case, the behavior doesn't modify the foreign keys, it just proxies method called on Basketballer
to the related Player
, or creates one if it doesn't exist:
<?php
$basketballer = new Basketballer();
$basketballer->setPoints(101);
$basketballer->setFieldGoals(47);
$basketballer->setThreePointsFieldGoals(7);
// set player identity via delegation
$basketballer->setFirstName('Michael');
$basketballer->setLastName('Giordano');
// same as
$player = new Player();
$player->setFirstName('Michael');
$player->setLastName('Giordano');
$basketballer->setPlayer($player);
// save basketballer and player
$basketballer->save();
// retrieve delegated data directly from the main object
echo $basketballer->getFirstName(); // Michael
And since several models can delegate to the same player object, that means that a single player can have both basketball and soccer stats!
Tip
In this example, table delegation is used to implement Class Table Inheritance. See how Propel implements this inheritance type, and others, in the inheritance chapter.
Delegating To Several Tables
Delegation allows to delegate to several tables. Just separate the name of the delegate tables by commas in the to
parameter of the delegate
behavior tag in your schema to delegate to several tables:
<table name="account">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="login" type="VARCHAR" required="true" />
<column name="password" type="VARCHAR" required="true" />
<behavior name="delegate">
<parameter name="to" value="profile, preference" />
</behavior>
</table>
<table name="profile">
<column name="email" type="VARCHAR" />
<column name="telephone" type="VARCHAR" />
</table>
<table name="preference">
<column name="preferred_color" type="VARCHAR" />
<column name="max_size" type="INTEGER" />
</table>
Now the Account
class has two delegates, that can be addressed seamlessly:
<?php
$account = new Account();
$account->setLogin('francois');
$account->setPassword('S€cr3t');
// Fill the profile via delegation
$account->setEmail('[email protected]');
$account->setTelephone('202-555-9355');
// Fill the preference via delegation
$account->setPreferredColor('orange');
$account->setMaxSize('200');
// save the account and its profile and its preference
$account->save();
On the other hand, it is not possible to cascade delegation to yet another model. So even if the profile
table delegates to another detail
table, the methods of the Detail
model won't be accessibe to the Profile
objects.
Parameters
The delegate
behavior takes only one parameter, the list of delegate tables:
<table name="account">
<column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" />
<column name="login" type="VARCHAR" required="true" />
<column name="password" type="VARCHAR" required="true" />
<behavior name="delegate">
<parameter name="to" value="profile, preference" />
</behavior>
</table>
Note that the delegate tables must exist, but they don't need to share a relationship with the main table (in which case the behavior creates a one-to-one relationship).
Found a typo ? Something is wrong in this documentation ? Just fork and edit it !