Saving HABTM with extra fields?

HABTM is over-sold. A lot of the times it fails to meet the needs, such as when you have additional data to store. You'll be better off to do a hasMany/belongsTo relationship between the models.

Taken from the CakePHP Book:

What to do when HABTM becomes complicated?

By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.

Also note that if you want to add more fields to the join (when it was created or meta information) this is possible with HABTM join tables, but it is important to understand that you have an easy option.

HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association.

In your case I would suggest making a LineItem model and joining everything that way:

  • Order hasMany LineItem
  • LineItem belongsTo Order, Product

Well HATBM works in an very strange way. If you really need HATBM I would suggest NOT changing a model association, but using an interesting shorthand for saving additional data fields in the join table.

I've encountered this a lot of times and this is the best solution: Actually, just for saving your additional fields you can unbind all HATBM relations and add a new hasMany binding "on the fly" pointing to the join model. Firstly you should unbind the existing HATBM relation.

$this->Order->unbindModel(array('hasAndBelongsToMany' => array('Product')));

Then add a new "on-the-fly" hasMany binding:

$this->Order->bindModel(array('hasMany' => array('OrdersPost')));

Then if your data is:

$this->data['Order']['id'] = '1';
$this->data['OrdersPost'][0]['product_id'] = '15';
$this->data['PostsTag'][0]['price'] = '5000.00';
$this->data['PostsTag'][0]['quantity'] = '1';
$this->data['OrdersPost'][1]['product_id'] = '16';
$this->data['PostsTag'][1]['price'] = '4000.00';
$this->data['PostsTag'][1]['quantity'] = '2';

As mentioned above, HATBM always erases all existing records before inserting new ones. So maybe you should take care about that before you do:

$this->Order->saveAll($this->data);

This works when you dont want to change your datamodel, but still need additional functionality. Hope this helps. Original Solution and Credits HERE


Another problem I usually encounter with using SaveAll is that it doesn't save the related records. In your example, the Order is saved but the OrderProducts(or OrderItems) are not saved. What I usually do is something like this:

if ($this->Order->save($this->data)) {
    for($i=0; $i<sizeof($this->data['OrderProduct']); $i++){
        $this->data['OrderProduct'][$i]['order_id'] = $this->Order->id;
    }
    $this->Order->OrderProduct->saveAll($this->data['OrderProduct']);
}

What happens here is that the Order is saved first, then its ID is copied to each OrderProduct. Then OrderProduct records are saved.