Editing hasAndBelongsToMany (HABTM) Relationships in CakePHP
I had a difficult time wrapping my head around how to do this. The manual is not much help. I did get a few clues from a couple of Wiki articles, but in the end I had to piece all this together with stuff I dug out of the CakePHP Google Group.
Let’s assume we have two tables, colors and products. Our model states that products hasAndBelongsToMany colors. Also, colors hasAndBelongsToMany products.
Add the following code to app_controller.php:
function getSelectedItems($data)
{
$return = array();
foreach ($data as $row)
{
$return[$row['id']] = $row['name'];
}
return $return;
}
Our edit function in the products_controller.php will look something like this:
function admin_edit($id = null)
{
if (empty($this->data))
{
$this->set('colors', $this->requestAction('/colors/select'));
// get data for colors DropDownList
$this->Product->id = $id;
$this->data = $this->Product->read();
$this->set('selected_colors', $this->getSelectedItems($this->data['Color']));
}
else
{
if ($this->Product->save($this->data))
{
$this->flash(”Your product has been updated.”, ‘/admin/products’);
}
else
{
$this->flash(”Unable to update your product.”, ‘/admin/products’);
}
}
}
And finally, the code for the multi-select:
=$html->selectTag(”Color/Color”, $colors, $selected_colors, array(’multiple’ => ‘multiple’))?>
If the associations are set up correctly, Cake will automagically save the updated colors when $this->Product->save($this->data) is called in the products controller. I hope this is helpful to someone, I spent a lot of time digging it up!
[...] shepherdweb.com » Editing hasAndBelongsToMany (HABTM) Relationships in CakePHP » Shane Shepherd: web design and development; music A quick howto on editable HABTM relationships. (tags: cakephp habtm) [...]
Pingback on August 22, 2006 @ 11:18 pm
Thanks for this, Shane. I found it to be very useful. However, it seems more appropriate to put the getSelectedItems() function in the model.
Comment on September 11, 2006 @ 2:10 pm
@Joel - The findSelectedItems() function is not specific to any particular model; I placed it in the app_controller for this reason. It might be more appropriate to create a component for this function…that seemed like overkill to me though.
Comment on September 11, 2006 @ 4:26 pm
good info, but what is returned by “/colors/select” ?
Comment on September 12, 2006 @ 9:11 am
This is the code for “/colors/select”:
function select() { return $this->Color->generateList(); }Comment on September 13, 2006 @ 6:45 pm
@Shane - The findSelectedItems() function is not specific to any particular model; I placed it in the app_controller for this reason.
So go for putting it into app_model!
Can you show the working example of your code? I’ve read the source of model_php5.php / save function and found it barely empty in what appears to be HABTM saving (I’m using Cake 1.1.8.xxxx).
Comment on October 3, 2006 @ 11:41 am
Thanks! This worked brilliantly.
By the way I made a slight mod. I was getting notices about index ‘name’ not being being defined, so I changed the function to this:
function getSelectedItems( $data, $nameField = ‘name’){
$return = array();
foreach ($data as $row){
$return[$row['id']] = $row[ $nameField ];
}
return $return;
}
When I call it, I can set the ‘name’ field to be whatever is appropriate for the table I am using, so that I don’t get undefined index notices.
Comment on November 29, 2006 @ 11:41 am
@Aran - Very nice, thanks for sharing. The addition of that parameter makes getSelectedItems() a lot more versatile.
Comment on November 29, 2006 @ 5:22 pm
I could use some help. I’ve tweaked this code for my data and this line:
$this->set(’computers’, $this->requestAction(’/computers/select’));
is causing this error:
Notice: Undefined property: AppController::$Control in /Library/WebServer/Documents/serials/cake/app_controller.php on line 45
Fatal error: Call to a member function generateList() on a non-object in /Library/WebServer/Documents/serials/cake/app_controller.php on line 45
What am I doing wrong?
Comment on March 21, 2007 @ 5:49 pm
Fixed one problem and caused another. I was missing
function select()
{
return $this->Computer->generateList();
}
from my computers_controller.
Now on save I get:
Notice: Undefined property: SerialsController::$Serials in /Library/WebServer/Documents/serials/app/controllers/serials_controller.php on line 403
Notice: Trying to get property of non-object in /Library/WebServer/Documents/serials/app/controllers/serials_controller.php on line 403
Fatal error: Call to a member function save() on a non-object in /Library/WebServer/Documents/serials/app/controllers/serials_controller.php on line 403
Any advice?
Comment on March 21, 2007 @ 6:01 pm
Sorry for the spam - I had a typo. Still not working, but now no errors. Looks like it’s not reading from or writing to my join table computers_serials. Argh!
Comment on March 21, 2007 @ 6:11 pm
It’s reading from the join table - I can see the array with the correct info when I print_r($selected_computers); in the view. But it’s not auto-setting selected in the form element and it’s not updating the db on save. here’s my selecttag:
$html->selectTag(’Computer/Computer’,$computers,$selected_computers,array(’multiple’ => ‘multiple’))
Comment on March 21, 2007 @ 6:29 pm
@Steve - I’m not sure I have enough information about the problem you’re having to venture a guess. Have you tried searching the Cake Google Group for answers?
Comment on March 21, 2007 @ 7:24 pm
My problem has been resolved, thanks to this site for providing a very detailed tutorial even I could follow. However, this didn’t get me all the way to my solution.
I had to apply the “array_keys()” function to filter out the names from my selected list. If you’ve implemented the solution above, and your select list still won’t highlight it’s default links selectTag is probably getting an id/name pair where it’s only expecting ids.
$html->selectTag(’Computer/Computer’,$computers,array_keys($selected_computers),array(’multiple’ => ‘multiple’));
A final problem I ran into (and this was a ball-buster) is described in this post (secret 1).
http://groups.google.com/group/cake-php/browse_thread/thread/b8816db7ff5854bc
My form would submit without error but never bother to update my join table with the new data. According to the link above, you can’t have your HABTM multi-select as the top element in your form, or you have to re-order the array before saving in your controller so that the results array starts with the controller-specific data and then finishes with the HATBM data. This is a bug in cakePHP & should be fixed.
Hope this helps somebody - sure would’ve saved me some time if I had found it earlier.
Steve
Comment on March 22, 2007 @ 9:13 am
@Steve - Thanks for sharing that — That is a weird bug. Would you mind posting which version of Cakephp you found this bug in for future readers? Also, it would be useful to the CakePHP development team if you would submit a bug report.
Comment on March 22, 2007 @ 9:38 am
I’m using CakePHP v1.1.13.4450 (current stable release). There’s already a ticket for this bug, https://trac.cakephp.org/ticket/1819. This tutorial was a lifesaver. Here’s the thread from the cake group where I was trying to get help. Apparently the tutorial on this page should be unnecessary because it’s all in the documentation. Who knew?
http://groups.google.com/group/cake-php/browse_thread/thread/52acdb41921e7732
Comment on March 22, 2007 @ 10:19 am
Hmmm. Documentation must have been expanded. I wrote this post because I had to do so much digging in order to figure out how to do this. Of course, it’s been almost a year since I wrote this.
Comment on March 22, 2007 @ 12:38 pm
Shane, don’t count on it - believe me I spent hours searching. Despite what some of the experts in the google group say, there’s nothing in the documentation that’s remotely as helpful as the info on this page.
Comment on March 22, 2007 @ 12:56 pm
@Steve - You are too kind.
Comment on March 22, 2007 @ 4:01 pm
ran into the same problems as Steve. The solutions here cover saving the array and populating the default items but an unsuccessful save still lost the items in my case. The handling of these HABTM default selections seems extremely inelegant.. I sincerely hope future versions will make this a little more straightforward.
My fix was the following (replace Blogentry with post and keyword with tag as appropriate):
function edit($id = null) {
$this->set(”keywords”, $this->Blogentry->Keyword->generateList(null, “keyword ASC”, null, ‘{n}.Keyword.id’, ‘{n}.Keyword.keyword’);
if (empty($this->data)) {
$this->Blogentry->id = $id;
$this->data = $this->Blogentry->read();
$this->set(’selected_keywords’, array_keys($this->getSelectedItems($this->data['Keyword'], ‘id’, ‘keyword’)));
} else {
if ($this->Blogentry->save($this->data)) {
$this->flash(’Your post has been updated.’,'/blogentries’);
} else {
$this->set(’selected_keywords’, $this->data['Keyword']['Keyword']);
$this->set(’errorMessage’, ‘Please correct errors below.’);
}
}
}
Comment on November 14, 2007 @ 10:44 pm
Very helpful, thank you.
I used the cake-defined _selectedArray() controller method in order to get an array with the selected elements of the multiple select instead of your getSelectedItems() function.
It also seems more adequate as selectTag() needs only the keys of the selected options rather than ids=>names.
Regards
Comment on November 30, 2007 @ 9:02 am
[...] shepherdweb 上記サイトのとおりにやってください! [...]
Pingback on February 12, 2008 @ 9:48 pm
[...] Refferences: 1. HABTM Add & Delete Behavior 2. Editing hasAndBelongsToMany (HABTM) Relationships in CakePHP [...]
Pingback on April 9, 2008 @ 1:56 am
Hi.
Thanks is very helpfull.
Is this possible with checkboxes instead of multi-select ?
I’ve tried to do it with your code but it doesn’t work…
Comment on August 22, 2008 @ 2:48 am