3.7.6 Associations: Linking Models Together

One of the most powerful features of CakePHP is the ability to link relational mapping provided by the model. In CakePHP, the links between models are handled through associations.

Defining relations between different objects in your application should be a natural process. For example: in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have many recipes. Defining the way these relations work allows you to access your data in an intuitive and powerful way.

The purpose of this section is to show you how to plan for, define, and utilize associations between models in CakePHP.

While data can come from a variety of sources, the most common form of storage in web applications is a relational database. Most of what this section covers will be in that context.

3.7.6.1 Relationship Types

The four association types in CakePHP are: hasOne, hasMany, belongsTo, and hasAndBelongsToMany (HABTM).

Relationship Association Type Example
one to one hasOne A user has one profile.
one to many hasMany Users in a system can have multiple recipes.
many to one belongsTo A recipe belongs to a user.
many to many hasAndBelongsToMany Recipes have, and belong to many tags.

Associations are defined by creating a class variable named after the association you are defining. The class variable can sometimes be as simple as a string, but can be as complete as a multidimensional array used to define association specifics.

<?php

class User extends AppModel {
    var $name = 'User';
    var $hasOne = 'Profile';
    var $hasMany = array(
        'Recipe' => array(
            'className'  => 'Recipe',
            'conditions' => 'Recipe.approved = 1',
            'order'      => 'Recipe.created DESC'
        )
    );
}

?>
  1. <?php
  2. class User extends AppModel {
  3. var $name = 'User';
  4. var $hasOne = 'Profile';
  5. var $hasMany = array(
  6. 'Recipe' => array(
  7. 'className' => 'Recipe',
  8. 'conditions' => 'Recipe.approved = 1',
  9. 'order' => 'Recipe.created DESC'
  10. )
  11. );
  12. }
  13. ?>

In the above example, the first instance of the word 'Recipe' is what is termed an 'Alias'. This is an identifier for the relationship and can be anything you choose. Usually, you will choose the same name as the class that it references. However, aliases must be unique both within a single model and on both sides of a belongsTo/hasMany or a belongTo/hasOne relationship. Choosing non-unique names for model aliases can cause unexpected behavior.

3.7.6.2 hasOne

Let’s set up a User model with a hasOne relationship to a Profile model.

First, your database tables need to be keyed correctly. For a hasOne relationship to work, one table has to contain a foreign key that points to a record in the other. In this case the profiles table will contain a field called user_id. The basic pattern is:

hasOne: the other model contains the foreign key.
Relation Schema
Apple hasOne Banana bananas.apple_id
User hasOne Profile profiles.user_id
Doctor hasOne Mentor mentors.doctor_id

The User model file will be saved in /app/models/user.php. To define the ‘User hasOne Profile’ association, add the $hasOne property to the model class. Remember to have a Profile model in /app/models/profile.php, or the association won’t work.

<?php

class User extends AppModel {
    var $name = 'User';                
    var $hasOne = 'Profile';   
}
?>
  1. <?php
  2. class User extends AppModel {
  3. var $name = 'User';
  4. var $hasOne = 'Profile';
  5. }
  6. ?>

There are two ways to describe this relationship in your model files. The simplest method is to set the $hasOne attribute to a string containing the classname of the associated model, as we’ve done above.

If you need more control, you can define your associations using array syntax. For example, you might want to limit the association to include only certain records.

<?php

class User extends AppModel {
    var $name = 'User';          
    var $hasOne = array(
        'Profile' => array(
            'className'    => 'Profile',
            'conditions'   => 'Profile.published = 1',
            'dependent'    => true
        )
    );    
}
?>
  1. <?php
  2. class User extends AppModel {
  3. var $name = 'User';
  4. var $hasOne = array(
  5. 'Profile' => array(
  6. 'className' => 'Profile',
  7. 'conditions' => 'Profile.published = 1',
  8. 'dependent' => true
  9. )
  10. );
  11. }
  12. ?>

Possible keys for hasOne association arrays include:

  • className: the classname of the model being associated to the current model. If you’re defining a ‘User hasOne Profile’ relationship, the className key should equal ‘Profile.’
  • foreignKey: the name of the foreign key found in the other model. This is especially handy if you need to define multiple hasOne relationships. The default value for this key is the underscored, singular name of the current model, suffixed with ‘_id’. In the example above it would default to 'user_id'.
  • conditions: An SQL fragment used to filter related model records. It’s good practice to use model names in SQL fragments: “Profile.approved = 1” is always better than just “approved = 1.”
  • fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
  • dependent: When the dependent key is set to true, and the model’s delete() method is called with the cascade parameter set to true, associated model records are also deleted. In this case we set it true so that deleting a User will also delete her associated Profile.

Once this association has been defined, find operations on the User model will also fetch a related Profile record if it exists:

//Sample results from a $this->User->find() call.

Array
(
    [User] => Array
        (
            [id] => 121
            [name] => Gwoo the Kungwoo
            [created] => 2007-05-01 10:31:01
        )
    [Profile] => Array
        (
            [id] => 12
            [user_id] => 121
            [skill] => Baking Cakes
            [created] => 2007-05-01 10:31:01
        )
)

3.7.6.3 belongsTo

Now that we have Profile data access from the User model, let’s define a belongsTo association in the Profile model in order to get access to related User data. The belongsTo association is a natural complement to the hasOne and hasMany associations: it allows us to see the data from the other direction.

When keying your database tables for a belongsTo relationship, follow this convention:

belongsTo: the current model contains the foreign key.
Relation Schema
Banana belongsTo Apple bananas.apple_id
Profile belongsTo User profiles.user_id
Mentor belongsTo Doctor mentors.doctor_id

If a model(table) contains a foreign key, it belongsTo the other model(table).

We can define the belongsTo association in our Profile model at /app/models/profile.php using the string syntax as follows:

<?php

class Profile extends AppModel {
    var $name = 'Profile';                
    var $belongsTo = 'User';   
}
?>
  1. <?php
  2. class Profile extends AppModel {
  3. var $name = 'Profile';
  4. var $belongsTo = 'User';
  5. }
  6. ?>

We can also define a more specific relationship using array syntax:

<?php

class Profile extends AppModel {
    var $name = 'Profile';                
    var $belongsTo = array(
        'User' => array(
            'className'    => 'User',
            'foreignKey'    => 'user_id'
        )
    );  
}
?>
  1. <?php
  2. class Profile extends AppModel {
  3. var $name = 'Profile';
  4. var $belongsTo = array(
  5. 'User' => array(
  6. 'className' => 'User',
  7. 'foreignKey' => 'user_id'
  8. )
  9. );
  10. }
  11. ?>

Possible keys for belongsTo association arrays include:

  • className: the classname of the model being associated to the current model. If you’re defining a ‘Profile belongsTo User’ relationship, the className key should equal ‘User.’
  • foreignKey: the name of the foreign key found in the current model. This is especially handy if you need to define multiple belongsTo relationships. The default value for this key is the underscored, singular name of the other model, suffixed with ‘_id’.
  • conditions: An SQL fragment used filter related model records. It’s good practice to use model names in SQL fragments: “User.active = 1” is always better than just “active = 1.”
  • fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
  • counterCache: (bool) If set to true the associated Model will automatically increase or decrease the “[singular_model_name]_count” field in the foreign table whenever you do a save() or delete(). The value in the counter field represents the number of related rows.

Once this association has been defined, find operations on the Profile model will also fetch a related User record if it exists:

//Sample results from a $this->Profile->find() call.

Array
(
   [Profile] => Array
        (
            [id] => 12
            [user_id] => 121
            [skill] => Baking Cakes
            [created] => 2007-05-01 10:31:01
        )    
    [User] => Array
        (
            [id] => 121
            [name] => Gwoo the Kungwoo
            [created] => 2007-05-01 10:31:01
        )
)

3.7.6.4 hasMany

Next step: defining a “User hasMany Comment” association. A hasMany association will allow us to fetch a user’s comments when we fetch a User record.

When keying your database tables for a hasMany relationship, follow this convention:

hasMany: the other model contains the foreign key.
Relation Schema
User hasMany Comment Comment.user_id
Cake hasMany Virtue Virtue.cake_id
Product hasMany Option Option.product_id

We can define the hasMany association in our User model at /app/models/user.php using the string syntax as follows:

<?php

class User extends AppModel {
    var $name = 'User';                
    var $hasMany = 'Comment';   
}
?>
  1. <?php
  2. class User extends AppModel {
  3. var $name = 'User';
  4. var $hasMany = 'Comment';
  5. }
  6. ?>

We can also define a more specific relationship using array syntax:

<?php

class User extends AppModel {
    var $name = 'User';                
    var $hasMany = array(
        'Comment' => array(
            'className'     => 'Comment',
            'foreignKey'    => 'user_id',
            'conditions'    => 'Comment.status = 1',
            'order'    => 'Comment.created DESC',
            'limit'        => '5',
            'dependent'=> true
        )
    );  
}
?>
  1. <?php
  2. class User extends AppModel {
  3. var $name = 'User';
  4. var $hasMany = array(
  5. 'Comment' => array(
  6. 'className' => 'Comment',
  7. 'foreignKey' => 'user_id',
  8. 'conditions' => 'Comment.status = 1',
  9. 'order' => 'Comment.created DESC',
  10. 'limit' => '5',
  11. 'dependent'=> true
  12. )
  13. );
  14. }
  15. ?>

Possible keys for hasMany association arrays include:

  • className: the classname of the model being associated to the current model. If you’re defining a ‘User hasMany Comment’ relationship, the className key should equal ‘Comment.’
  • foreignKey: the name of the foreign key found in the other model. This is especially handy if you need to define multiple hasMany relationships. The default value for this key is the underscored, singular name of the other model, suffixed with ‘_id’.
  • conditions: An SQL fragment used filter related model records. It’s good practice to use model names in SQL fragments: “Comment.status = 1” is always better than just “status = 1.”
  • fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
  • order: An SQL fragment that defines the sorting order for the returned associated rows.
  • limit: The maximum number of associated rows you want returned.
  • offset: The number of associated rows to skip over (given the current conditions and order) before fetching and associating.
  • dependent: When dependent is set to true, recursive model deletion is possible. In this example, Comment records will be deleted when their associated User record has been deleted.

    The second parameter of the Model->delete() method must be set to true in order for recursive deletion to occur.

  • finderQuery: A complete SQL query CakePHP can use to fetch associated model records. This should be used in situations that require very custom results.

Once this association has been defined, find operations on the User model will also fetch related Comment records if they exist:

//Sample results from a $this->User->find() call.

Array
(  
    [User] => Array
        (
            [id] => 121
            [name] => Gwoo the Kungwoo
            [created] => 2007-05-01 10:31:01
        )
    [Comment] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [user_id] => 121
                    [title] => On Gwoo the Kungwoo
                    [body] => The Kungwooness is not so Gwooish
                    [created] => 2006-05-01 10:31:01
                )
            [1] => Array
                (
                    [id] => 123
                    [user_id] => 121
                    [title] => More on Gwoo
                    [body] => But what of the ‘Nut?
                    [created] => 2006-05-01 10:41:01
                )
        )
)

One thing to remember is that you’ll need a complimentary Comment belongsTo User association in order to get the data from both directions. What we’ve outlined in this section empowers you to get Comment data from the User. Adding the Comment belongsTo User association in the Comment model empowers you to get User data from the Comment model - completing the connection and allowing the flow of information from either model’s perspective.

3.7.6.5 hasAndBelongsToMany (HABTM)

Ok. Jetzt kannst du dich bereits einen CakePHP Model-Assoziationen-Profi nennen. Du kennst dich bereits sehr gut in den drei Assoziationen, die den Großteil der Objekt-Relationen ausmachen, aus.

Wir wollen uns nun den letzten Relations-Typ in Angriff nehmen: hasAndBelongsToMany oder einfach HABTM. Diese Assoziation wird benutzt, wenn man zwei Models hat die sich auf verschiedene Weise und wiederholt verbinden lassen sollen.

Der größte Unterschied zwischen hasMany und HABTM ist, dass eine Verbindung zwischen Models bei Verwendung der HABTM-Relation nicht exklusiv ist. Ein Beispiel sind Tags. Wenn in meiner Rezeptsammlung die singalesische Kokossuppe das Tag "Singalesisch" trägt heißt das nicht - wie das bei hasMany der Fall wäre - dass die singalesische Knoblauchsuppe dieses Tag nicht besitzen darf.

hasMany-Verbindungen hingegen sind exklusiv. Wenn zu einem Rezept mehrere Kommentare per hasMany gehören, dann gehören diese Kommentare keinem anderen Rezept.

Allerdings braucht es für derartigen Komfort eine neue Tabelle in unserer Datenbank, die die Verbindungen zwischen den Datensätzen beinhaltet. Der Name der neuen Tabelle muss die Namen beider beteiligten Models in alphabetischer Reihenfolge beinhalten. Der Inhalt der Tabelle muss aus mindestens zwei Feldern betehen. Der Inhalt dieser Felder sollten die jeweiligen Schlüssel der betroffenen Datensätze sein.

HABTM: Benötigte eine neue Tabelle, die beide Model-Namen beinhaltet.
Relation Schema
Rezept HABTM Tag rezepte_tags.rezept_id, rezepte_tags.tag_id
Cake HABTM Fan cakes_fans.cake_id, cakes_fans.fan_id
Foo HABTM Bar bars_foos.foo_id, bars_foos.bar_id

Tabellennamen sind konventionsgemäß in alphabetischer Reihenfolge.

Sobald die neue Tabelle erstellt wurde, können wir die HABTM-Assoziation in unserer Model-Datei definieren. Wir werden dieses mal direkt die Array-Syntax verwenden:

<?php

class Rezept extends AppModel {
    var $name = 'Rezept';   
    var $hasAndBelongsToMany = array(
        'Tag' =>
            array('className'            => 'Tag',
                'joinTable'              => 'rezepte_tags',
                'foreignKey'             => 'rezept_id',
                'associationForeignKey'  => 'tag_id',
                'conditions'             => '',
                'order'                  => '',
                'limit'                  => '',
                'unique'                 => true,
                'finderQuery'            => '',
                'deleteQuery'            => '',
                'insertQuery'            => ''
            )
        );             
}
?>
  1. <?php
  2. class Rezept extends AppModel {
  3. var $name = 'Rezept';
  4. var $hasAndBelongsToMany = array(
  5. 'Tag' =>
  6. array('className' => 'Tag',
  7. 'joinTable' => 'rezepte_tags',
  8. 'foreignKey' => 'rezept_id',
  9. 'associationForeignKey' => 'tag_id',
  10. 'conditions' => '',
  11. 'order' => '',
  12. 'limit' => '',
  13. 'unique' => true,
  14. 'finderQuery' => '',
  15. 'deleteQuery' => '',
  16. 'insertQuery' => ''
  17. )
  18. );
  19. }
  20. ?>

Mögliche Schlüssel für eine HABTM-Assoziation:

  • className: Der Name der jeweils anderen Klasse mit der das Model verbunden ist. Wenn du eine ‘Rezept hasAndBelongsToMany Kommentar’-Assoziation planst, sollte der Wert im Rezept-Model Kommentar und im Kommentar-Model Rezept lauten.
  • joinTable: Das ist der Name unserer neuen Tabelle, die für uns alle Verbindungen zwischen den Datensätzen der involvierten Modelle beinhaltet. Dieses Feld ist nötig, falls wir der Tabelle einen Namen gegeben haben, der nicht konventionsgemäß ist.
  • foreignKey: Der Name des Feldes, das den Schlüssel des jeweils anderen Models enthält. Das ist wichtig, wenn wir mehrere HABTM-Relationen definineren wollen. Der Standard-Name sollte singular_name_des_models_id lauten.
  • associationForeignKey: Das Gegenteil des foreignKey. Beachte, dass der foreignKey unter dem Schlüssel Tag im $hasAndBelongsToMany-Array rezept_id und der associationForeignKey tag_id lautet. Was welcher Schlüssel sein muss prägt sich nach ein wenig Übung schnell ein.
  • conditions: Normale SQL-Syntax, um die Query-Ergebnisse für alle find()-Aufrufe zu filtern. Beispielsweise kann hier vorrausgesetzt werden, dass nur Tags gesucht werden, die freigeschalten wurden, was man wiederrum anhand eines zusätzlichen Feldes (z.B. status) festlegen könnte. Es ist dabei immer anzuraten den Feldnamen vollständig mit Modelnamen anzugeben. Also "Tag.status = 1" anstatt nur "status = 1". Sobald beide Models ein status-Feld haben weiß MySQL dann nicht mehr welches gemeint ist.
  • fields: Hier kann das selbe mit den Tabellen-Feldern gemacht werden. Beides gilt übrigens immer für die Ergebnisse des verbundenen Models (im obigen Beispiel also Tag).
  • order: Angaben zur Sortierung der Ergebnisse. z.B. id DESC.
  • limit: Die Höchstanzahl der zurückgegebenen Datensätze.
  • unique: If true (default value) cake will first delete existing relationship records in the foreign keys table before inserting new ones, when updating a record. So existing associations need to be passed again when updating.
  • offset: Die andere Hälfte des LIMIT-statements von MySQL (siehe limit). Soviele Datensätze werden übersprungen bevor welche zurückgegeben werden..
  • finderQuery, deleteQuery, insertQuery: Hier können für alle Belange, die durch die vorherigen Optionen nicht zu erreichen sind, eigene SQL-Statements zum finden, löschen und einfügen von Datensätzen genutzt werden.

Wenn diese Assoziation einmal fertig ist, werden find()-Aufrufe im Rezepte-Model immer auch die verbundenen Tag-Datensätze finden (sofern vorhanden und die Rekursionstiefe beim find()-Aufruf nicht zu niedrig ist):

//Beispiel-Ergebnisse eines $this->Rezept->find()-Aufrufs.

Array
(  
    [Rezept] => Array
        (
            [id] => 2745
            [name] => Erbsensuppe mit Bockwurst
            [created] => 2007-05-01 10:31:01
            [user_id] => 2346
        )
    [Tag] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [name] => Mittagessen
                )
           [1] => Array
                (
                    [id] => 124
                    [name] => Hauptgericht
                )
           [2] => Array
                (
                    [id] => 125
                    [name] => günstig
                )
        )
)

Wenn du das ganze auch umgedreht im Tag-Model machen willst darfst du nicht vergessen auch dort eine solche HABTM-Assoziation definieren.

Man kann genauso eigene find-queries nach Maßgaben der HABTM-Relation ausführen. Lasst uns einen Blick auf die folgenden Beispiele werfen:

Gehen wir von der selben Struktur wie im obigen Beispiel aus (Rezept HABTM Tag). Jetzt wollen wir beispielsweise alle Rezepte, die das Tag "günstig" besitzen. Ein möglicher (aber leider falscher) Weg, um das zu erreichen wäre diese Bedingung im Model selbst einzusetzen:

$this->Rezept->bindModel(array(
    'hasAndBelongsToMany' => array(
        'Tag' => array('conditions'=>array('Tag.name'=>'günstig'))
)));
$this->Rezept->find('all');
  1. $this->Rezept->bindModel(array(
  2. 'hasAndBelongsToMany' => array(
  3. 'Tag' => array('conditions'=>array('Tag.name'=>'günstig'))
  4. )));
  5. $this->Rezept->find('all');
//Zurückgegebene Daten
Array
(  
    0 => Array
        {
        [Rezept] => Array
            (
                [id] => 2745
                [name] => Erbsensuppe mit Bockwurst
                [created] => 2007-05-01 10:31:01
                [user_id] => 2346
            )
        [Tag] => Array
            (
               [0] => Array
                    (
                        [id] => 125
                        [name] => günstig
                    )
            )
    )
    1 => Array
        {
        [Rezept] => Array
            (
                [id] => 2745
                [name] => Flußkrebse mit Trüffeln
                [created] => 2008-05-01 10:31:01
                [user_id] => 2349
            )
        [Tag] => Array
            (
            }
        }
}

Wie du siehst gibt dieses Beispiel ALLE Rezepte zurück und beschränkt sich dann bei den Tags auf die Tags (unter den mit dem Datensatz verbundenen), die den Namen "günstig" tragen. Deswegen werden hier neben der Erbsensuppe auch die Flußkrebse gefunden. Da die Flußkrebse aber unter allen Tags, die mit zu ihnen gehören mögen aber keinen haben, dessen Name "günstig" ist, ist der Array mit den HABTM-Ergebnissen leer. Um unser Ziel tatsächlich zu erreichen gibt es eine Menge Möglichkeiten, alle basieren darauf eine temporäre Verbindung zur Join-Tabelle und/oder zum Tag-Model herzustellen.

$this->Rezept->bindModel(array('hasOne' => array('RezepteTag')));
$this->Rezept->find('all', array(
        'fields' => array('Rezept.*'),
        'conditions'=>array('RecipesTag.tag_id'=>125) // id von günstig
));
  1. $this->Rezept->bindModel(array('hasOne' => array('RezepteTag')));
  2. $this->Rezept->find('all', array(
  3. 'fields' => array('Rezept.*'),
  4. 'conditions'=>array('RecipesTag.tag_id'=>125) // id von günstig
  5. ));

Genauso könnte man eine exotische Assoziation mit dem Zweck so viele Verknüpfungen wie gebraucht werden um das Filtern zu ermöglichen zu erstellen, zum Beispiel:

$this->Rezept->bindModel(array(
    'hasOne' => array(
        'RezepteTag',
        'FilterTag' => array(
            'className' => 'Tag',
            'foreignKey' => false,
            'conditions' => array('FilterTag.id = RezepteTag.id')
)));
$this->Rezept->find('all', array(
        'fields' => array('Rezept.*'),
        'conditions'=>array('FilterTag.name'=>'günstig')
));
  1. $this->Rezept->bindModel(array(
  2. 'hasOne' => array(
  3. 'RezepteTag',
  4. 'FilterTag' => array(
  5. 'className' => 'Tag',
  6. 'foreignKey' => false,
  7. 'conditions' => array('FilterTag.id = RezepteTag.id')
  8. )));
  9. $this->Rezept->find('all', array(
  10. 'fields' => array('Rezept.*'),
  11. 'conditions'=>array('FilterTag.name'=>'günstig')
  12. ));

Beide Methoden geben die folgenden Daten zurück:

//Zurückgegebene Daten
Array
(  
    0 => Array
        {
        [Rezept] => Array
            (
                [id] => 2745
                [name] => Erbsensupper mit Bockwurst
                [created] => 2007-05-01 10:31:01
                [user_id] => 2346
            )
    [Tag] => Array
        (
            [0] => Array
                (
                    [id] => 123
                    [name] => Breakfast
                )
           [1] => Array
                (
                    [id] => 124
                    [name] => Dessert
                )
           [2] => Array
                (
                    [id] => 125
                    [name] => günstig
                )
        )
}

Für weitere Informationen zum Thema Model-Assoziationen on-the-fly hier klicken: Erstellen und Beenden von Assoziationen on-the-fly

Um dein spezifisches Ziel zu erreichen solltet du die verschiedenen Techniken miteinander kombinieren.

3.7.6.6 Creating and Destroying Associations on the Fly

Sometimes it becomes necessary to create and destroy model associations on the fly. This may be for any number of reasons:

  • You want to reduce the amount of associated data fetched, but all your associations are on the first level of recursion.
  • You want to change the way an association is defined in order to sort or filter associated data.

This association creation and destruction is done using the CakePHP model bindModel() and unbindModel() methods. (There is also a very helpful behavior called "Containable", please refer to manual section about Built-in behaviors for more information). Let's set up a few models so we can see how bindModel() and unbindModel() work. We'll start with two models:

<?php

class Leader extends AppModel {
    var $name = 'Leader';
 
    var $hasMany = array(
        'Follower' => array(
            'className' => 'Follower',
            'order'     => 'Follower.rank'
        )
    );
}

?>

<?php

class Follower extends AppModel {
    var $name = 'Follower';
}

?>
  1. <?php
  2. class Leader extends AppModel {
  3. var $name = 'Leader';
  4. var $hasMany = array(
  5. 'Follower' => array(
  6. 'className' => 'Follower',
  7. 'order' => 'Follower.rank'
  8. )
  9. );
  10. }
  11. ?>
  12.  
  13. <?php
  14. class Follower extends AppModel {
  15. var $name = 'Follower';
  16. }
  17. ?>

Now, in the LeadersController, we can use the find() method in the Leader model to fetch a Leader and its associated followers. As you can see above, the association array in the Leader model defines a "Leader hasMany Followers" relationship. For demonstration purposes, let's use unbindModel() to remove that association in a controller action.

function someAction() {
    // This fetches Leaders, and their associated Followers
    $this->Leader->findAll();
  
    // Let's remove the hasMany...
    $this->Leader->unbindModel(
        array('hasMany' => array('Follower'))
    );
  
    // Now a using a find function will return 
    // Leaders, with no Followers
    $this->Leader->findAll();
  
    // NOTE: unbindModel only affects the very next 
    // find function. An additional find call will use 
    // the configured association information.
  
    // We've already used findAll() after unbindModel(), 
    // so this will fetch Leaders with associated 
    // Followers once again...
    $this->Leader->findAll();
}
  1. function someAction() {
  2. // This fetches Leaders, and their associated Followers
  3. $this->Leader->findAll();
  4. // Let's remove the hasMany...
  5. $this->Leader->unbindModel(
  6. array('hasMany' => array('Follower'))
  7. );
  8. // Now a using a find function will return
  9. // Leaders, with no Followers
  10. $this->Leader->findAll();
  11. // NOTE: unbindModel only affects the very next
  12. // find function. An additional find call will use
  13. // the configured association information.
  14. // We've already used findAll() after unbindModel(),
  15. // so this will fetch Leaders with associated
  16. // Followers once again...
  17. $this->Leader->findAll();
  18. }

Removing or adding associations using bind- and unbindModel() only works for the next model operation only unless the second parameter has been set to false. If the second parameter has been set to false, the bind remains in place for the remainder of the request.

Here’s the basic usage pattern for unbindModel():

$this->Model->unbindModel(
    array('associationType' => array('associatedModelClassName'))
);
  1. $this->Model->unbindModel(
  2. array('associationType' => array('associatedModelClassName'))
  3. );

Now that we've successfully removed an association on the fly, let's add one. Our as-of-yet unprincipled Leader needs some associated Principles. The model file for our Principle model is bare, except for the var $name statement. Let's associate some Principles to our Leader on the fly (but remember–only for just the following find operation). This function appears in the LeadersController:

function anotherAction() {
    // There is no Leader hasMany Principles in 
    // the leader.php model file, so a find here, 
    // only fetches Leaders.
    $this->Leader->findAll();
 
    // Let's use bindModel() to add a new association 
    // to the Leader model:
    $this->Leader->bindModel(
        array('hasMany' => array(
                'Principle' => array(
                    'className' => 'Principle'
                )
            )
        )
    );
 
    // Now that we're associated correctly, 
    // we can use a single find function to fetch 
    // Leaders with their associated principles:
    $this->Leader->findAll();
}
  1. function anotherAction() {
  2. // There is no Leader hasMany Principles in
  3. // the leader.php model file, so a find here,
  4. // only fetches Leaders.
  5. $this->Leader->findAll();
  6. // Let's use bindModel() to add a new association
  7. // to the Leader model:
  8. $this->Leader->bindModel(
  9. array('hasMany' => array(
  10. 'Principle' => array(
  11. 'className' => 'Principle'
  12. )
  13. )
  14. )
  15. );
  16. // Now that we're associated correctly,
  17. // we can use a single find function to fetch
  18. // Leaders with their associated principles:
  19. $this->Leader->findAll();
  20. }

There you have it. The basic usage for bindModel() is the encapsulation of a normal association array inside an array whose key is named after the type of association you are trying to create:

$this->Model->bindModel(
        array('associationName' => array(
                'associatedModelClassName' => array(
                    // normal association keys go here...
                )
            )
        )
    );
  1. $this->Model->bindModel(
  2. array('associationName' => array(
  3. 'associatedModelClassName' => array(
  4. // normal association keys go here...
  5. )
  6. )
  7. )
  8. );

Even though the newly bound model doesn't need any sort of association definition in it’s model file, it will still need to be correctly keyed in order for the new association to work properly.