Notes on CakePHP HABTM (Part 1, the basics)

Part 1. The basics

HABTM seems to give a lot of people trouble, so I wanted to cover a few points that may or may not be in the manual. And I will assume here that you have basic understanding or some knowledge of HABTM, otherwise you should really go and read the manual first… (By the way, do read the manual if you are having trouble. It’s constantly evolving and some points you’ve missed before may very well be covered).

OK, so let’s begin with a good ol’ Post HABTM Tag.

Here’s the simplest definition of the Post model:

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

(In PHP5 you won’t even need $name, but let’s leave it in for a few years).
To make HABTM work you have to have a very similar definition of the Tag model:

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

And the magic has happened already…

However, unless you know a thing or two about magic, it can be hard to figure out what’s going on because CakePHP makes a lot of assumptions if you do not manually override the defaults.

I’m going to cover here with and joinTable as I find these to be the most often overlooked/misunderstood HABTM basics.

The with key specifies an auto-model, which cake creates to represent the join table (joinTable in CakePHP syntax). If we follow the defaults the joinTable model is named PostsTag and you can use it like any other regular CakePHP model. Yes, it is automagically created for you.

Still following conventions, CakePHP will assume that the joinTable is named posts_tags. Table name consists of plural model names involved in HABTM and is always in alphabetical order. So, P goes before T, therefore posts_tags.

Well, let’s say you don’t like the name posts_tags and instead would like to rename your joinTable to my_cool_join_table…

Off you go and modify your Post model like so:

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

Guess what? You’ve just messed with the magic.

First of all CakePHP will now rename your auto-model to MyCoolJoinTable, so if you had some references in the code to PostsTag (the default with model) you have to go and change them.
Secondly, and maybe more importantly, you’ve probably forgot about your Tag model. If you haven’t made any changes, Tag will still expect all the defaults. This can create a lot of mess and bizarre, unexpected results. So the point here is that since HABTM goes in both directions any changes you apply to one model definition should most likely be manually applied to another.

Now, what if you don’t like PostsTag name and would like to rename your join (with) model to PostTagJoin?

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

Changing the with key will not affect the joinTable value (i.e. CakePHP will not assume posts_tags_joins or something) so if you don’t change the default, CakePHP will still expect posts_tags.
In other words, with only changes the name of the auto-model, so in reality it’s not something one would or should bother to do.

Now onto some good stuff…

Having this auto-model is quite handy if you are interested in the joinTable data and becomes even more powerful when you have some additional fields in the joinTable, which you need queried or saved.

In the Posts controller you could do:


Or you could even apply some conditions to find the most popular tag ID’s:

$this->Post->PostsTag->find('all', array('fields'=>array('tag_id','COUNT(tag_id) AS total'),

One caveat to note here is that PostsTag is not automagically associated with neither Post nor Tag. So in order to fetch some associated data, you’ll have to manually do something like:


Now, using the above example, you can even get the most popular Tag names by using Containable in the above query or simply increasing the recursive of PostsTag.

Part 2, saving HABTM data

Related Posts