Adding Comments to a CakePHP Blog App (hasMany, belongsTo)
One of the most powerful features of CakePHP is the ability to handle model associations, at its very simplest an association occurs when some data in the database is related to some other data. In normal PHP applications you would normally have to write a custom SQL statement that will grab the data using a JOIN statement. Obviously this kind of custom query can be accomplished in CakePHP but it will automatically grab all the related data as long as you have specified the relationship in the Model file.
In a simple example i'm going to stick with the mandatory 'Blog Application' and add a comment system, so that people can assign comments to posts. Before starting I recommend reading the Associations section in the manual and my previous post about using the 'bake' script.
Creating the Comments Table
The first thing we should do, as with all applications you build, is create the database. I'm going to be following on from my previous posts and at the moment i only have 2 tables which include 'posts' and 'users'. We need another to hold the 'comments' information and the most important field in the table is the 'post_id' column which indicates which post the comment belongs to and acts as a foreign key.
CakePHP expects this to be in a very specific format, and this the name of the model followed by '_id', in PHP terms this is very common and hopefully it should come as second nature to you, if not then follow this practice and you should be fine. Below is the SQL statement used to generate the 'comments' table.
CREATE TABLE `comments` ( `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY , `post_id` INT NOT NULL , `name` VARCHAR( 255 ) NOT NULL , `email` VARCHAR( 255 ) NOT NULL , `text` TEXT NOT NULL , `created` DATETIME NOT NULL );
Creating the Models and Relationships
With any new table in the database we also need a Model, View and Controller files, so create a file called 'comment.php' in the 'models' folder. This is where the relationships between models need to be defined, in this case we need to setup a 'belongTo' association. This is the correct association to use because a comment can only belong to a single post, at this stage we are only going to use the 'className' variable. By reading the manual you will find there are plenty of options to use so that your can order or limit the related data.
class Comment extends AppModel {
var $name = 'Comment';
var $belongsTo = array('Post'=>array('className'=>'Post'));
}
We also need to define the association in the 'post' model so that the relationship works both ways, i.e. a post can see its comments and a comment can see the post it belongs to. The association in the 'post' model is going to be a 'hasMany' and is created the same way as before, again we will just be using the 'className' variable.
class Post extends AppModel {
var $name = 'Post';
var $hasMany = array('Comment'=>array('className'=>'Comment'));
}
Baking the Blog Application
Now that we have setup our Models, we need to go through the process of baking our Controller Files and our View Files, if you dont know how to do this then please go through my previous post as i wont be going through it here. Hopefully if your Model files do not contain errors you should not encounter any problems with the bake script.
Another great thing about the Bake script is that it will automatically create the code for displaying related data, so if your still finding your way around CakePHP and dont know how things relate just yet you can look at the generated source code.
Have a look at your application in a browser, when viewing a post you should be able to add a comment to that post and if you have already done so, see a list of comments that have been added. Just like with the posts you will be able to add, edit and delete comments and all the necessary views and application logic has been created for you.
How CakePHP works with related data
If we have a look at the data for one of the posts using the 'print_r()' php function we can see that CakePHP gets all the post information as well as all the related comments. This happens automatically when a $this->Post->read() or $this->Post->findAll() call is made. To access the post information we simply need to access the $post['Post'] array and for the comments we can simply loop through them all using foreach($post['Comment'] as $comment).
Array
(
[Post] => Array
(
[id] => 1
[title] => The title
[body] => This is the post body.
[created] => 2007-12-28 13:54:34
[modified] =>
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[name] => James
[email] => info@jamesfairhurst.co.uk
[text] => This is a sample comment.
[created] => 0000-00-00 00:00:00
)
[1] => Array
(
[id] => 2
[post_id] => 1
[name] => James
[email] => info@jamesfairhurst.co.uk
[text] => This is another sample comment.
[created] => 0000-00-00 00:00:00
)
)
)
Because the associations were setup both ways CakePHP also grabs the post information when viewing a comment, and this can be seen in the comment data.
Array
(
[Comment] => Array
(
[id] => 1
[post_id] => 1
[name] => James
[email] => info@jamesfairhurst.co.uk
[text] => This is a sample comment.
[created] => 0000-00-00 00:00:00
)
[Post] => Array
(
[id] => 1
[title] => The title
[body] => This is the post body.
[created] => 2007-12-28 13:54:34
[modified] =>
)
)
This is standard CakePHP way of getting related data so once you get to grips with using and accessing your data in this way it will become second nature in the future.
Wrapping Up
Setting up an application that uses associated data is really quite simple in CakePHP, to make sure that everything will work you need to make sure that your database and Models have been setup correctly. Usually if this is not the case you will encounter errors when baking your application. Just remember that you will need to properly setup all the foreign and primary keys in the database using the proper CakePHP naming conventions. A quick look in the manual will point you in the right direction if you are having problems or alternatively just drop me an email or comment and i'll respond as soon as can.
Comments
ega (30/09/2008 - 01:31)
hello.. i don't know what i missed out but i got this error once i added new comment...
Notice: Undefined index: in C:\server\xampplite\htdocs\cakephp\app\views\comments\index.thtml on line 17
Notice: Undefined index: in C:\server\xampplite\htdocs\cakephp\app\views\comments\index.thtml on line 17
James (30/09/2008 - 04:59)
@ega: looks like your trying to access an index 'in' that doesn't exist in your view file. It should be just a typo if you've created the page manually.
ega (03/10/2008 - 04:45)
Thanks for the response James!
I really try to find time to follow your tutorials. I can't to start your Full cakePHP app tutorials. I hope after doing this, I can now develop my own app using cakephp. Thanks!
melanger (26/12/2008 - 00:31)
Hey James, this whole series is perfect for beginners like me trying to wrap their head around cakephp. I love that I can extend the functionality of the same blog project with clear and simple steps. sensational!
@ega - I got this same error initially. I looked through the 'post' views and controller and noticed that they had not been updated to handle the new 'comments' after baking the new 'comments' view and controller.I simply re-baked the 'posts' controller and view as per the previous article and voila. I am assuming that 'posts' need to be rebaked to handle the new functionality created in the 'post' model(var $hasMany = array....etc).
James (26/12/2008 - 01:40)
@melanger: Hey, thanks for commenting and I'm glad these are helping you out.
Shafiq Jetha (07/05/2009 - 22:29)
Hi James,
I seem to keep finding my way back to your site, via Google, looking up ways to build my CakePHP app, so I thought I'd post asking a question:
I currently blog posts that connect to users using a hasMany relationship, but I also want comments made by registered users attached to them too. These seem to be working but the problem is that, when viewing a particular blog post, I am unable to get the users associated with each comment, they just don't appear in the array, even though they appear fine if I'm viewing a single comment.
I feel the problem might be because there is a hasMany relationship (between comment and user) within another hasMany relationship (between post and comment). If you have any ideas on this it would be greatly appreciated.
Thanks,
Shafiq.
James (07/05/2009 - 23:54)
@Shafiq: Hi and thanks for commenting. Your problem may be solved by setting the recursive variable to a higher level
e.g. $this->Post->recursive = 2;
This way your Post model is finding Comments which in turn finds associated User's, either that or your Model associations are incorrect somewhere although if everything is ok when viewing a single Comment I would assume that they are working fine. Let me know how you get on.
Shafiq (08/05/2009 - 06:41)
Hi James, thanks for the quick reply.
I guess I was on the right track since I had:
$this->Post->Comment->recursive = 2;
in my Posts Controller anyway.
Thanks for your help!
Shafiq.
Web Design Quote (05/06/2009 - 21:33)
Hello james.. thanks for the post and regrading the CakePHP blog application. i hope readers are easily understand this, Keep adding more tutorial.
Rudy (24/06/2009 - 08:45)
Hello James,
Thank you for the great tutorials you are adding, it's really very useful for beginners. I'm developping a news/comments features, exactly same way as you're doing in this article. I have a link to add a comment from my news view pages, which is working, but i would like to pass the id of the linked news as a hidden field, not giving the opportunity to the user to select it. Would you have any idea on how to do this?
nathi (06/09/2009 - 00:14)
Hi James,
Still waiting for Rudy's question to be answered.
thanks for the tuts
James (08/09/2009 - 04:48)
@nathi & @Rudy: sorry must of missed that question. This one's quite a tricky one, you can use a hidden field to pass the post id but with any form a malicious user could alter it when sending the POST data to the server.
Another approach would be to save the Post Id in the Session when the user views the post and use that value when adding comments. For added security you could also add a timestamp to the session and also save is as a hidden input field and compare the two when the form is submitted. This ensures that the user came from your site when the form was submitted.
Hope that helps!
Chris (10/02/2010 - 10:08)
Just to clarify, in the above example, when you add a comment, would you do it within the form at /posts/add/ or would you have to go to /comments/add/ ?
James (14/02/2010 - 22:35)
@Chris: I would put the logic in the "comments/add" method and maybe do a check to see if the post id is already set. This way you can keep the logic for a normal add comment. Hope that makes sense.
foroctfralion (06/03/2010 - 17:02)
Hi James,
I think I am trying to do something similar to Shafiq. I have a users model, a posts model and a comments model. I am just a bit unsure about the proper associations. Currently a user hasMany posts and a user hasMany comments, posts hasMany comments and posts belongsTo a user. So how does comments work? Comments belongTo both a user and a post?
Thanks for your time and great posts.
James (09/03/2010 - 11:45)
@foroctfralion: yeah Comments belong to both a User and a Post, this way you can grab all the Comments by a User and also grab all the Comments from a Post.
Adam (09/07/2010 - 00:06)
Hey James,
If a Post hasMany and a Comment belongsTo, will the comments that belongsTo a Post be deleted automatically when the Post is deleted? Likewise for creating?
James (19/07/2010 - 05:42)
@Adam: There's an option in your model to automatically delete related data, so when a post is deleted all comments are also removed. Check out the 'dependent' option when setting up <a href="http://book.cakephp.org/view/1041/hasOne" target='_blank'>associations</a>