How To Save Associated HABTM Models in CakePHP
A common problem programmers have with CakePHP is saving related models with a many-to-many relationship, something Cake calls “hasAndBelongsToMany” or HABTM. One of the things that makes saving HABTM models so challenging is that when you go to save your models, Cake will act like everything saved fine when in fact only one of the models saved, or in some cases, none of them saved.
To illustrate how to overcome this common problem, let’s say you’ve just read Dr. Seuss’ Butter Battle Book. In the book, the Yooks eat their bread butter side up, and the Zooks eat it butter side down, and they’re always fighting about it. You decide to write a social network app to try to bring these two groups together.
Of course, as with any respectable social network, you’ll need a way to allow Zooks and Yooks to become friends. Since a Zook can have many Yook friends, and a Yook can have many Zook friends, This will require three models in a HABTM relatonship: Yooks, Zooks, and Yooks_Zooks to join the two models together (CakePHP orders the model names alphabetically, so Zooks_Yooks would not work).
Since Zooks and Yooks still don’t see eye-to-eye on the whole butter side orientation thing, you decide it will be necessary to write separate controllers for Zooks and Yooks. You start by creating the controller for Yooks.
In your first action, you want a Yook to be able to invite a Zook to the new social networking site. This will require creating a new account for the Zook on the fly, as well as linking it to the Yook’s account so the site knows they are friends.
$this->Zook->create();
$this->Zook->ZooksYook->create();
$this->data['ZooksYook']['Yook_id'] = $this->Auth->User('id');
if ($this->Zook->save($this->data)) {
$this->data['ZooksYook']['Zook_id'] = $this->Zook->id;
$this->Zook->ZooksYook->save($this->data);
...
}
Looking at it line-by-line, the first thing we do is initialize a new Zook. Next, we have to initialize a new ZooksYook, which as you recall is the model linking Zooks and Yooks. In our ZooksYook model, we only have three fields (you could have more, but for simplicity let’s stick with three): id, zook_id, and yook_id. Since this controller is for the Yook, his user id will go into the yook_id field of the new row we want Cake to create for us in the database. That is easy enough.
But before we can put anything in the zook_id field, we have to know what the new Zook’s id is. He doesn’t exist in the database yet and has no id, so we have to save the Zook. Upon a successful save, we then retrieve the Zook’s id and set the zook_id field in the ZooksYook model equal to it. Finally, we save the ZooksYook model.
The above is a unique use case for HABTM, and it is certainly not always necessary to do it the way I just showed you. Other ways of accomplishing the same thing are out there, but they didn’t neatly fit into my existing code so I experimented until I got something different that worked. Enjoy!