shepherdweb.com

Editing hasAndBelongsToMany (HABTM) Relationships in CakePHP

August 21st, 2006

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:

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!

24 Comments »

  1. Richard@Home » Blog Archive » links for 2006-08-23 wrote,

    [...] 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

  2. Joel Stein wrote,

    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

  3. Shane wrote,

    @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

  4. andy wrote,

    good info, but what is returned by “/colors/select” ?

    Comment on September 12, 2006 @ 9:11 am

  5. Shane wrote,

    This is the code for “/colors/select”:

    	function select()
    	{
    		return $this->Color->generateList();
    	}
    

    Comment on September 13, 2006 @ 6:45 pm

  6. max wrote,

    @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

  7. Aran wrote,

    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

  8. Shane wrote,

    @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

  9. Steve wrote,

    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

  10. Steve wrote,

    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

  11. Steve wrote,

    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

  12. Steve wrote,

    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

  13. Shane wrote,

    @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

  14. Steve wrote,

    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

  15. Shane wrote,

    @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

  16. Steve wrote,

    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

  17. Shane wrote,

    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

  18. Steve wrote,

    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

  19. Shane wrote,

    @Steve - You are too kind. :)

    Comment on March 22, 2007 @ 4:01 pm

  20. jas wrote,

    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

  21. David wrote,

    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

  22. toyosystem | CakePHP::HABTMの編集画面を作る wrote,

    [...] shepherdweb 上記サイトのとおりにやってください! [...]

    Pingback on February 12, 2008 @ 9:48 pm

  23. Palivoda IT Solutions » Blog Archive » HABTM with multiple select in CakePHP 1.2 wrote,

    [...] Refferences: 1. HABTM Add & Delete Behavior 2. Editing hasAndBelongsToMany (HABTM) Relationships in CakePHP [...]

    Pingback on April 9, 2008 @ 1:56 am

  24. Gianni wrote,

    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

Leave a comment

RSS feed for comments on this post.

TrackBack URI

Bad Behavior has blocked 679 access attempts in the last 7 days.