Adding Tags to a CakePHP App (hasAndBelongsToMany)

In this article I'm going to concentrate on the 'hasAndBelongsToMany' (HABM) model association by integrating Tags to the Blog application that we have experimenting with. As you are probably aware Tags have become a popular method of organising posts and are really just categories. This type of relationship is different from the others you have been using because a single post can have multiple Tags and a single Tag can have multiple posts. This is different from the comments example that I looked at previously where a comment can only belong to a single Post.

As usual I suggest reading the models chapter of the online CakePHP manual, taking special attention to the 'Defining and Querying with hasAndBelongsToMany' section near the bottom. At first I had problems with choosing an association to use but this will come with practise and experience building different applications. If you're having trouble just drop me an email and I'll try to help you out.

Designing the Database

When using this type of association (HABM) we need an extra table in the database to keep track of all the associated data. Once this has been setup we don't need to worry about it in our application because CakePHP will take of the associated data when getting and saving our information.

I'm going to create a simple 'Tags' table in the database that will hold the name of the tag along with when it was created and modified. (Just a side note, if you have a 'created' and 'modified' fields in your table CakePHP will automatically use these when adding and editing data).

CREATE TABLE `tags` (
  `id` int(11) NOT NULL auto_increment,
  `tag` varchar(100) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY  (`id`)
);

Next I'm going to create the table that will link the 'posts' with their 'tags', this is a simple table with just the primary keys of the two tables. In this case 'post_id' and 'tag_id', the name of the table is also very important and allows CakePHP to automatically build the relationship, we need to call the table the plural versions of the two tables in alphabetical order. In the case of 'posts' and 'tags' we simply need to join them together with an underscore in alphabetical order and we get 'posts_tags'.

CREATE TABLE `posts_tags` (
  `post_id` int(11) NOT NULL,
  `tag_id` int(11) NOT NULL
);

Creating the Models and Relationships

Like in all of my previous articles I'm going to start by creating the model files, create a file called 'tag.php', define the class and the HABM relationship like any other model you would create and do the same for the 'post.php'. I didn't have to modify the comment model so your 'post' and 'tag' model should look like the ones I have below.

class Tag extends AppModel {
	var $name = 'Tag';
	var $hasAndBelongsToMany = array('Post'=>array('className'=>'Post'));
}

class Post extends AppModel {
	var $name = 'Post';
	var $hasMany = array('Comment'=>array('className'=>'Comment'));
	var $hasAndBelongsToMany = array('Tag'=>array('className'=>'Tag'));
}

The relationship has been defined using the absolute bare minimum of configuration and this is enough for the time being. This only works however if you are using the naming conventions set out by CakePHP (which we have done in this example) so everything will work just fine.

Baking the Application

Hopefully by now you should be able to bake the controller files along with all of the view files for each of the models in the application, if not then please go through my previous article on 'baking with CakePHP'. Now that you done that you can test your application in a browser.

CakePHP Tags

If we go to our application (http://cakephp/tags/) just like our posts, we can start adding, editing and deleting tags. Start by adding a few Tags, the first thing you will notice is that you can choose posts that may be related to your Tag and this is all done automatically because we have followed the conventions set out by CakePHP.

CakePHP Tags

If we try to add a new post (http://cakephp/posts/add/) you will notice that the 'related tags' are not displaying correctly. To fix that we need to make a small change in the 'posts_controller.php', in the add function CakePHP uses a method called 'generateList()' which is extremely handy for creating a html select tag. At the moment the method is returning just the id of the tags:

Array
(
    [1] => 1
    [2] => 2
    [3] => 3
)

This isn't very helpful and we don't know the name of the tag without delving into the database, to fix this we need to know how the 'generateList()' method works. To find this out we need to use the highly useful API. Go to the API page and go a search for the method name, it should return a few results, click on the top item in the list. This will take you to a page that shows all the arguments that the method can take. All we are interested in is the $keyPath and $valuePath which correspond to the key and values of the array that it returns. To get the tag name we simply insert "{n}.Tag.name" into the fifth argument leaving everything else null so we have:

$this->set('tags', 
$this->Post->Tag->generateList(null,null,null,null,"{n}.Tag.tag"));

Now instead of a number this will return the name of the tag because we have specified which column in the database we should select in this case its 'tag' which is just the name of the tag.

Array
(
    [1] => General
    [2] => Programming
    [3] => Films
)
CakePHP Tags

In our add post page the tag names should be properly displayed, please note that this change needs to be made throughout your 'posts_controller.php' file. If you also want to remove the empty space in the select tag, open up the add post view file and in your $html->selectTag simply change the final argument from 'true' to 'false'.

$html->selectTag('Tag/Tag', 
	$tags, $selectedTags, 
	array('multiple' => 'multiple', 'class' => 'selectMultiple'), 
    array(), false);

This problem has been encountered many times before especially in the CakePHP Google group, however I'm trying to push the use of the API and the manual before you concede and ask a question online. Obviously if you're really stuck then head online and try searching for a solution before asking a question, no doubt someone else has come across the same problem and it's been answered already. A few resources for help include the CakePHP Google group and the unofficial forum which I've used a few times before with great results.

Wrapping Up

One of the biggest problems that you'll encounter when starting with CakePHP is understanding how your models are related and which associations to use. The HABM relationship is unique as it requires an extra table in the database to keep track of everything, this table is necessary because each 'tag' can have multiple 'posts' and vice versa. This is unlike the 'posts' and 'comments' association where we had to include the 'post_id' as a foreign key in the 'comments' table, notice that this is not the case in the 'tags' table.

The most important part of building any application in CakePHP are your model files, if they are setup correctly you should have no problems baking your controller and view files. If you do encounter problems with bake, then check and double check your model files. Again I'm going to emphasise reading the 'Models' section of the manual especially the associations chapters, if you are having trouble with something then drop me an email and I'll try and help you out. In my next CakePHP article I'm going to dealing with file uploads so check back soon.

Posted on 15th February 2008
on 15/2/08

comments powered by Disqus