Archive for April 19th, 2007|Daily archive page

CakePHP: HasAndBelongsToMany (HABTM)

WARNING, this post is related to the 1.1 version of the framework. Now there’s the 1.2, so maybe something may not work properly.

Originally posted by me on June 6th, 2006 on another blog.

Cake allow you to relate two tables in a n<->m relation. This is done by the HasAndBelongsToMany relationship (HABTM). In order to implement this relationship you need three table: the two tables to be related and a table for the relationship.

Profiles      relation-table          Users
--------      --------------          -----
id* --------> profile_id*     |------ id*
dx            user_id*    <---|       dx

If you’ll follow the cake naming conventions, implementing this relation is very simple. Let’s quickly sum up the conventions

  1. Relation table must be named like PluralTable1_PluralTable2
  2. Tables that give the name to the relation table must be alphabetically sorted. So if you have a users and a profiles table, you’ll get the profiles_users.
  3. The fields of the relation table must be the primary keys of the related tables and must be named like SingularTable_id. For example profile_id and user_id.
drop table if exists profiles_users;
create table profiles_users(
   profile_id int(8) unsigned not null default 0,
   user_id int(8) unsigned not null default 0,
   primary key(profile_id, user_id)
)engine=MyISAM;

Now in the model of the tables you need to relate them. This is done by the $hasAndBelongsToMany variable.

class User extends AppModel{
   var $name="User";
   var $hasAndBelongsToMany = array("Profile");
}

class Profile extends AppModel{
   var $name="Profile";
   var $displayField = "dx";
   var $hasAndBelongsToMany = array("User");
}

Done and done. Now everything should be handled automatically by cake.

Now you just need to insert the fields in the form. Example on the manual use a multiple section <select> tag. In order to create this control you just need the HtmlHelper and selectTag():

$html->selectTag('Profile/Profile',$profiles,null,array('multiple'=>'multiple'))

The only trick is the field name that must be RelatedTable/RelatedTable. In the example, if we are adding a user (users table) and need to save the specified profiles (profiles table), the name to be specified will be Profile/Profile.

In case you don’t like the use of a multiple select and prefer to use more than one checkbox, the workaround is something like the following.

In the needed controller let’s extract the needed values

class UsersController extends AppController{
   var $name="Users";

  function add(){
      ...
      $this->set("profiles",$this->User->Profile->generateList());
      ...
   }
}

Then in the view something like

<?php
   if(is_array($profiles)){
      foreach($profiles as $profile => $title){
         print "<input type='checkbox' value='{$profile}'
		name='data[Profile][Profile][]'>" . $title . "\n";
      }
   }
?>

Notice the name parameter. I’ve not used the HtmlHelper::checkbox() since I failed to make it works in this case.