When working with associated models, it is important to realize that saving model data should always be done by the corresponding CakePHP model. If you are saving a new Post and its associated Comments, then you would use both Post and Comment models during the save operation.

If neither of the associated model records exists in the system yet (for example, you want to save a new User and their related Profile records at the same time), you'll need to first save the primary, or parent model.

To get an idea of how this works, let's imagine that we have an action in our UsersController that handles the saving of a new User and a related Profile. The example action shown below will assume that you've POSTed enough data (using the FormHelper) to create a single User and a single Profile.

<?php
function add() {
	if (!empty($this->data)) {
		// We can save the User data:
		// it should be in $this->data['User']
 
		$this->User->save($this->data);
 
		// Now we add this information to the save data
		// and save the Profile.
 
		// The ID of the newly created user has been set
		// as $this->User->id.
		$this->data['Profile']['user_id'] = $this->User->id;
 
		// Because our User hasOne Profile, we can access
		// the Profile model through the User model:
		$this->User->Profile->save($this->data);
	}
}
?>
  1. <?php
  2. function add() {
  3. if (!empty($this->data)) {
  4. // We can save the User data:
  5. // it should be in $this->data['User']
  6. $this->User->save($this->data);
  7. // Now we add this information to the save data
  8. // and save the Profile.
  9. // The ID of the newly created user has been set
  10. // as $this->User->id.
  11. $this->data['Profile']['user_id'] = $this->User->id;
  12. // Because our User hasOne Profile, we can access
  13. // the Profile model through the User model:
  14. $this->User->Profile->save($this->data);
  15. }
  16. }
  17. ?>

As a rule, when working with hasOne, hasMany, and belongsTo associations, its all about keying. The basic idea is to get the key from one model and place it in the foreign key field on the other. Sometimes this might involve using the $id attribute of the model class after a save(), but other times it might just involve gathering the ID from a hidden input on a form that’s just been POSTed to a controller action.

To supplement the basic approach used above, CakePHP also offers a very handy method saveAll(), which allows you to validate and save multiple models in one shot. In addtion, saveAll() provides transactional support to ensure data integrity in your database (i.e. if one model fails to save, the other models will not be saved either).

For transactions to work correctly in MySQL your tables must use InnoDB engine. Remember that MyISAM tables do not support transactions.

Let's see how we can use saveAll() to save Company and Account models at the same time.

First, you need to build your form for both Company and Account models (we'll assume that Company hasMany Account).


echo $form->create(Company, array('action'=>'add'));
echo $form->input('Company.name', array('label'=>'Company name'));
echo $form->input('Company.description');
echo $form->input('Company.location');

echo $form->input('Account.0.name', array('label'=>'Account name'));
echo $form->input('Account.0.username');
echo $form->input('Account.0.email');

echo $form->end('Add');

  1. echo $form->create(Company, array('action'=>'add'));
  2. echo $form->input('Company.name', array('label'=>'Company name'));
  3. echo $form->input('Company.description');
  4. echo $form->input('Company.location');
  5. echo $form->input('Account.0.name', array('label'=>'Account name'));
  6. echo $form->input('Account.0.username');
  7. echo $form->input('Account.0.email');
  8. echo $form->end('Add');

Take a look at the way we named the form fields for the Account model. If Company is our main model saveAll() will expect the related model's (Account) data to arrive in a specific format. And having Account.0.fieldName is exactly what we need.

The above field naming is required for a hasMany association. If the association between the models is hasOne, you have to use ModelName.fieldName notation for the associated model.

Now, in our companies_controller we can create an add() action:


function add() {
   if(!empty($this->data)) {
      $this->Company->saveAll($this->data, array('validate'=>'first'));
   }
}

  1. function add() {
  2. if(!empty($this->data)) {
  3. $this->Company->saveAll($this->data, array('validate'=>'first'));
  4. }
  5. }

That's all there is to it. Now our Company and Account models will be validated and saved all at the same time. A quick thing to point out here, is the use of array('validate'=>'first'), this option will ensure that both of our models are validated.

3.7.4.1.1 counterCache - Cache your count()

This function helps you caching the count of related data. Instead of counting the records manually via find('count'), the model itself tracks any addition/deleting towards the associated $hasMany model and increases/decreases a dedicated integer field within the parent model table.

The name of the field consists of the singular model name followed by a underscore and the word "count".

Let's say you have a model called "ImageAlbum" and a model called "Image", you would add an INT-field to the "image_album" table and name it "image_count". Or if your names are more complex, here is another example: With "BlogEntry" and "BlogEntryComment", the name of the field would be "blog_entry_comment_count" and needs to be added to "blog_entries".

Once you have added the counter field you are good to activate this functionality by adding the "counterCache" key to the "$belongsTo" association array and set the value to "true".

class ImageAlbum extends AppModel {
    var $hasMany = array(
        'Image'
    );
}

class Image extends AppModel {
    var $belongsTo = array(
        'ImageAlbum' => array('counterCache' => true)
    );
}
  1. class ImageAlbum extends AppModel {
  2. var $hasMany = array(
  3. 'Image'
  4. );
  5. }
  6. class Image extends AppModel {
  7. var $belongsTo = array(
  8. 'ImageAlbum' => array('counterCache' => true)
  9. );
  10. }

Now every time you add a new "Image" to "ImageAlbum" the number within "image_count" will increase (or decrease if you do a delete).