CakePHP 2.2 / MongoDB 2.0.4
Let’s continue building on top of what we’ve done previously.
I’d say that the main consideration when it comes to building out your MongoDB is the choice between embedding everything into a single collection vs linking (or manual referencing) to another collection.
What does it mean?
If we take our blog example, we saw how easy it was to build a tiny app, which allows to add posts. No need to create any tables (collections) or define any sort of schema. Just get the data and insert.
Now we are ready to move forward and our application would probably require an ability to add comments. Because MongoDB is schema-less, first thought would be to store the Post and Comments into a single document. After all, this is the beauty of NoSQL, we don’t need any JOIN’s, we don’t have to worry about belongsTo or hasMany. Again, a simple head-on approach would be: get the data and insert it into the collection of documents (i.e. Posts and Comments are stored together, aka “embedding”).
This is all fine and dandy, and will likely work quite well for our simple example.
However, this post would be useless if we didn’t consider some other issues. For instance, it would become a bit more difficult (actually it can become very difficult in a more complex application) to single out a specific comment. Perhaps you need to edit one comment out of a hundred or so. Having Comments embedded with the Posts, will require a bit of hacking around. (I strongly urge you to read this wonderful post, which details some these considerations: http://seanhess.github.com/2012/02/01/mongodb_relational.html).
So, for the sake of the example (at least), we’ll separate our collections into something more RDBMS-like and create Post and Comment separately (aka “linking” or “manual referencing”).
As you probably suspect we’ll use the same approach as we would in a traditional SQL DB, and create a reference field in the Comments collection… post_id anyone?
Side note: the referencing that we are doing here is called “manual referencing” in MongoDB terms. There is also an option to use DBRefs, which a slightly more advanced approach. In most cases, and such as the one we have here, “manual referencing” is the recommended way to structure data. Also, at the time of writing PHP’s MongoDB driver does not yet support DBRefs, it is however in the works.
With all this mind let’s take a look at our CakePHP view, app/View/Posts/view.ctp:
<h3>
<?php echo h($post['Post']['title']); ?>
</h3>
<p>
<?php echo h($post['Post']['body']); ?>
</p>
<?php if (!empty($comments)) : ?>
<?php foreach($comments as $comment) : ?>
<p>
<?php echo h($comment['Comment']['user']); ?>
</p>
<p>
<?php echo h($comment['Comment']['body']); ?>
</p>
<?php endforeach; ?>
<?php endif; ?>
<?php
echo $this->Form->create(array(
'url' => array(
'controller' => 'comments',
'action' => 'add'
)
));
echo $this->Form->inputs(array(
'legend' => 'Add some comments:',
'Comment.user',
'Comment.body'
)
);
echo $this->Form->input('Comment.post_id', array(
'type' => 'hidden',
'value' => $post['Post']['_id']
));
echo $this->Form->end('Add comment');
?>
<?php endif; ?>
Nothing different here from our typical CakePHP view. The page will display a post, some comments (if available) and a form to add more comments.
As evident from the form, we should create a Comments Controller with an add() action. To allow the saving of the comments.
Let’s do so right now:
class CommentsController extends AppController {
public function add() {
if ($this->request->is('post')) {
if ($this->Comment->save($this->request->data)) {
$this->Session->setFlash('Your comment has been saved.');
return $this->redirect(array(
'controller' => 'posts',
'action' => 'view',
$this->request->data['Comment']['post_id']
));
} else {
$this->Session->setFlash('Unable to add your post.');
}
}
}
}
This is all that we would need in order to start saving Comments and their link (via post_id) to the actual Post. Again, since we are dealing with a schema-less DB there is no need to worry about creating anything on the DB layer. Our Comment model is also not required, because we don’t have any validation logic or custom methods, so an App Model instance, which is created by cake for us is sufficient for this basic operation.
Now, that the comments are saved we should also display them on the “view post” page. If you take a look at the view above, you’ll see that we’ve already setup the logic to display the comments and we’d expect a $comments data array to do so.
Here’s our view() method in the relevant Posts Controller:
Whoa… what happened to find(‘all’)? Why did I just issue two separate queries, and had to instantiate a Comment model like that?
Before you despair, let’s summarize a few things:
- We did not setup any models, so our relationship between Post and Comment is unknown.
- Currently the MongoDB driver does not support relationships as you would expect from a typical RDBMS. This is not to discourage you form the beauty of using an ORM. On the contrary, MongoDB is not meant to have deep-linking and complex relationships between collections. Remember, MongoDB is all about de-normalization. (That being said, there’s no reason why cake can’t support a basic one-level association, the driver is simply not there yet).
- Considering the above, this is not to say that we don’t need models at all. I always mention that all business logic should be tucked away in the model as much as possible. So this is not an excuse to make our controllers fat. The example is simple enough, where extra code would be wasteful.
I’m going to wrap things up at this point, as this should already give you some interesting ideas and food for thought. You’ll notice that I’ve embedded the Poster’s name with the Comment. Does that mean that I would take the approach of storing all user data with the Comments? No, not really. Here it is just a display name, which can be derived from the form or a session if we had logged-in users in the system.
Further considerations and examples of this will be a good topic for another day.