notEmpty validation rule

With the recent changeset (https://trac.cakephp.org/changeset/7399) a new, ‘notEmpty’, validation rule is now available. As you can guess, it checks to make sure that a field contains something other than a white space.

You will have to upgrade to the latest build, to ensure that this rule is available in your cake core.

Otherwise you can rely on this simple regex, to achieve the same thing:

‘rule’ => array(’custom’, ‘/\S+/’)

Displaying sort direction in paginated data

Just a little tip on styling your table with paginated data…

Wouldn’t it be nice to show to the user what column and direction is currently used for sorting of your data? Here’s how you can do that with a little CSS and some Paginator methods.

First, we need to find out in our view which column we use to sort and in what direction (ASC or DESC) the sorting goes:

$sortDir = $paginator->sortDir();
$sortKey = $paginator->sortKey();

Now that we know the two values, we can easily style the table header:

<th <?php echo ($sortKey == 'ID') ? 'class="'.$sortDir.'"' : ''; ?>>

In the example above, if our sorting field happens to be “ID” we assign a class=”asc” (for ascending) or class=”desc” (for descending), else no class is assigned.
This code uses a short-hand if/else condition, which is sometimes frowned upon, but I feel in this case it provides for a cleaner looking code.

Forcing an SQL JOIN in CakePHP

Update (23/7/2010) The improved way to handle this now-a-days: http://book.cakephp.org/view/872/Joining-tables
Update (10/7/2009)… this functionality has been rolled into Habtamable behavior

You’ve probably noticed that by default CakePHP will only create a JOIN query if you have a hasOne or belongsTo associations between your models. There are cases, however, when a JOIN is necessary to get just the right data.

So how do you tell cake to create a JOIN?

Well, you have to rely on some crafty methods, but all in all it’s not very hard, once you get the hang of it…

I will summarize here a few tricks I’ve encountered on the google group, so big thanks to the original authors of these ideas.

Let’s take our favorite sample models:

User hasMany Post
and
Post hasAndBelongsToMany Tag

If we were to do: $this->User->find(‘all’);
CakePHP will run a few selects and return you all the User data. The problem, however, is that if a User doesn’t have any posts CakePHP will still return you the User and an empty array for Post (I’m sure you’ve noticed that before)…

So let’s try to force cake to do a JOIN…

First things first, we have to make cake forget about our previous bindings between the models:


$this->User->unbindModel(array('hasMany'=>array('Post')));
$this->User->Post->unbindModel(array('hasAndBelongsToMany'=>array('Tag')));

Now, we need to trick CakePHP into thinking that we’ve got a hasOne relationship between our models, so that it will build a JOIN query…

We can use bindModel() to achieve that:

$this->User->bindModel(array('hasOne'=>array('Post'=>array(),
                    'PostsTag'=>array(
                        'foreignKey'=>false,
                        'conditions'=>array('PostsTag.post_id = Post.id')),
                    'Tag'=>array(
                        'foreignKey'=>false,
                        'conditions'=> array('PostsTag.tag_id = Tag.id')
))));

So what’s going on here?

We are telling cake to bind the User model using a hasOne relationship to the Post model, which forces cake to build the JOIN query like:

LEFT JOIN `posts` AS `Post` ON (`Post`.`user_id` = `User`.`id`)

Next, we need to ensure that we get all related information from our joinTable (i.e. posts_tags), so that we can eventually get the relevant Tags. Just as before we tell cake to bind PostsTag using a hasOne relationship. Note, we need to tell CakePHP how to do the JOIN condition by specifying the ‘conditions’ key. Therefore, cake will do:

LEFT JOIN `posts_tags` AS `PostsTag` ON (`PostsTag`.`post_id` = `Post`.`id`)

Last, but not least, we have to grab all the Tags. Again, we tell cake to join our Tag model using hasOne (of course here we also have to specify the conditions).

The complete query looks like:

SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`name`, `User`.`created`, `Post`.`id`, `Post`.`title`, `Post`.`post`, `Post`.`created`, `Post`.`modified`, `Post`.`user_id`, `PostsTag`.`id`, `PostsTag`.`post_id`, `PostsTag`.`tag_id`, `PostsTag`.`status`, `Tag`.`id`, `Tag`.`tag`, `Tag`.`status` FROM `users` AS `User` LEFT JOIN `posts` AS `Post` ON (`Post`.`user_id` = `User`.`id`) LEFT JOIN `posts_tags` AS `PostsTag` ON (`PostsTag`.`post_id` = `Post`.`id`) LEFT JOIN `tags` AS `Tag` ON (`PostsTag`.`tag_id` = `Tag`.`id`) WHERE 1 = 1

OK, this is much better and pretty much what we need. However, we are still getting records with empty values, yet we need only the records where the User has a Post and a Post has a Tag.
Well, this is actually due to the fact that we are using a LEFT JOIN, what we really need is an INNER JOIN. You can look up the difference, if you are not sure, but basically INNER JOIN ensures that matching records must exist in all tables.

Remember that for a hasOne (or belongsTo) relationship you can specify a ‘type’ key, so go ahead and modify the above bindModel() call to include ‘type’ => ‘INNER’ for both PostsTag and Tag association.
How? Here’s a hint: ‘foreignKey’=>false, ‘type’=>’INNER’, ‘conditions’=>array(‘PostsTag.post_id=Post.id’)).

Now, we’ve got just the records we were looking for.

A quick note on ‘foreignKey’=>false…
It’s necessary to specify that, so that CakePHP does not attempt to automagically establish a relationship between the models, instead it forces cake to use our conditions for the JOIN
(i.e. ‘conditions’=>array(‘PostsTag.post_id = Post.id’)).

P.S. as josoroma pointed out, if you use this method with paginate() be sure to supply a ‘false’ param, to your bindModel() so that the binding persists for all subsequent methods, i.e. paginate() and paginateCount()

LEFT JOIN vs INNER JOIN

By default, if you have a hasOne or belongsTo relationship between models, CakePHP will build a JOIN query using LEFT JOIN. In some cases, however, you would really prefer to do an INNER JOIN instead.

This is easily accomplished by specifying the ‘type’ key in your association.

So, for example, User hasOne Post:

var $hasOne = array(‘Post’=>array(‘type’=>’INNER’));

That’s all. Cake will now build a query using the INNER JOIN.

Remember that this key only works in the hasOne or belongsTo associations. If you would like to know how to force CakePHP to do JOIN for other types of associations, please take a look at this post.

Notes on CakePHP HABTM (Part 2, saving data)

If you haven’t read part 1, where I cover some HABTM basics, you should probably take a look at that article as well.

Part 2. Saving data

The next topic I wanted to cover is how to go about saving the HABTM data. Actually it is really not that complicated, as most of the work is taken care of for you.
Probably the most important thing is to make sure that your data arrives in the correct format for the save() method. The rest is really pretty easily handled by cake.

So let’s take a look at a few examples (still using our Post HABTM Tag)…

First, we’ve got a case where we know a Post ID and we are creating a single, new tag for it. We’d like to save the tag name and have CakePHP automagically associate it with a given post.

The form would look something like this:


echo $form->create('Tag');
echo $form->input('tag');
echo $form->input('Post.id', array('type'=>'hidden', 'value'=>5));
echo $form->end('Add tag');

And the controller action would be:


function add() {
$this->Tag->save($this->data);
}

Let’s also take a look at our data:

Array
(
    [Tag] => Array
        (
            [tag] => new one
        )

    [Post] => Array
        (
            [id] => 5
        )

)

To clear things up quickly, CakePHP will insert the new tag, grab the ID of the last insert and save the ID of the known post plus the ID of the newly created tag into the join table.

Now, let’s say you need to associate that same tag with a few different posts. Well, the only thing you need to do is to, again, make sure that you have correct fields in your form… so:


echo $form->create('Tag');
echo $form->input('tag');
echo $form->input('Post.1', array('type'=>'hidden', 'value'=>5));
echo $form->input('Post.2', array('type'=>'hidden', 'value'=>6));
echo $form->input('Post.3', array('type'=>'hidden', 'value'=>7));
echo $form->end('Add tag');

Will produce a data array that looks like:

Array
(
    [Tag] => Array
        (
            [tag] => my brand new tag
        )

    [Post] => Array
        (
            [1] => 5
            [2] => 6
            [3] => 7
        )

)

Basically this will save a new tag and associate that newly created tag with posts, which happen to have ID’s 5,6 and 7. I should mention that in the previous example the field could be named Post.1 instead of Post.id, but I find that ‘id’ is more descriptive and works just as well.

Also, in the example I’m using a hidden field with a given value for the ID, in the real life it would probably be a few checkboxes, with values for ID’s.

And lastly it’s worth to note that the above can be reversed, just as well you could setup a quick form to create a new Post and associate some known tags with it.

P.S. Be sure to check out Habtamable behavior, which helps to avoid some of the pain when working with HABTM models.

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.

with
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.

joinTable
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(
                                               'joinTable'=>
                                               'my_cool_join_table'));
}

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:

$this->Post->PostsTag->find(‘all’);

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'), 
                                                      'group'=>'tag_id')));

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:

$this->Post->PostsTag->bindModel(array(‘belongsTo’=>array(‘Tag’)));

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

What can we learn from CakePHP tests?

Update: Currently the API for test cases searches is down (but coming back soon).
Remember, that you still have the test cases in the core of cake, so you can and should utilize them all the time ;)
(If anyone wishes to volunteer their time to help out to bring test case search online, please contact Gwoo or Mark Story)

First things first, all current CakePHP test cases are now searchable here:
http://api.cakephp.org/tests/

If you ever find yourself stuck wondering how to use some feature in CakePHP look to the test cases to provide a helpful hint.

Let’s consider an example.

I don’t know how to use SQL’s BETWEEN syntax in cake… let’s see if there are any matches for “between” in the test cases.

Search at the above link, and a few results, but here’s one: DboSourceTest::testArrayConditionsParsing
Hmm… conditions parsing? Sounds about right…
And looky-looky what we’ve got there.
All sorts of examples including the BETWEEN syntax.

Hopefully you can see how great it is to have the test cases available and how much you can discover and learn by digging around in the tests source code.